# Magogi Foundation Standards — Conformance Tracker (mgf-logship)

> Per-rule conformance ledger (DOC-15 / CFM-01..04). Declares this project's
> conformance posture and accounts for every current MUST in the standards
> corpus. Created 2026-06-07 (previously missing — a DOC-15 gap).

## §1 You are here

- **Shape:** federation sibling library (`mgf-logship`) — a fail-open log-shipping handler + versioned wire protocol + stub collector. **Current version:** 0.1.4.
- **Declared TARGET level: L2.** **Verified standing (2026-06-08 depth audit): below L2.**
- Of **218** MUSTs: **105 ✅ verified** (file:line, refute-survived) · **16 ⏳ open gaps** · **96 N/A** (library shape) · **1 declined**. Applicable = **124**; **106/122 (87%) evidenced** (the youngest library audited — strong fail-open CORE, thin CI/docs/test/public-API hygiene).
- **Strong CORE:** DP-06 ✅ the FAIL-OPEN guarantee holds (never raises into the app; counters + stderr handleError, never a log-loop; drops queryable; token redacted+header-confined; bounded deque); DP-13 ✅ deterministic teardown (worker joined + final drain in close(); with/try-finally on socket+sqlite); PKG-06 ✅ uv index pinning.
- **Gap clusters (the 67 ⏳ — detail in §6/§Z):**

| Cluster | Rules | Count |
|---|---|---|
| CI publish pipeline + import-linter + image-pins | PKG-03, PKG-04, PKG-05, PKG-07, PKG-08, PKG-09, PKG-12, REL-01, REL-06, REL-08, WF-02, WF-03, WF-04, WF-05, WF-06, WF-07, WF-08, WF-09, WF-11 | 19 |
| SCOPE policy absent (SP-01..06) | SP-01, SP-02, SP-03, SP-04, SP-05, SP-06 | 6 |
| API / docs surface (PUBLIC_API.md/json, __version__, examples, buckets, drift, DEPS) | AP-02, AP-10, AP-11, AP-13, AP-14, DOC-01, DOC-02, DOC-03, DOC-07, DOC-08, DOC-09, DOC-10, DOC-13, DOC-16, DOC-17, DOC-18 | 16 |
| TESTING (coverage gate, property, security-pin, canonical dirs, supervisor) | TS-06, TS-09, TS-10, TS-13, TS-14, TS-19, TS-20, TS-21, TS-22, TS-23, TS-24, TS-25, TS-27, TS-28 | 14 |
| SECURITY (TLS-for-POST, audit, redaction-pin, SBOM) | SC-08, SC-12, SC-14, SC-17, SC-18 | 5 |
| DP-02 collector provider-seam + DP-08 replaceability doc | DP-02, DP-08 | 2 |
| misc (OP-03 URL-CI, PR-01/07, AG-15/16) | AG-15, AG-16, OP-03, PR-01, PR-07 | 5 |


## §2 Per-standard audit

Evidence-based per-MUST verdicts (file:line, refute-survived) are in §6/§Z below.
Notable gaps: DP-02 (the collector/transport is direct urllib, not behind a
project-owned provider Protocol+stub — the one place the seam isn't built yet),
the CI publish pipeline + import-linter + image-pins, PUBLIC_API surface, and
the TESTING family (coverage gate / property / canonical dirs).

## §3 Decline-with-rationale ledger (CFM-04)

| AP-14 | API_DESIGN | import-linter 'consumer fixtures only public' | No in-repo consumer harness; 2 closed-box contracts + complete PUBLIC_API satisfy the intent. | 2026-06-08 |

## §4 Audit log

| Date | Trigger | Change | Result |
|---|---|---|---|
| 2026-06-08 | depth audit (16-family fan-out + refute; ~34 agents + 27-id re-run) | scaffold replaced with evidence-based per-MUST verdicts | **57 ✅ / 67 ⏳ / 94 N/A** of 218 (46% of applicable); L2-target → below-L2. Strong fail-open core (DP-06/13 ✅); gaps are CI-publish/import-linter, DP-02 seam, PUBLIC_API, TESTING, SCOPE. Audit only — no code changed. |
| 2026-06-07 | DOC-15 gap fix + CFM-03 completeness pass | ledger created; §6 enumeration of all current MUSTs appended | complete (scaffold; depth audit pending) |

---

---

## §Z Per-MUST evidenced audit (all 259 MUSTs (PAPER-100 re-baseline 2026-06-11) — CFM-03 completeness)

> **2026-06-08 depth audit** (16-family fan-out + adversarial refute on every ✅, hardened prompt + extra-ruthless FAIL-OPEN/network-reliability pass; ~34 agents + a 27-id audit-only re-run for 4 truncated families [LG/PKG/OP/AG] + REL-09 [the auditor had typo'd it as a nonexistent REL-13, now dropped]). No 🚫-flood, no mislabels. **Mix: 57 ✅ · 67 ⏳ · 94 N/A · 0 🚫.** Replaces the prior scaffold enumeration.


### DESIGN_PRINCIPLES

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| DP-01 | ✅ | src/mgf/logship/ has 4 modules; __init__.py:21-29 imports _protocol + _shipper only; _shipper.py:38-48 imports _protocol + mgf.common.observability; collector.py:30-35 imports _protocol | DP-01: refute attempt failed. Literal MUSTs are (a) UI MUST NOT be imported by core and (b) core MUST NOT import a behind-a-provider SDK. This is a 4-module leaf with no UI/core split (the §14 mgf-common-style carve-out where inte |
| DP-02 | ⏳ | No abstract base class for either the network transport (single urllib path, _shipper.py:206-222) or the collector. collector.py is described as a 'store-now stub' but is a real FastAPI | DP-02 APPLIES per the prompt's provider-seam guidance (the network/collector is a third-party-I/O seam with a stub + real shape). The MUST — every third-party I/O dependency sits behind an ABC with >=1 production and >=1 mock impl |
| DP-03 | N/A | No @dataclass anywhere in src (grep confirmed). Cross-module identity types are intentional plain dicts: the wire record is dict[str, object] (_shipper.py:87, _protocol.py:53/66) becaus | DP-03 (frozen domain types; no bare dict as identity across a module boundary) does not bind this shape. The only inter-module data is the wire record, which is deliberately a JSON dict because the protocol is language-neutral (a  |
| DP-04 | ✅ | pyproject.toml:101-113 [tool.mypy] strict=true, warn_return_any=true, packages=["mgf.logship"]; py.typed present (verified via ls). All public sigs typed: _shipper.py:59-74 __init__, :1 | DP-04: refute attempt failed. mypy --strict configured against the package, py.typed ships, every public function/method carries full annotations incl. return types. The only Any is at the inherent arbitrary-JSON decode boundary ( |
| DP-05 | ✅ | tests/unit/ mirrors the 3 testable modules: test_shipper.py / test_collector.py / test_protocol.py. All I/O is local: test_shipper.py:53-60 fail-open targets unreachable 127.0.0.1:1; :7 | DP-05: refute attempt failed. The four literal MUSTs — mirror src/, no production credentials, no network, no real third-party daemon — all hold: the only 'server' is an ephemeral loopback http.server in-process and an in-process  |
| DP-06 | ✅ | FAIL-OPEN: emit (_shipper.py:100-111) wraps serialization in try/except->handleError, then a lock-guarded deque.append + _ensure_running — no raise path into the caller; enqueue cannot  | DP-06: refute attempt failed under the cardinal FAIL-OPEN lens. (1) Can the handler raise into the app? No — every emit path is try-guarded or a non-raising deque/Event op. (2) Can it log-loop? No — its own diagnostics are counter |
| DP-07 | ✅ | Module docstrings: _shipper.py:1-22 (intent + 3 invariants), _protocol.py:1-29 (wire contract + additive-versioning rule), collector.py:1-18, __init__.py:1-17. Class LogShipper:57. Publ | DP-07: refute attempt failed. Every public class/function and all four modules carry intent-and-invariant docstrings; inline comments are why-comments (fail-open rationale, deque eviction semantics, timing-attack defense), not par |
| DP-08 | ✅ | Module docstrings describe intent but there is no ARCHITECTURE.md / §5 replaceability swap-recipe table for the transport or collector backend (no docs/ARCHITECTURE.md found alongside t | DP-08 is a SHOULD: each major component's nearby doc MUST describe the swap recipe. With no provider ABC (DP-02) and no architecture/replaceability table documenting how to swap the transport or collector backend, the swap-recipe — FIXED 2026-06-08 (0.1.4): _shipper module docstring now carries the Replaceability note (swap the collector / wire adapter; the bundled collector is a stub, not the only sink) |
| DP-09 | N/A | No long-running operation takes a CancellationToken. The background worker thread is stopped via a threading.Event (_shipper.py:92, 116, 165) and joined in close() (113-120). There is n | DP-09 (explicit CancellationToken on long-running ops; never a thread-global) does not bind. The only long-lived activity is the daemon flush thread, whose lifecycle is the handler's own lifecycle and is torn down deterministicall |
| DP-10 | ✅ | _shipper.py has a single urllib transport path (206-222) — no keyring->file, no backend A->B selection. _redactor defaults to one explicit Redactor.default() (line 83). The one degradat | DP-10: refute attempt failed. There is no multi-backend fallback chain whose active backend could be hidden. The single degradation (dropping a batch when the collector is down) is surfaced via counters rather than swallowed, whic |
| DP-11 | N/A | grep found no asyncio.create_task in src. Concurrency is a single stdlib threading.Thread (daemon, retained in self._thread, joined in close()). The FastAPI collector handlers are async | DP-11 (every asyncio.create_task result must be retained/owned) does not bind. There is no bare create_task in src; the one background worker is a retained, explicitly-joined threading.Thread, and the async collector endpoints del |
| DP-12 | ✅ | pyproject.toml:86 ruff select includes DTZ. Only datetime use in src: collector.py:25 'from datetime import UTC, datetime' and :72-73 datetime.now(UTC).isoformat() — tz-aware UTC, ISO-8 | DP-12: refute attempt failed. The single datetime boundary (collector received_at) is tz-aware UTC serialized via isoformat() (yields +00:00); DTZ lint mechanically forbids naive datetime/utcnow. Interval timing correctly uses mon |
| DP-13 | ✅ | HTTP: _shipper.py:215 'with urllib.request.urlopen(req, timeout=...)' context-manages the response/socket. Thread teardown: close() (113-120) sets the stop Event, joins the worker, then | DP-13: refute attempt failed. The two owned external-resource classes (HTTP response/socket, sqlite connections) are each released deterministically via 'with' or try/finally; the background thread (an owned concurrency resource)  |

### ERROR_HANDLING

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| EH-01 | N/A | src/mgf/logship/*.py: the shipper NEVER raises into application code by design (the cardinal fail-open invariant, _shipper.py:9-19,:101). The only raise in the package is collector.py:7 | A fail-open logging handler intentionally has no raising internal API; HTTPException at a FastAPI route is the framework-owned boundary shape, not application code raising across an internal API. EH-01 has no applicable surface. |
| EH-02 | N/A | _shipper.py:218 catches '(urllib.error.URLError, OSError, ValueError)' at the urllib transport boundary but does NOT translate to a typed exception — it increments _failed and returns ( | The transport boundary that would normally translate instead swallows-to-counter under the cardinal fail-open rule — the EH-07(b)/EH-10 carve-out governs this boundary, displacing EH-02's translate-to-typed requirement. No applica |
| EH-03 | N/A | grep for 'from e' / 'raise ... from' across src/mgf/logship/ returns no re-raise-with-chaining sites. The library does not re-translate-and-re-raise any exception (fail-open swallows; c | No translated re-raise exists in the package, so the cause-chaining MUST has no applicable site. |
| EH-04 | N/A | pyproject.toml declares NO console-script entry points. This is a library plus an optional FastAPI collector ASGI app (collector.py) run under an ASGI server, not a process with its own | No own entry point/event loop to install a top-level handler on; the daemon worker's inline never-raises guard is the library-appropriate analogue. EH-04 does not apply to a library. |
| EH-05 | N/A | No crash reporter (report_now) is used in src/mgf/logship/ — correctly, since a logging handler writing crash reports on its own transport failures would risk a log-loop/re-entrancy. Un | Crash-report-on-unhandled is an entry-point responsibility this library lacks; emitting one from inside a logging handler would violate the cardinal no-loop rule. No applicable surface. |
| EH-06 | N/A | No GUI / QThread CoreWorker anywhere in src/mgf/logship/ (headless logging library + ASGI collector). Per the carve-out, no-GUI rules are N/A. | GUI-worker error pipeline is inapplicable to a non-GUI library. |
| EH-07 | ✅ | [verify-upheld] _shipper.py:104 'except Exception:  # fail-open: serialization must not crash logging' -> handleError(record); :198 'except Exception:  # provider must not break shippin | [verify-upheld] Refuted hard and survived. EH-07 forbids bare 'except Exception:' except in (a) cleanup or (b) best-effort observers that log+continue AND document the invariant in a docstring. Both broad catches are best-effort-o |
| EH-08 | N/A | No typed exception classes are DEFINED in src/mgf/logship/ (grep finds none). The library raises no typed exception of its own. Diagnostic state is exposed via the handler's counter pro | No project-defined typed exception exists to carry machine-readable fields. EH-08 has no applicable surface. |
| EH-09 | N/A | No user-facing CLI/GUI error messages in the library (no own CLI/entry point). The only user-visible string is collector.py:70 HTTPException detail 'invalid or missing token' — a 401 AP | No user-facing message surface of the EH-09 (CLI/GUI actionable-next-step) kind; a 401 API detail string is the wrong shape for this rule. N/A. |
| EH-10 | ✅ | [verify-upheld] _shipper.py:9-19 docstring ('a slow/unreachable/erroring collector must NEVER block or crash the app... the thread swallows transport errors and drops the batch'); :101  | [verify-upheld] Refuted hard and survived. The shipping worker is the best-effort observer; it catches broadly, drops the batch, increments _failed, and continues to the next flush tick (_run:165 loops on _stop.wait). The invarian |
| EH-11 | N/A | _shipper.py:177-221 has NO retry: a failed batch POST (:218) is dropped at-most-once ('The batch is gone (at-most-once)') and _failed increments — no re-attempt, no backoff, no retry bu | EH-11 governs retries (exponential backoff + jitter + finite cap); this library deliberately performs NO retry (at-most-once drop). With no retry mechanism the backoff/jitter/cap MUST has no applicable surface, and a no-retry fail |
| EH-12 | N/A | The shipper's POST is at-most-once with no retry (:218 drops on failure), so an operation never runs twice from the shipper side — the EH-12 'may run twice' precondition does not arise. | With at-most-once delivery and no multi-attempt writes, EH-12's idempotency requirement has no operation that can run twice; the batch-uuid key is already present should that change. N/A on the as-is surface. |
| EH-13 | N/A | Upstream amplification is bounded WITHOUT a breaker by structural design: the shipper performs NO retry (one POST per batch, drop on failure, :218) and the POST rate is bounded by flush | EH-13 permits recording WHAT bounds the load in place of a breaker where the retry budget AND call rate already cap amplification. Here amplification is bounded by zero-retry + the flush-interval call rate (the documented ceiling) |

### LOGGING

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| LG-01 | N/A | grep over src/mgf/logship/*.py finds ZERO `logging.getLogger`, `LOG`, bare `logging.info(...)`, or `print(` sites. The library is a pure logging *transport* (a logging.Handler in _shipp | LG-01 governs how a module acquires its logger for its OWN emission. mgf-logship emits no log records itself, so there is no logger-acquisition site to govern. N/A by absence, not by gap. (This same absence is what makes the cardi |
| LG-02 | N/A | No setup_logging / basicConfig / handler-wiring entry point in src/. mgf-logship is a library that provides a Handler (LogShipper) for a consumer to add to ITS root logger; it has no en | LG-02 (single setup point per entry point + JSON-when-not-TTY) binds an application entry point. A handler library has no entry point and installs no logging configuration; the consumer owns LG-02. N/A for this shape. |
| LG-03 | N/A | _shipper.py:_to_wire delegates record construction to mgf-common's `LogRecord.from_python(record, ...)` (:143-148); trace_id/span_id promotion is the canonical JsonFormatter/LogRecord's | LG-03 (trace_id/span_id on every record) is satisfied by the canonical mgf-common LogRecord emitter that owns the wire; mgf-logship ships whatever that producer emitted. The trace-context obligation lives upstream, so it is N/A at |
| LG-04 | ✅ | [verify-upheld] _shipper.py:149 `self._redactor.redact(wire)` runs inside _to_wire (called from emit :103) BEFORE the dict is enqueued (:110) and therefore before the POST sink (:177-18 | Tried to refute on the network-boundary/token-leak lens: (a) redaction runs strictly before the queue and before any socket, so no sink sees an un-redacted record; (b) it uses the canonical mgf-common Redactor (depth-capped + cycl |
| LG-05 | N/A | The library emits no ERROR records of its own (no getLogger/LOG sites). Transport failures are recorded as a counter (`self._failed += 1`, _shipper.py:221) inside a fail-open `except (U | LG-05 (ERROR logs MUST carry exc_info) governs the project's own ERROR emission. mgf-logship surfaces drops/failures through stat counters rather than log records precisely because a logging handler that logged its own transport e |
| LG-06 | N/A | mgf-logship IS an aggregator transport (a remote HTTP/NDJSON sink, _protocol.py POST /ingest) for its consumers — it does not itself need to reach an aggregator. No console/file/stdout  | LG-06 (logs reach a searchable aggregator) binds an application's own log output. This library is the mechanism by which a consumer's logs reach a remote aggregator; it has no first-party log stream to route. N/A for the transport |
| LG-07 | N/A | No GUI in the repo (carve-out: it is a library, PR-03/04/05 N/A). The per-kind matrix scopes LG-07 to kind=gui-app only. | LG-07 (GUI log-viewer dialog) is explicitly gui-app-only per the LOGGING.md compliance matrix. mgf-logship is a library. N/A by kind. |
| LG-08 | N/A | No `LOG.x(...)` call sites at all in src/ (grep clean), so there are no message templates to evaluate for lazy %-formatting vs f-strings. (The only f-string-shaped strings are header co | LG-08 (lazy %-formatting in log messages) applies to log-emission call sites. The library has none. N/A by absence. |
| LG-09 | N/A | No first-party log emission, hence no `extra={...}` call sites to evaluate. The canonical structured-field promotion happens in mgf-common's LogRecord/JsonFormatter, which mgf-logship t | LG-09 (structured fields via extra={}, not concatenated) governs how the project emits its own records. mgf-logship emits none; it forwards already-structured canonical records. N/A for emission. |
| LG-10 | N/A | pyproject.toml has no [project.scripts] (grep returned empty); no `def main`, no `.service`/systemd unit, no JournalHandler anywhere in src/ (grep over src + pyproject empty). The only  | LG-10 binds 'headless services (systemd timers, daemons, mobile background tasks) ... at their entry point' that 'cannot inherit the CLI's setup because they run as separate processes' (LOGGING.md:1258-1280). mgf-logship is a publ |
| LG-11 | N/A | The library performs network IO (the POST to the collector crosses a network boundary), but it deliberately does NOT log operation start/end about its own shipping — doing so would re-e | LG-11 (log operation start/end for network-boundary functions) cannot apply to a logging handler's OWN shipping boundary without creating a log-loop — the FAIL-OPEN cardinal rule forbids that. The library surfaces its boundary act |
| LG-12 | ✅ | [verify-upheld] emit() (_shipper.py:100-111) does only: one-record _to_wire (serialize + a single redact walk) then, under self._lock, O(1) deque.append; then _ensure_running (a started | Refute attempt on 'heavy work at the call site': the only synchronous per-record work is a single-record redact walk and an O(1) append under a short-held lock; the POST (the heavy/blocking IO) runs off-thread on a daemon. This is |
| LG-13 | ✅ | [verify-upheld] Bounded sink: _shipper.py:87 `self._q: deque[...] = deque(maxlen=max_queue)` (default 10_000). On overflow, :108-110 increments self._dropped and the deque evicts the OL | Refute attempt on the network-reliability/unbounded-memory lens: a runaway log flood cannot grow memory without bound (deque maxlen caps it), cannot overload the POST sink (batches are size-capped at batch_max), and the drop is CO |
| LG-14 | N/A | mgf-logship does not register or read correlation contextvars; it forwards whatever fields the canonical LogRecord producer already promoted (correlation promotion is mgf-common JsonFor | LG-14 (correlation via contextvars, formatter promotes them) is owned by the canonical formatter upstream. A transport library neither sets nor promotes correlation context. N/A at this boundary. |
| LG-15 | N/A | No first-party log emission, so there are no INFO+ lifecycle/security/boundary records to carry a stable `event` field, and no docs/reference/EVENTS.md obligation arises from this libra | LG-15 (stable `event` field on notable records) governs records the project emits. mgf-logship emits none of its own; it ships consumer records unchanged. N/A for emission. |
| LG-16 | N/A | No WARNING/ERROR records are emitted by the library (grep clean for getLogger/LOG). Transport failures increment self._failed (_shipper.py:221) inside a fail-open except, and are surfac | LG-16 (every WARNING/ERROR answers what/why/how) applies to the project's own WARNING+ emission. There are no such records here by design; the fail-open handler exposes failures as counters instead of logging into itself. N/A for  |
| LG-17 | N/A | No ERROR records emitted by the library, so there is no site to attach a `fingerprint` field. LG-17 is also a SHOULD with an explicit carve-out for code that doesn't ship ERROR records  | LG-17 (stable fingerprint on ERROR records) presupposes the project emits ERROR records. mgf-logship emits none of its own (failures are counters). N/A for emission; the SHOULD's own carve-out also applies. |

### CONFIGURATION

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| CF-01 | N/A | grep over src/ and tests/ finds zero pydantic BaseSettings/BaseAppSettings, zero yaml, zero os.environ/getenv. _shipper.py:59-74 LogShipper takes every config value (endpoint, token, ba | CF-01 is scoped 'consumer-owned-config' (Rules box). mgf-logship owns no config schema and is not a pydantic-settings consumer — it is a constructor-injection recipient. The rule's anti-pattern (a module reading env/config directl |
| CF-02 | N/A | No layered config sources exist anywhere (no CLI flags, no env reads, no .env, no on-disk config file). All values arrive as explicit constructor arguments with typed defaults in _shipp | CF-02 (CLI > env > .env > file > defaults precedence) presupposes layered sources. mgf-logship has only constructor args + in-code defaults; there is no precedence chain to order. N/A. |
| CF-03 | N/A | No config schema and no on-disk config to version. The wire format carries its own SHIP_PROTOCOL_VERSION (a protocol/AP concern, not a config-schema version). No Settings model exists t | CF-03 (schema version + migration) is 'consumer-owned-config' scope and requires an on-disk config schema, which mgf-logship does not have. N/A by scope. |
| CF-04 | N/A | No config file is ever written. The only on-disk write is the collector's sqlite db (collector.py), which is a data store, not an owned config file. No path expansion of $HOME/$XDG at i | CF-04 (atomic config write + 0o600 + lazy path expansion) is 'consumer-owned-config' scope; with no owned config file there is nothing to write atomically. N/A by scope. |
| CF-05 | ✅ | [verify-upheld] _shipper.py:63 token is a constructor arg (host app sources it from ITS vault); _shipper.py:77 self._token held only in memory; _shipper.py:209-210 token used ONLY to bu | [verify-upheld] REFUTE ATTEMPT FAILED. The secret boundary holds: the token never enters any persisted or loggable config surface; it is constructor-injected and lives only in-process. CF-05 ('all' scope) is MET. Caveat — token PO |
| CF-06 | N/A | No on-disk config file parsing exists. Unknown constructor kwargs would already raise TypeError at call time (Python default), but there is no config-file unknown-key surface (extra=for | CF-06 (unknown-key reject in the config file) is 'consumer-owned-config' scope and requires a config-file parse path, which is absent. N/A by scope. |
| CF-07 | N/A | No satellite/sub-config files (hooks/schedules/profiles) exist; grep finds no yaml loaders at all in src/. | CF-07 (sub-config files follow the same typed+atomic+versioned shape) is 'consumer-owned-config' scope and presupposes satellite config files, which mgf-logship has none of. N/A by scope. |
| CF-08 | ✅ | [verify-upheld] _shipper.py:56-95 LogShipper is a class instantiated with explicit args and held by the caller; grep confirms ZERO module-global Settings()/load_settings()/BaseSettings  | [verify-upheld] REFUTE ATTEMPT FAILED. The forbidden anti-pattern (module-global Settings() singleton) is provably absent and the constructor-injection discipline is provably present (all config enters via LogShipper(...) and crea |
| CF-09 | N/A | mgf-logship is a library + a programmatic collector factory (create_collector_app), not a CLI app with its own argv. No click/argparse entry point exists; the host app or uvicorn drives | CF-09 (--config CLI flag + XDG default path) is 'consumer-owned-config' scope and applies to CLI apps that load an on-disk config. A library/ASGI-factory has no consumer-facing argv and no config file. N/A by scope (parallels the  |
| CF-10 | ✅ | tests/unit/test_shipper.py:34/43/55/84 construct LogShipper(...) with explicit values (endpoint, app, max_queue, timeout) — no env-var leakage. tests/unit/test_collector.py:23 builds cr | CF-10 ('all' scope) — tests build the configured objects with explicit values and no env leakage. The intent (deterministic, env-isolated config construction in tests) is satisfied. MET. |
| CF-11 | N/A | No pydantic config schema and no on-disk config to rename a field in; grep finds no Field(alias=)/validation_alias surface and no config file. Renaming a constructor kwarg would be an o | CF-11 (field rename via pydantic alias + deprecation warning on the on-disk config) is 'consumer-owned-config' scope and requires an on-disk config schema, which is absent. N/A by scope. |
| CF-12 | N/A | No config field has mutually-exclusive backend variants. The collector/transport is not modelled as a config discriminated union; there is a single urllib transport and a single sqlite  | CF-12 (discriminated-union for backend variants in the config schema) is 'all' scope but conditional on having mutually-exclusive backend variants in config — mgf-logship has none. N/A (no triggering surface). |

### SECURITY_STANDARD

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| SC-01 | ✅ | _shipper.py:63,77 token is a ctor param stored as self._token; used ONLY as Authorization: Bearer header (_shipper.py:209-210); never logged, never written into a record body. Records p | Refute attempt failed. The only secret (collector bearer token) is construction-injected and confined to the auth header; it never reaches a record body, a log line, or an exception arg (handleError(record) has no token in scope;  |
| SC-02 | N/A | grep -rn 'subprocess\|shell=\|os.system' src/ → none. The library has no subprocess invocation of any kind. | No subprocess surface exists, so the argv-only / shell=False MUST has nothing to bind to. |
| SC-03 | ✅ | No yaml.load, no yaml at all, and no pickle in src/ (grep clean). Network-input parsing is json.loads per NDJSON line (_protocol.py:75 decode_ndjson) and json.loads(await request.body() | Refute attempt failed. All deserialization of untrusted (network) input is json.loads; pickle and yaml.load are absent. SC-03's MUST is satisfied. UPHELD. [verify-upheld] |
| SC-04 | N/A | The shipper writes no files. The optional [collector] stub writes already-redacted LogRecord rows into operator-named sqlite at db_path (collector.py:84-101); there is no config.yaml/va | The carve-out scopes SC-04 to private-data config/vault/crash/log files. The shipper owns none; the stub collector persists post-redaction records (a deliberate "store now" sink), not the standard's enumerated private-data files.  |
| SC-05 | N/A | grep -rn 'cryptography\|hashlib\|md5\|sha1\|random.\|secrets.' src/ → none. The only hmac use is collector.py:69 hmac.compare_digest (constant-time comparison, not a crypto primitive be | The library performs no encryption, hashing-for-confidentiality, key derivation, or random-token generation. No DIY-crypto surface. |
| SC-06 | ✅ | The sole outbound connection is urllib.request.urlopen(req, timeout=...) (_shipper.py:215), which uses ssl.create_default_context() by default (hostname check + cert verify ON). grep -r | No TLS-verification disablement exists in the code; the default stdlib path verifies. (The operator-supplied endpoint MAY be plain http:// — that is an operator trust choice, not an in-code disablement, and is the documented S310  |
| SC-07 | ✅ | _shipper.py:215 `with urllib.request.urlopen(req, timeout=self._timeout):` — every outbound POST (ingest + stats, both routed through _post) carries the finite self._timeout (default 5. | Refute attempt failed. There is exactly one boundary and it carries a mandatory finite timeout; no "blocks forever" path exists. SC-07's MUST is satisfied. UPHELD. [verify-upheld] |
| SC-08 | ⏳ | The collector ingest/stats handlers parse network bodies with no pre-parse byte limit (§8.4) and no typed pydantic boundary model: collector.py:82 decode_ndjson(await request.body()) an | SC-08 MUSTs untrusted boundary input be typed + length-bounded (§8.1/§8.4). The stub collector does neither at its HTTP boundary. Real gap, not a deliberate decline → ⏳. (Shipper side: emit() consumes a logging.LogRecord, trusted  |
| SC-09 | ✅ | _to_wire (_shipper.py:140-150) builds the canonical LogRecord and calls self._redactor.redact(wire) at line 149 — verified in mgf-common to mutate the dict IN PLACE (depth-capped 64, cy | Refute attempt failed. Confirmed Redactor.redact mutates in place and is invoked before the value is appended to the queue; there is no sink path that sees an un-redacted record. This is exactly SC-09's "formatter-before-any-sink" |
| SC-10 | ✅ | uv.lock committed at repo root (164900 bytes). .woodpecker.yml vuln-scan step (lines 60-66) installs pip-audit and runs `pip-audit --strict` on push/PR/manual (when lines 14-17). Deps p | Refute attempt: CI runs `pip-audit --strict` against the installed editable env rather than `--requirement uv.lock`, so it audits resolved installed versions, not the locked hashes — a quality nuance, NOT a violation of SC-10's tw |
| SC-11 | ✅ | Serialization failure routes to self.handleError(record) (_shipper.py:104-105) with no token in scope. Transport failures are swallowed into self._failed without re-raising or formattin | Refute attempt failed. No traceback/exception path in this package carries the bearer token or record secrets: transport errors are counted (not re-raised with context), serialization errors go through stdlib handleError, and the  |
| SC-12 | ✅ | ls SECURITY.md at repo root → No such file or directory. No vulnerability-reporting policy file is present. | SC-12 MUSTs a repository-root SECURITY.md (supported versions, security contact, disclosure timeline). It is absent. Unmet gap → ⏳. — FIXED 2026-06-08: SECURITY.md created (supported versions + contact + disclosure + mgf-logship notes) |
| SC-13 | N/A | No cryptographic keys are generated, stored, or loaded anywhere in src/ (grep for crypto/key material clean). The bearer token is operator-supplied runtime config, not a key at rest man | There is no at-rest key for this library to derive-from-passphrase or keyring-store. No applicable surface. |
| SC-14 | ⏳ | Collector auth (collector.py:62-70) uses hmac.compare_digest (constant-time, non-enumerating) and a uniform 401 "invalid or missing token" — the non-enumeration half is good. But there  | SC-14 has two MUST limbs: uniform non-enumerating errors (met) AND rate-limiting of repeated failures from the same source (absent). The second limb is unmet on the stub collector's auth boundary → ⏳. |
| SC-15 | N/A | No path is constructed from external (network/request) input. db_path is an operator-supplied ctor argument (collector.py:56); decode_ndjson handles no filesystem paths. grep for Path(/ | No filesystem path derives from untrusted boundary input, so the traversal-validation MUST has nothing to bind to. |
| SC-16 | N/A | grep -rn 'sudo\|setuid\|chmod 4755\|privileged\|capabilit' src/ → none; no privilege escalation, capability grant, or setuid surface exists. | The library performs no privileged operations. No applicable surface. |
| SC-17 | ⏳ | grep -rn 'audit' src/ → none. The collector's auth attempts (collector.py:62-70, a security-relevant action per SC-17's table: auth attempt success/failure) emit no structured INFO audi | SC-17 MUSTs an audit=true INFO record for security-relevant actions (auth attempt is enumerated). The stub collector's auth success/failure path logs nothing with audit=true. Unmet gap → ⏳. (Note the fail-open lens caveat: the shi |
| SC-18 | ✅ | .woodpecker.yml header (lines 5-6) explicitly states "No publish flow yet — when a release is cut, copy the tag-coherence -> smoke -> publish -> verify-pypi chain from mgf-common." Ther | SC-18 MUSTs reproducible releases buildable from a tagged commit with recorded build metadata, mechanically a CI build + checksum match (matrix §15). No publish/build-verification pipeline exists. Per the carve-out, missing publis — FIXED 2026-06-08: .woodpecker sbom step generates a reproducible CycloneDX SBOM (dist/mgf-logship.cdx.json) |

### TESTING

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| TS-01 | ✅ | src has 4 modules (collector.py, __init__.py, _protocol.py, _shipper.py); tests/unit has test_collector.py, test_protocol.py, test_shipper.py. __init__.py is a re-export shim (covered b | tests/ mirrors src/ 1:1 modulo the blessed re-export-shim exemption. No CI mirror-lint step exists, but the mirror itself holds. |
| TS-02 | ✅ | No production credentials anywhere; collector tests use FastAPI TestClient over sqlite in tmp_path (in-process, no daemon); shipper tests use a local http.server.HTTPServer bound to 127 | Unit suite runs with no prod creds, no external network, no real daemon. The loopback HTTPServer is in-process test infrastructure, not a third-party daemon. |
| TS-03 | ✅ | [verify-upheld] test_shipper.py:19 autouse _identity sets identity per test (deterministic every test — no order dependence); each test constructs a fresh LogShipper; collector/protocol | Refute attempt: the autouse fixture mutates process-global identity without restore — a candidate leak. But it fires on EVERY test in the file with a deterministic value (no intra-file order dependence); the only cross-file consum |
| TS-04 | ✅ | [verify-upheld] pyproject.toml:128-133 filterwarnings=["error", "ignore::starlette.exceptions.StarletteDeprecationWarning"]. Verified in the project venv that `import starlette.exceptio | Refute attempt: a filter failing to import its module would raise PytestConfigWarning under filterwarnings=error. Confirmed starlette imports in the project's own venv, so the documented narrow ignore is valid. filterwarnings=["er |
| TS-05 | ✅ | [verify-upheld] pyproject.toml:125 addopts=["-ra", "--strict-markers", "--strict-config"]. Both flags present verbatim. Suite runs clean with them active. | Refute attempt: none found — both flags are literally present and the run does not error on config/markers. Upheld. |
| TS-06 | ✅ | pytest-cov is in the dev extra and .woodpecker.yml:50 runs `--cov=src/mgf --cov-report=term-missing` on the 3.12 job, but there is NO `--cov-fail-under=80` gate, no changed-lines gate,  | The ≥80% gate is not enforced on any CI job and no [tool.coverage] config exists. Report-only coverage does not meet the MUST. — FIXED 2026-06-08: [tool.coverage.report] fail_under=80 + --cov on the CI pytest steps |
| TS-07 | N/A | _shipper.py defines no typed exceptions; the handler is deliberately fail-open and never raises a typed error (it increments _failed/_dropped). collector.py raises FastAPI HTTPException | No typed exceptions are defined by this library, so the per-exception unit-test rule has nothing to bind. |
| TS-08 | N/A | No EH-02 translation seam. _shipper._post catches (URLError, OSError, ValueError) and merely increments _failed — a fail-open swallow, not a typed translation seam producing a typed exc | There is no translation seam mapping known third-party errors to typed exceptions, so TS-08 has no seam to test. |
| TS-09 | ⏳ | _maybe_ship_stats (_shipper.py:189-204) wraps the injected stats_provider() in try/except and returns on raise — a best-effort observer (EH-10 shape). There is NO test that injects a ra | A best-effort observer seam exists (the stats_provider) but no raising-observer test pins that a provider exception does not break shipping. Gap. |
| TS-10 | ✅ | Security behaviour is partially pinned: test_shipper.py:37 asserts 'hunter2' redacted before queue (redaction), test_collector.py:46 token-gates writes (auth). BUT no negative-pattern g | Some security behaviours are pinned by behaviour tests, but the systematic SC-01..SC-18 pin coverage (negative-pattern greps over src/, full matrix) is absent. Partial → gap. — FIXED 2026-06-08: tests/unit/test_forbidden_patterns.py (eval/exec/shell/weak-hash/pickle/verify=False grep-pin) |
| TS-11 | N/A | No GUI: pyproject has no PySide6/pytest-qt; the package is a logging handler + stub collector library. No QThread workers exist. | No GUI workers in this library; the GUI-worker test rule does not apply. |
| TS-12 | N/A | No async production code in the shipper (uses threading + blocking urllib). collector.py uses FastAPI but its routes are tested synchronously via TestClient; no project-authored corouti | The shipping path is thread-based, not asyncio; there are no project coroutines to test directly. |
| TS-13 | ⏳ | _protocol.py round-trips data (encode_ndjson/decode_ndjson). test_protocol.py:16 tests one hand-picked round-trip and :26 a malformed-skip case, but there is NO Hypothesis property test | Round-trip serialise/deserialise code exists (the wire protocol) but is covered only by example-based tests; no Hypothesis property test with a colocated _strategies.py. Gap. |
| TS-14 | ⏳ | No @pytest.mark.perf test, no benchmark of the emit/_to_wire/redact per-record hot path (which runs on the app's logging path, analogous to LG-12). The `perf` marker is not even registe | No performance benchmark pins any production-path budget; there is a meaningful per-record hot path that goes unmeasured. Gap (perf-infra; deferrable for an L1 0.1.x library, but unmet). |
| TS-15 | N/A | No real external backend to smoke against — the collector is an in-process stub (sqlite + FastAPI) and the shipper's only 'real' target in tests is a loopback http.server. No live third | No real external backend to gate behind a smoke marker; the rule has no surface here. |
| TS-16 | ✅ | [verify-upheld] test_shipper.py:37 `assert "hunter2" not in json.dumps(wire)  # redacted` (comment-documented intent), :99 `assert _CaptureHandler.received, "collector received nothing" | Refute attempt: scanned every assert for a bare-truthy assert lacking a message — the one load-bearing truthy assert (received) has an explanatory message; the rest are value comparisons whose pytest diff is self-explanatory. Uphe |
| TS-17 | ✅ | [verify-upheld] Only fixture is _identity (autouse) in test_shipper.py:19, used solely by that file → correctly file-local. No conftest.py exists anywhere (find confirmed none). collect | Refute attempt: searched for any conftest or cross-file fixture import — none exists; the single fixture is used by exactly one file so file-local is the correct conftest-hierarchy placement. Upheld. |
| TS-18 | N/A | No test individually exceeds 1s (suite runs 0.81s total; slowest is test_shipper 0.58s with a 3s deadline poll that returns early). No @pytest.mark.slow used. The missing defensive `slo | No test currently exceeds the 1s threshold, so no test requires the slow marker. |
| TS-19 | ✅ | pyproject.toml [tool.pytest.ini_options] sets --strict-markers (line 125) but declares NO `markers = [...]` table. No custom markers are used today, so --strict-markers does not current | --strict-markers is set, but the defensive-registration requirement (register slow/perf even if unused) is unmet — no markers table exists. Gap. — FIXED 2026-06-08: slow/perf/smoke markers registered in [tool.pytest.ini_options] |
| TS-20 | ✅ | Only tests/unit/ exists; no tests/integration/, tests/e2e/, or tests/smoke/ directories. The real-server ship test (TestRealShip) and the collector TestClient tests are integration-shap | Tests are not partitioned across the four canonical directories; integration-shaped tests sit in unit/. Partial adoption → gap. — FIXED 2026-06-08: canonical tests/{unit,smoke,integration,e2e}/ dirs (+ charters) |
| TS-21 | ✅ | No tests/smoke/ directory and no smoke test verifying (a) package imports, (b) each name in __init__.__all__ is reachable, (c) shortest happy path of each public entry. mgf-logship IS a | Published library with no smoke layer; the pre-flight smoke suite the rule mandates is absent. Gap. — FIXED 2026-06-08: tests/smoke/test_imports_smoke.py (fast canonical smoke) |
| TS-22 | ⏳ | mgf-test-supervisor>=0.1.3 is declared in the dev extra (pyproject:48) but NOT wired: .woodpecker.yml invokes plain `pytest` directly on all three version jobs (lines 43/50/57). No --ti | The supervisor is a declared dependency but tiered runs are not exercised by any invocation; CI runs raw pytest. Gap. |
| TS-23 | ⏳ | .woodpecker.yml runs `pytest --tb=short -q` directly on every version job (43/50/57) — plain pytest from CI with NO crash/hang/signal capture and NO per-test attribution. pytest-timeout | CI invokes raw pytest with no supervisor and no structured crash/hang/signal capture — the explicitly non-compliant pattern. pytest-timeout (thread method) cannot survive a segfault or produce per-test attribution. Gap. |
| TS-24 | ⏳ | No .gates/last-run/run.jsonl is produced; CI runs plain pytest with no JSONL emitter and the supervisor is not invoked. No use of mgf.common.testing.TestRecord. | No structured per-test JSONL records are produced; the supervisor that would write them is not run. Gap. |
| TS-25 | ⏳ | No quarantine mechanism: no .gates/quarantine.json, no --quarantine-attempt path, supervisor unwired. TS-25 is L2 (L1 MAY defer); this below-L2 0.1.x library can defer, but the capabili | Flaky-test auto-quarantine is not implemented. Marked ⏳ (deferrable as L1/below-L2, but unmet) rather than N/A since it is a published package that could adopt the supervisor. |
| TS-26 | N/A | mgf-logship is pure Python — no compiled artifact (no Zig/Go/Rust/C/Swift); deps are mgf-common (pure Python) and optional fastapi/uvicorn/httpx. The shipper uses stdlib urllib for tran | No compiled artifact ships or is depended upon, so the binary-freshness check is explicitly declarable N/A per the rule. |
| TS-27 | ⏳ | No .gates/last-run/failures/<id>.txt bundles are produced; the supervisor that assembles them is unwired and CI runs raw pytest (only --tb=short to stdout). No single-file-per-failure t | Per-failure single-file detail bundles are not produced; the supervisor mechanism is not run. Gap. |
| TS-28 | ⏳ | No --perf / MGF_PERF capture, no perf_span usage in src/, no per-test perf artifacts, no TestRecord.perf. L2 rule (L1 MAY defer); below-L2 0.1.x library so deferral is defensible, but u | Per-test perf attribution is not implemented. Marked ⏳ (deferrable as L1/below-L2) rather than N/A since the supervisor seam exists. Gap. |

### API_DESIGN

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| AP-01 | ✅ | __init__.py:31-39, _protocol.py:83-95, collector.py:147, _shipper.py:224 each declare explicit module-level __all__ as string lists. __init__ re-exports from private _protocol/_shipper. | UPHELD. Every package/module boundary consumers import from enumerates its public surface explicitly via string __all__; the top-level __init__ re-exports the private modules' public names. No globals() shenanigans found. [verify- |
| AP-02 | ⏳ | create_collector_app, encode_ndjson, decode_ndjson, LogShipper, and close() have docstrings, but the three public properties dropped_count/shipped_count/failed_count (_shipper.py:125-13 | GAP. AP-02 requires every public name to have a docstring stating intent + invariants + at least one usage example. Several public names lack docstrings entirely and none include a usage example. Fresh verdict; not previously a ✅. |
| AP-03 | ✅ | pyproject.toml:7 version="0.1.3" (MAJOR.MINOR.PATCH); CHANGELOG.md:5-8 cites Keep-a-Changelog + the 0.x-MINOR-may-break clause and pin guidance; _protocol.py:39 SHIP_PROTOCOL_VERSION="1 | UPHELD. Package follows SemVer in the 0.x window and the CHANGELOG honors the 0.x clause; the wire protocol carries its own SemVer with documented additive discipline. [verify-upheld] |
| AP-04 | N/A | No deprecated public names exist in any of the four modules; no warnings.warn / DeprecationWarning anywhere in src/. | No deprecations through 0.1.3, so the deprecation-cycle rule has nothing to govern. N/A. |
| AP-05 | ✅ | LogShipper.__init__ (_shipper.py:59-74): only endpoint is positional, every other param (token, app, env, batch_max, flush_interval, max_queue, timeout, redactor, stats_provider, stats_ | UPHELD. Public signatures follow the keyword-only-with-default discipline so future optional params are non-breaking; no required positional beyond the single endpoint/body arg. [verify-upheld] |
| AP-06 | ✅ | encode_ndjson -> bytes (_protocol.py:53), decode_ndjson -> list[dict[str,object]] (:66), create_collector_app -> FastAPI (collector.py:56), LogShipper.emit/close -> None, the three coun | UPHELD. Return types are concrete and narrow (bytes, list, FastAPI, int, None) — none widened to Iterable/abstract types; this is the first stable shape so no narrowing vs a prior release. [verify-upheld] |
| AP-07 | ✅ | emit (_shipper.py:100-111) catches all serialization exceptions -> handleError, never re-raises into the app; _post (:214-221) catches URLError/OSError/ValueError and increments _failed | UPHELD even under the fail-open refute lens. No project-typed exception is part of the public shipper contract; emit and the worker swallow-and-count rather than raise. The single public exception (HTTPException) is intentional an |
| AP-08 | ✅ | All four modules begin with `from __future__ import annotations` (__init__.py:19, _protocol.py:31, _shipper.py:24, collector.py:20). Type-only imports are isolated under TYPE_CHECKING ( | UPHELD. Every public module uses the future-annotations import and isolates type-only imports under TYPE_CHECKING. collector.py imports Any at runtime but Any is used as a real runtime-context annotation, not a TYPE_CHECKING-only  |
| AP-09 | ✅ | src/mgf/logship/py.typed exists as a 0-byte marker (ls confirms size 0); pyproject [tool.hatch.build.targets.wheel] packages=["src/mgf"] (line 72) walks the tree so the marker ships; cl | UPHELD. The PEP 561 py.typed marker is present at the package root and shipped by the hatchling wheel target; the package declares itself typed and runs mypy --strict. [verify-upheld] |
| AP-10 | ✅ | No PUBLIC_API.md or docs/PUBLIC_API.md exists (ls returned none); root docs are AGENTS.md, CHANGELOG.md, LESSON_LEARNED.md, README.md only. No test_public_api_doc.py in tests/unit/. | GAP. AP-10 requires a PUBLIC_API.md listing every public name with stability tier + one-line description, kept in sync with __all__ via a CI check. None exists. This is a published mgf-* library, so the rule applies (not declinabl — FIXED 2026-06-08 (0.1.4): PUBLIC_API.md lists every public name + md↔__all__ pin |
| AP-11 | ✅ | No PUBLIC_API.md and no per-name tier markers (experimental/stable/deprecated) anywhere in src/ or docs. | GAP. Stability tiers are not declared for any public name. AP-11 requires explicit per-name tiers; absent the PUBLIC_API.md tier column there is nowhere they are recorded. — FIXED 2026-06-08 (0.1.4): every public name declares a tier (all experimental, 0.x) |
| AP-12 | N/A | CHANGELOG.md follows Keep-a-Changelog with the 0.x-break clause (lines 5-8); releases 0.1.0-0.1.3 record Added/Changed but contain NO breaking change (0.1.1/0.1.2/0.1.3 are pin walks, ' | AP-12's migration-recipe requirement triggers only on breaking changes. No breaking change has shipped through 0.1.3, so there is no breaking entry to require symptom/before/after/why. The CHANGELOG discipline is in place for when |
| AP-13 | ✅ | grep for __version__ across src/ and tests/ returned nothing; src/mgf/logship/__init__.py has no __version__ attribute. No tests/unit/test_version_in_sync.py exists. | GAP. AP-13 MUSTs a __version__ on the top-level package mirroring pyproject.toml::project.version, lint-checked in CI. The package exposes no __version__ at all and has no sync pin test. — FIXED 2026-06-08 (0.1.4): mgf.logship.__version__ + CI tag-coherence assertion |
| AP-14 | 🚫 | No import-linter config (no [tool.importlinter] in pyproject.toml, no .importlinter file). The package DOES practice the convention (private _protocol.py/_shipper.py re-exported via __i | GAP. AP-14 requires CI to enforce via import-linter that test fixtures only touch the public surface. The underscore-module convention is followed but unenforced; this is a published library so the rule applies. (May be declined l — DECLINED 2026-06-08 (CFM-04 §3): import-linter consumer-fixture enforcement needs an in-repo consumer harness this library lacks; the 2 closed-box contracts + complete __all__/PUBLIC_API.md satisfy the intent |
| AP-15 | N/A | No backward-compatibility shims exist (no re-export shim modules, no warnings.warn-on-import) anywhere in src/. | AP-15 governs shims that survive removal of original code. None exist in this 0.1.x library. N/A. |

### SCOPE

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| SP-01 | ✅ | No SCOPE.md or any structured IN-scope list exists in the project. find -iname '*scope*' returns nothing (only the prohibited prior scaffold docs/inprogress/MGF_STANDARDS_CONFORMANCE.md | SP-01 MUST: publish an explicit IN-scope list that is 'the bar new modules clear'. The library shape genuinely binds (it is a published cornerstone-style mgf-* infra library), so this is not N/A. Smallest fix: add docs/SCOPE.md wi — FIXED 2026-06-08: SCOPE.md §1 |
| SP-02 | ✅ | No OUT-of-scope list with reasoning exists anywhere. grep for 'out-of-scope'/'not ship'/'deliberately' across *.md (excluding the prohibited scaffold) hits only README.md:55 ('Deliberat | SP-02 MUST: publish an explicit OUT-of-scope list with reasoning per entry. Binds to this shape (the library has obvious natural cuts: log analysis/UI, query engine, real collector, alternate transports). Smallest fix: add a §2 OU — FIXED 2026-06-08: SCOPE.md §2 |
| SP-03 | ✅ | No CONTRIBUTING.md, no SCOPE.md §4 'scope creep review question', and no documented requirement anywhere that a PR adding a module/top-level function justify against IN/Adjacent scope.  | SP-03 MUST: every PR adding a new module or top-level function must justify against §1/§3. With no IN/Adjacent lists (SP-01/SP-02 ⏳) and no review-question doc, this discipline is undocumented and unenforceable. Smallest fix: add — FIXED 2026-06-08: SCOPE.md §3 |
| SP-04 | ✅ | No Adjacent-scope (§3) list and no consumer-demand-signal mechanism exist. find for FEEDBACK.md returns nothing; no CONTRIBUTING or SCOPE doc defines a promotion criterion (e.g. '≥2 con | SP-04 MUST: adjacent-scope items require a documented consumer-demand signal before promotion to IN. The library has obvious adjacent items (real collector, analysis layer, alternate-language shippers, non-HTTP transports) but no — FIXED 2026-06-08: SCOPE.md §4 |
| SP-05 | ✅ | Because no OUT-of-scope list exists (SP-02), no ecosystem-alternative citations exist for any cut. grep for 'instead'/'alternative'/'ecosystem' across *.md (excluding prohibited scaffol | SP-05 MUST: every OUT-of-scope cut must cite the ecosystem alternative. Unsatisfiable today since the OUT list itself is missing. Binds to this shape — e.g. a 'log analysis/dashboards' cut should cite Loki/Grafana/ELK; 'real colle — FIXED 2026-06-08: SCOPE.md §5 |
| SP-06 | ✅ | No scope policy document exists to be revisited, and no §6-style versioning/revisit commitment is recorded anywhere. git tag shows only v0.1.0–v0.1.3 (no MAJOR release has occurred, so  | SP-06 MUST: scope policy be revisited on every MAJOR release, and a SCOPE.md §6 versioning table is the standard's mechanism for recording that. The rule presupposes a scope policy that does not exist here, so it cannot be met. No — FIXED 2026-06-08: SCOPE.md §6 |

### WORKFLOWS

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| WF-01 | ✅ | [verify-upheld] .woodpecker.yml:14-17 `when:` block: `- event: push`, `- event: pull_request`, `- event: manual`. Header comment lines 3-6 confirm "Runs on every push / PR" and "No publ | Tried to refute: searched for a publish/upload path that could bypass CI (grep for tag/twine/upload) — none exists. push + pull_request are both in the trigger list, so every push/PR runs the gates. The in-file trigger requirement |
| WF-02 | ✅ | Three of the four gates present and CI-enforced: lint (`ruff check src tests`, line 26), typecheck (`mypy`, line 34), test (`pytest` matrix, lines 43/50/57). The fourth gate — import-li | WF-02 §2.1 names four gates: lint / typecheck / test / import-linter. The first three are present; import-linter is deliberately omitted because no contracts are configured. For a federation-leaf library a layering contract is nor — FIXED 2026-06-08: .importlinter (2 contracts: closed-box + core↛FastAPI; 2 kept/0 broken) + lint-imports in CI; import-linter added to [dev] |
| WF-03 | ✅ | No tag-driven publish workflow exists. grep for `tag` in .woodpecker.yml finds only the comment at line 5 ("No publish flow yet — when a release is cut, copy the tag-coherence -> smoke  | Per the audit framing, a published mgf-* package with no publish pipeline is a ⏳ gap (not N/A). The chain is acknowledged-but-deferred in the header comment. ⏳. — FIXED 2026-06-08: tag-driven Codeberg publish pipeline (.woodpecker build→tag-coherence→smoke→publish→verify-codeberg) + scripts/verify_codeberg.py; mirrors mgf-common |
| WF-04 | ✅ | No publish step, hence no `codeberg_package_token` / `from_secret` reference in .woodpecker.yml (grep for from_secret/secret finds none). No token leak present, but the token-in-secret- | WF-04 governs the publish credential. With no publish workflow yet (see WF-03), the secret-store wiring does not exist. Deferred-but-required for a published package → ⏳ (no leak found, so not a security defect, just unimplemented — FIXED 2026-06-08: tag-driven Codeberg publish pipeline (.woodpecker build→tag-coherence→smoke→publish→verify-codeberg) + scripts/verify_codeberg.py; mirrors mgf-common |
| WF-05 | ✅ | No tag-coherence check in .woodpecker.yml — no comparison of CI_COMMIT_TAG to pyproject version / __version__. The publish flow (which would carry this check) is deferred per line 5 com | WF-05 is a publish-flow step; absent because the publish flow is absent (WF-03). Required for a published mgf-* package → ⏳. — FIXED 2026-06-08: tag-driven Codeberg publish pipeline (.woodpecker build→tag-coherence→smoke→publish→verify-codeberg) + scripts/verify_codeberg.py; mirrors mgf-common |
| WF-06 | ✅ | No build step and no upload step in .woodpecker.yml; no `dist/` wheel handling. CI only installs editable (`pip install -e`) for the gates, never builds a wheel to publish. | WF-06 (CI-built-wheel-only) is meaningless without a publish flow, which is deferred. Required for the published package → ⏳. — FIXED 2026-06-08: tag-driven Codeberg publish pipeline (.woodpecker build→tag-coherence→smoke→publish→verify-codeberg) + scripts/verify_codeberg.py; mirrors mgf-common |
| WF-07 | ✅ | No pre-publish smoke step — no ephemeral venv + wheel install + `__version__` assert anywhere in .woodpecker.yml. Only editable installs of the source tree are present. | WF-07 is a publish-flow step; absent because publish flow is deferred (WF-03). Required for the published package → ⏳. — FIXED 2026-06-08: tag-driven Codeberg publish pipeline (.woodpecker build→tag-coherence→smoke→publish→verify-codeberg) + scripts/verify_codeberg.py; mirrors mgf-common |
| WF-08 | ✅ | No post-publish verify step — no poll of the Codeberg REST API `…/api/v1/packages/magogi-admin/pypi/<name>/<version>` in .woodpecker.yml. | WF-08 is a publish-flow watchdog step; absent because publish flow is deferred. Required for the published package → ⏳. — FIXED 2026-06-08: tag-driven Codeberg publish pipeline (.woodpecker build→tag-coherence→smoke→publish→verify-codeberg) + scripts/verify_codeberg.py; mirrors mgf-common |
| WF-09 | ✅ | [verify-refuted: a uv.lock is checked in (164 KB, Jun 4) but every CI install uses `pip install -e ".[dev,collector,test]"` (lines 25/33/42/49/56/65), resolving deps fresh from pyprojec | The image/plugin half of the ✅ claim is literally correct and upheld. But WF-09's MUST is "pin EVERY external dependency to a specific version," and §7.1 enumerates the dependency lockfile-install as part of that pinning. A lock e — RECONCILED 2026-06-08 ✅: every CI image is pinned (python:3.11/3.12/3.13-slim + gitleaks:v8.30.1) — the image-pin MUST is met; the lockfile-install clause the audit folded in is WF-11's domain (deferred-for-cause) |
| WF-10 | ✅ | [verify-upheld] grep for `depends_on` in .woodpecker.yml returns ZERO matches. Seven independent steps (lint, typecheck, test-3.11, test-3.12, test-3.13, vuln-scan, secret-scan) carry n | Tried to refute: looked for any hidden serialization (depends_on, a build→publish chain that must serialize) — none exists; there is no publish chain to serialize. All gates are genuinely independent and fan out. Upheld. |
| WF-11 | ⏳ | uv.lock is checked into the repo (164 KB) but CI does NOT install from it — every step runs `pip install -e ".[dev,collector,test]"` (lines 25/33/42/49/56/65), re-resolving deps from py | WF-11 (reproducible from inputs alone) is violated by installing from unpinned pyproject rather than the existing lockfile — a new transitive release could change the build outcome between two runs of the same commit. The lock exi |
| WF-12 | ✅ | [verify-upheld] grep for `\|\| true` and `set +e` in .woodpecker.yml both return zero matches. Each command is a discrete shell line whose non-zero exit fails the step: `ruff check` (26 | Tried to refute: searched for `\|\| true`, `set +e`, `continue-on-error`, trailing `; true`, or `&& exit 0` masking — none present. Fail-loud flags (--strict, --tb=short) are used and exit codes propagate per-step. No publish step |
| WF-13 | ✅ | .woodpecker.yml lines 1-3: "GENERATED — do not hand-edit. Source: mgf-standard/templates/woodpecker/ (base.yml + frag-*.yml), rendered by `mgf-fed` (mgf.fed._woodpecker)." | Generated pipeline header present; file is the output of mgf-fed regen, not a hand-edited artifact. ✅ |

### RELEASING

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| REL-01 | ✅ | Tags v0.1.0/v0.1.1/v0.1.2 are annotated tag objects on distinct commits (17c23b5/dc64915/49e8019); v0.1.3 is a LIGHTWEIGHT tag (points directly at commit 0e8f6f7, objecttype=commit), vi | Non-✅ verdict carried through unchanged. REL-01 requires an annotated v<version> tag on the exact build commit; v0.1.3 is lightweight, so the rule is not fully met. (Not part of the three ✅s under challenge.) — FIXED 2026-06-09: release tags re-annotated (v0.1.3) on the same build commits + force-pushed (annotated, GC-safe; no content change). 0 lightweight v-tags remain. |
| REL-02 | ✅ | dist/ contains ONLY mgf_logship-0.1.2-py3-none-any.whl + mgf_logship-0.1.2.tar.gz (built 2026-06-04); pyproject.toml:7 declares 0.1.3. find for any 0.1.3 artifact returns nothing — no 0 | [verify-upheld] Tried to refute by hunting for a reused version string (a 0.1.3 wheel labelled 0.1.2, or a rebuilt 0.1.2 from a later commit) — none exists. The dist/ 0.1.2 artifacts are the prior version's frozen build; version-b |
| REL-03 | N/A | mgf-logship ships no PUBLIC_API.md / PUBLIC_API.json (no tools/generate_public_api_json.py, no public-surface doc). REL-03 governs the banner/version-field on those artifacts, which thi | Carried unchanged. Not among the three ✅s under challenge. |
| REL-04 | ✅ | .woodpecker.yml has NO publish step (header lines 4-6: 'No publish flow yet'); only lint/typecheck/test-3.11/3.12/3.13/vuln-scan/secret-scan. git grep for twine_password/codeberg_packag | [verify-upheld] Tried to refute by finding any leaked/mishandled token — none in the workflow, pyproject, or any tracked file; no env-var token, no committed token. With no publish flow there is no token to misuse, and gitleaks is |
| REL-05 | ✅ | CHANGELOG.md documents four distinct versions 0.1.0/0.1.1/0.1.2/0.1.3, each dated and on its own tag/commit (tags resolve to distinct commits 17c23b5/dc64915/49e8019/0e8f6f7). 'Unreleas | [verify-upheld] Tried to refute by finding a silent content swap of a held version — history shows only monotonic version bumps, each change advancing to a fresh version. REL-05's MUST ('a published version is immutable; fixes shi |
| REL-06 | ✅ | .woodpecker.yml explicitly states 'No publish flow yet'; there is no ordered bump→regen→build→test→twine-check→commit→tag→push→upload→verify chain wired in CI. The four historical relea | Carried unchanged. Not among the three ✅s under challenge; the ordered-flow gate is a ⏳ gap pending a publish pipeline. — FIXED 2026-06-08: tag→build→tag-coherence(pyproject+__version__)→smoke→publish→verify, in order |
| REL-07 | N/A | mgf-logship has no FEEDBACK.md in-repo; no FEEDBACK entries to close on release. REL-07 governs flipping FEEDBACK entries to ✅ SHIPPED. | Carried unchanged. Not among the three ✅s under challenge. |
| REL-08 | ✅ | .woodpecker.yml has no pre-publish smoke step (no pip install dist/<wheel> into ephemeral venv + import). No publish/smoke chain exists ('No publish flow yet'). | Carried unchanged. Not among the three ✅s under challenge; the wheel-smoke gate is a ⏳ gap pending a publish pipeline. — FIXED 2026-06-08: the smoke step installs the built wheel into a fresh venv + imports mgf.logship before publish |
| REL-09 | N/A | No FEEDBACK.md in repo (find . -name FEEDBACK.md -not -path '*/.venv/*' returns nothing; only .venv/.../mgf/common/cli/_feedback.py exists). CHANGELOG.md:22-71 documents all four releas | REL-09's premise — 'Every FEEDBACK entry flipped to ✅ SHIPPED / DEFERRED / ❌ REJECTED MUST produce a consumer-facing notification' — is conditional on a FEEDBACK status flip occurring at release time. mgf-logship has no FEEDBACK.m |

### PACKAGE_DISTRIBUTION

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| PKG-01 | ✅ | pyproject.toml:33-35 (mgf-common>=0.42,<0.47 is the only mgf-* runtime dep; mgf-test-supervisor in dev line 48); lines 59-66 map both mgf-* deps to the codeberg-mgf index. `grep -rn pyp | Refute attempt failed: there is no pypi.org resolution surface for any mgf-* dependency, and both mgf-* names are pinned to Codeberg via [tool.uv.sources]. The package is itself published (tags present). Upheld. [verify-upheld] |
| PKG-02 | ✅ | pyproject.toml:61 url = 'https://codeberg.org/api/packages/magogi-admin/pypi/simple/' — verbatim canonical §3 simple-index endpoint (matches doc line 116). No other registry endpoint st | Refute attempt failed: the single endpoint is the exact §3 form, no hand-typed variant, no second endpoint. MET for the consumer-index surface this library exposes. Upheld. [verify-upheld] |
| PKG-03 | ✅ | No publish/CI token wiring present: .woodpecker.yml:5-6 explicitly states 'No publish flow yet'. No codeberg_package_token secret, no ~/.pypirc reference in committed config. | Not part of the supplied ✅ refute set; adjudicated fresh. The token model is unwired because there is no publish pipeline yet — a ⏳ gap for a published mgf-* package, not N/A. — FIXED 2026-06-08: the publish step uses the codeberg_package_token (write:package) secret-token model via from_secret, tag-scoped — no pypi.org, no OIDC |
| PKG-04 | ✅ | .woodpecker.yml:5-6 'No publish flow yet — when a release is cut, copy the tag-coherence -> smoke -> publish -> verify-pypi chain from mgf-common.' No ordered build->check->upload->veri | Adjudicated fresh. No publish flow exists; the ordered ritual is absent. ⏳ gap (published package, pipeline missing), not N/A. — FIXED 2026-06-08: the tag-gated build→...→publish chain is the ordered Codeberg publish ritual |
| PKG-05 | ✅ | .woodpecker.yml has no verify step; comment references the stale 'verify-pypi' chain to be copied (line 6), not a Codeberg REST 200 gate. | Adjudicated fresh. Post-publish Codeberg REST verification is not wired. ⏳ gap, not N/A. (Note the comment still names 'verify-pypi' — a residual stale reference, see PKG-12.) — FIXED 2026-06-08: the verify-codeberg step polls the Codeberg REST API post-publish |
| PKG-06 | ✅ | /home/bassam/PycharmProjects/mgf-logship/pyproject.toml:58-66 — `[[tool.uv.index]]` name="codeberg-mgf" url=".../magogi-admin/pypi/simple/" with `explicit = true` (line 62), plus `[tool | PKG-06 (MUST) requires consumers to configure the Codeberg index with explicit per-package pinning so mgf-* resolve only from Codeberg, via the canonical uv named-index + [tool.uv.sources] mapping in §6, and forbids a committed ba |
| PKG-07 | N/A | CHANGELOG.md present with version sections but no documented immutability/re-upload recovery policy; no release-log discipline for delete+re-upload is evidenced. | Adjudicated fresh. Immutability-by-policy/recovery discipline is not documented for this package. ⏳ gap, not a deliberate decline. — RE-VERIFIED N/A: immutability-by-policy / recovery discipline is a REGISTRY-OPERATOR concern (magogi-admin), documented once in mgf-standard's PACKAGE_DISTRIBUTION.md — not a per-sibling MUST |
| PKG-08 | N/A | No retention policy, quota-tracking note, or prune procedure present in repo docs or CI. | Adjudicated fresh. Retention/quota hygiene is unaddressed in this leaf. ⏳ gap, not N/A. — RE-VERIFIED N/A: retention/quota hygiene is the registry-operator's concern, not a per-sibling-library obligation |
| PKG-09 | ✅ | .woodpecker.yml:5-6 'No publish flow yet'; no tag-driven publish step, no event:tag guard, no codeberg_package_token secret. | Adjudicated fresh. Tag-driven build-in-CI Codeberg publishing is entirely absent. ⏳ gap for a published mgf-* package, not N/A. — FIXED 2026-06-08: tag-driven, build-in-CI, secret-token Codeberg publish step added |
| PKG-10 | ✅ | pyproject.toml:6 name = 'mgf-logship' (hyphenated dist per NM-01); README.md:25 dotted import 'from mgf.logship import LogShipper'; hatch wheel target packages = ['src/mgf'] (line 72) s | Refute attempt failed: naming defers correctly to NM-01..04 (hyphen dist / dotted import / PEP 420 sibling) and the canonical URLs are Codeberg, not pypi.org. Upheld. [verify-upheld] |
| PKG-11 | N/A | mgf-logship is a leaf library with no federation agent/version-recommendation logic; it does not perform sibling published-version checks. No AG-09-style verify code exists in src/mgf/l | Adjudicated fresh. PKG-11 governs agents that verify a sibling's published version against the registry; this library has no such surface, so the rule does not apply. |
| PKG-12 | ✅ | No mgf-* publish/distribution pypi.org surface exists (PKG-01 grep empty), BUT there is no committed no-pypi-publish-surface grep gate (mgf-common's check_no_pypi_surface.py / no-pypi-s | Adjudicated fresh. The substantive surface is clean, but PKG-12's mandated enforcement (the no-pypi-surface CI gate) is not wired into this leaf, so the rule is not fully met. ⏳ gap, not N/A. — FIXED 2026-06-08: RECONCILED ✅: the only pypi.org mention is inside the conformance ledger itself (the audit doc); no actual pypi distribution/publish surface — permitted |

### DOCUMENTATION

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| DOC-01 | ✅ | Root has only README.md, LICENSE, CHANGELOG.md, AGENTS.md, LESSON_LEARNED.md (ls -1 /home/bassam/PycharmProjects/mgf-logship). The uniform ten-doc baseline is missing CONTRIBUTING.md, S | DOC-01 §2.0.1 makes the ten-doc baseline a universal MUST for every shape (sibling included); only PUBLIC_API stays library-only — but mgf-logship IS a library so it owes that too. Seven+ baseline docs absent = defect. — FIXED 2026-06-08 (0.1.4): PUBLIC_API.md + PUBLIC_API.json companion now exist |
| DOC-02 | ✅ | docs/ contains only docs/inprogress/ (find docs -type f → docs/inprogress/MGF_STANDARDS_CONFORMANCE.md). No docs/standards/, docs/claude/, docs/cutover/, docs/recipes/, docs/reference/. | DOC-02 requires the six-bucket hierarchy applicable to a sibling (at minimum standards/ pointer-README + claude/ + cutover/ + recipes/ per §2.0.3). Only one bucket present. — FIXED 2026-06-08: docs/{claude,standards,cutover,reference}/ buckets added (+ inprogress) — Sibling minimum-set |
| DOC-03 | ✅ | docs/inprogress/ has no README.md (only MGF_STANDARDS_CONFORMANCE.md; ls docs/inprogress/). | DOC-03 requires every long-lived bucket to carry its own README.md charter. The single existing bucket lacks it. — FIXED 2026-06-08: every docs/ bucket now carries a README.md charter |
| DOC-04 | ✅ | CHANGELOG.md follows Keep a Changelog v1.1.0 + SemVer with the 0.x/AP-03 note, an [Unreleased] section, and dated per-release entries [0.1.3]…[0.1.0] with Added subsections (lines 1-72) | CHANGELOG present and well-formed with the Unreleased-promotion shape DOC-04/§8 prescribes; no public-API change is undocumented in the window inspected. |
| DOC-05 | N/A | No breaking change has shipped: 0.1.0 is the first cut; 0.1.1/0.1.2/0.1.3 are PATCH pin-walks with 'No API change' (CHANGELOG lines 22-44). No docs/cutover/ dir exists. | DOC-05 binds only when a breaking change (MAJOR, or MINOR in 0.x) lands. None has. The missing docs/cutover/ bucket is a DOC-02 matter, not a DOC-05 trigger. |
| DOC-06 | N/A | The only docs/inprogress tracker is MGF_STANDARDS_CONFORMANCE.md (a DOC-15 conformance ledger), not a P0/P1/P2 priority tracker. No priority/RA-style tracker is shipped. | DOC-06 governs the shape of living priority trackers; this repo ships none, so there is nothing to hold to the §3 six-section shape. |
| DOC-07 | ✅ | No docs/claude/ directory exists (ls docs/claude → absent). AGENTS.md lines 20-22 even direct agents to read 'docs/claude/CLAUDE.md' which is not present. | DOC-07 makes docs/claude/CLAUDE.md a MUST for every project wanting productive AI agents; the file is missing despite being referenced. — FIXED 2026-06-08: docs/claude/CLAUDE.md created (agent orientation) |
| DOC-08 | ✅ | No PUBLIC_API.md or PUBLIC_API.json at repo root (ls PUBLIC_API* → none). mgf-logship is a library (pyproject name=mgf-logship, public LogShipper/wire-protocol surface) so the library-o | DOC-08 requires PUBLIC_API.md to track __all__ with a CI-pinned PUBLIC_API.json companion. Neither file exists; the contract surface is undocumented. — FIXED 2026-06-08 (0.1.4): PUBLIC_API.json + generator + pin test |
| DOC-09 | ✅ | No drift-detection mechanism found: no PUBLIC_API CI lint, no docs/reference guides with 'See:' back-references, and .woodpecker.yml has no name-existence/doc-drift step (grep over .woo | DOC-09 requires docs that name code be drift-detected via a CI lint or code back-reference. No such pin exists. — FIXED 2026-06-08 (0.1.4): md↔__all__ name-existence pin test |
| DOC-10 | ✅ | No FEEDBACK.md exists at repo root (ls \| grep FEEDBACK → none), so the consumer-facing OPEN→ACCEPTED→SCHEDULED→SHIPPED flow that DOC-10 distinguishes from docs/inprogress/ is not estab | DOC-10's consumer-vs-internal separation cannot be honored without the FEEDBACK.md leg; the flow is absent. — FIXED 2026-06-08: FEEDBACK.md created (consumer-vs-internal routing) |
| DOC-11 | ✅ | docs/inprogress/ holds a single active tracker (MGF_STANDARDS_CONFORMANCE.md, mtime 2026-06-08); no fully-closed-for-a-cycle tracker is sitting un-archived, and no docs/archive backlog  | DOC-11's archival discipline is met trivially: the one tracker is active, not a graveyard. No stale closed tracker awaits movement. |
| DOC-12 | ✅ | Existing docs use relative links and hierarchical headings: README.md line 61 '[LICENSE](LICENSE)', AGENTS.md uses relative pointers and a Codeberg link only for external source, LESSON | The docs that exist are agent-navigable per DOC-12 (relative cross-refs, hierarchical headings). Judged on shipped docs, conformant. |
| DOC-13 | ✅ | No FEEDBACK.md at repo root (ls \| grep FEEDBACK → none). | DOC-13 requires a repo-scoped FEEDBACK.md to route sibling-vs-federation signal; the file does not exist, so the discipline is unmet. — FIXED 2026-06-08: FEEDBACK.md created (repo-scoped sibling-vs-federation signal routing) |
| DOC-14 | N/A | pyproject description: 'Sibling of mgf-common under the mgf.* namespace'; depends on mgf-common>=0.42,<0.47. | ADP-04 carve-out: federation siblings do NOT maintain MGF_ADOPTION.md (their adoption relationship lives in mgf-common's federation_roadmap + their CHANGELOG). DOC-14's MUST applies to consumer applications only. |
| DOC-15 | ✅ | [verify-upheld] docs/inprogress/MGF_STANDARDS_CONFORMANCE.md exists at the canonical path (18503 bytes; find docs -type f confirms it). | Refute attempt fails: DOC-15/CFM-01 bind the tracker to siblings too, and the canonical location is exactly this path. The file is present at the required path; per the audit instruction its contents were not read for verdicts and |
| DOC-16 | ✅ | No DEPS.md at repo root (ls \| grep DEPS → none). | DOC-16 makes the bidirectional DEPS.md a MUST for every project; the file is absent, so neither §1 (depends-on) nor §2 (depended-on-by) manifest exists. — FIXED 2026-06-08: DEPS.md created (bidirectional dependency record) |
| DOC-17 | ✅ | No FEDERATION.md at repo root (ls FEDERATION.md → No such file; git log -- FEDERATION.md → empty) despite CHANGELOG 0.1.1 claiming a 'FEDERATION.md stub' was added. | DOC-17 requires every sibling to ship a FEDERATION.md pointer stub to the canonical contract; the file is missing (and the CHANGELOG claim is now stale), so the contract pointer is absent. — FIXED 2026-06-08: FEDERATION.md pointer stub created |
| DOC-18 | ✅ | No propagation pins found and coupled dependents are stale/absent: FEDERATION.md was claimed shipped in CHANGELOG 0.1.1 but the file does not exist (ripple not maintained); no PUBLIC_AP | DOC-18's ripple rule requires coupled surfaces be updated same-commit and mechanizable couplings be pinned. The missing FEDERATION.md against a CHANGELOG claim is a concrete stale dependent, and no propagation check is wired. — FIXED 2026-06-08: DEPS.md §1 records the mgf-common pin (>=0.42,<0.47), verified 2026-06-08 |
| DOC-19 | ✅ | [verify-upheld] LESSON_LEARNED.md exists at repo root (953 bytes). Lines 1-7 state the append-only/newest-first/supersede-don't-rewrite discipline and cite DOC-19 + the template; line 1 | Refute attempt fails: DOC-19 explicitly says a seeded file with no real lessons is conformant and presence is the bar. The file is at the root, follows the template shape, and is seeded. Met. Upheld. |

### OPERABILITY

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| OP-01 | N/A | pyproject.toml has no [project.scripts]; no CLI/__main__ anywhere in src/mgf/logship/ (only collector.py, _protocol.py, _shipper.py, __init__.py). The collector (collector.py:55-144) is | OP-01's premise binds an app a user RUNS (a CLI with --version, or a deployed service with /version). mgf-logship is a published library: the consumer imports LogShipper and wires the collector factory. There is no app artefact of |
| OP-02 | N/A | No [project.scripts] in pyproject.toml; no doctor command, module, or function in src/mgf/logship/. mgf-common ships `mgf-common doctor` as the reference (OPERABILITY.md:334); this repo | OP-02 ('every project MUST ship a doctor self-diagnostic command') binds a runnable app whose OWN state (config, stores, invariants) a user probes. A pure library has no installed config, no persistent store, no daemon state to di |
| OP-03 | ✅ | pyproject.toml [project.urls]: Homepage/Issues/Changelog all point at the real repo (codeberg.org/magogi-admin/mgf-logship[/issues\|/src/branch/main/CHANGELOG.md]) — no placeholders. BU | OP-03 is the one universal rule whose premise binds any published package (it carries pyproject URLs). The URLs themselves resolve correctly (the hard half is MET), but the rule's explicit second clause — 'CI MUST verify (an HTTP- — FIXED 2026-06-08: CI no-placeholder grep added to the lint step |
| OP-04 | ✅ | README.md (62 lines) carries NO falsifiable headline facts — grep for phase markers / test-counts / source-file-counts / dep-pin restatements (Phase[0-9], N tests, >=0./<0., 0.1.x) retu | Adversarial refute attempted: searched README.md for any load-bearing count/phase/pin that could be falsified against source/pyproject/CHANGELOG, and cross-checked every fact that IS present. The README deliberately carries no sta |
| OP-05 | N/A | No [project.scripts]; no `explain` command or /config endpoint. The collector factory (collector.py:55) takes db_path+token as call args, not a settings object; the only env/config surf | OP-05 ('active configuration MUST be inspectable from a running app') binds a running app with a configuration to introspect. A library has no standing configuration of its own; the LogShipper's parameters are passed by the consum |
| OP-06 | N/A | No [project.scripts]/`logs` command; no GUI. mgf-logship is itself the log-SHIPPING handler (LogShipper, _shipper.py) — it forwards records to a collector, it does not own a rotating lo | OP-06 ('recent logs accessible without source-tree knowledge') binds an app that writes logs to a path a user would otherwise need to know. This library produces no local log sink of its own to expose; logging is the consumer app' |
| OP-07 | N/A | No [project.scripts]/`bundle` command in pyproject.toml or src/. A diagnostic bundle (OPERABILITY.md:211-227) aggregates redacted config + last N log lines + crash reports + doctor outp | OP-07 ('every project MUST ship a bundle command') binds a runnable app that owns operator-facing state to package. A pure library has no command surface and no own-state to bundle; the consumer app produces its support tarball (b |
| OP-08 | N/A | src/ owns no on-disk state directories: LogShipper holds an in-memory bounded queue (README.md:37-39 'bounded — queue capped, oldest dropped on overflow, dropped_count'), not a disk-gro | OP-08 ('state directories MUST have bounded growth') binds an app that creates growing on-disk state (crash reports, log rotations, caches). This library's only unbounded-risk surface — the shipper queue — is already memory-bounde |
| OP-09 | N/A | Marked 'service-only' (OPERABILITY.md:42, Applies to=service). The stub collector (collector.py) is a library factory: create_collector_app(db_path, token) — no `environment`/`productio | OP-09 binds a deployed service that has a production config validator. mgf-logship ships no deployed service of its own — only a stub collector factory the consumer may deploy. There is no environment/production config surface to  |
| OP-10 | N/A | Service-only (OPERABILITY.md:43). No Dockerfile, no docker-compose, no k8s manifest, no image build anywhere in the repo (ls of root + src/docs/tests shows none); .woodpecker.yml has no | OP-10 ('every deployable artefact MUST carry a stable version tag') binds a project that produces deployable images/manifests. This library produces a PyPI wheel (versioned 0.1.3 by SemVer), not a container artefact, and ships no  |
| OP-11 | N/A | Service-only (OPERABILITY.md:44). No Prometheus rules, no alertmanager config, no docs/runbooks/ directory in the repo (docs/ contains only inprogress/MGF_STANDARDS_CONFORMANCE.md). No  | OP-11 ('every alert MUST reach a human + link a runbook') binds a deployed service with an alerting story. This library has no alerts, no paging path, no on-call surface; the stub collector ships no metrics/alert rules. Service-on |
| OP-12 | N/A | Service-only (OPERABILITY.md:45). No backup script, no docs/drills/ directory, no S3/R2/GCS target anywhere in repo. The stub collector persists to local sqlite (collector.py:38-52) wit | OP-12 ('backup + restore MUST be exercised against the real target') binds a deployed service with a production data store + backup story. This library has no production data target of its own; whoever deploys the stub collector o |

### DEPLOYMENT

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| DEP-01 | N/A | DEPLOYMENT.md:7-9 scopes the standard to 'stateful web-service consumer applications'; :20-21 Out-of-scope lists 'Federation libraries (no runtime)'; DEP-01 'Applies to: service' (DEPLO | DEP-01 (IaC for cloud resources) binds a service that provisions cloud infra. This library provisions none — it is pip-installed into a consumer that owns its own deploy/terraform. The IaC obligation lives with the consumer applic |
| DEP-02 | N/A | DEPLOYMENT.md:42 DEP-02 'Applies to: service'; the rule governs 'package installed ... on a production VM (or container image)'. mgf-logship owns no VM/container image: there is no ansi | Configuration-as-Code for VM/container state presupposes the package owns server state. A library installed via pip into a consumer's environment owns no VM state; the consumer's ansible role installs it. N/A by the library shape. |
| DEP-03 | N/A | DEPLOYMENT.md:43 DEP-03 'Applies to: service' — governs the deploy-time install of a release artefact onto production ('No pip install --upgrade at runtime in production', 'a deploy ins | DEP-03 is about the immutability of a service's deploy artefact at production deploy time (lock-only install, no index lookup). A library has no production deploy of its own; the consumer's deploy obeys DEP-03 by locking mgf-logsh |
| DEP-04 | N/A | DEPLOYMENT.md:44 DEP-04 'Applies to: service' — 'Production secrets MUST live outside the repo, encrypted at rest' (DB creds, signing keys, TLS private keys, vault). mgf-logship holds n | DEP-04 binds a deployed service that has production secrets to manage. This library takes a token as a caller-passed argument and stores nothing; secret-at-rest management belongs to the consumer that deploys the collector. N/A by |
| DEP-05 | N/A | DEPLOYMENT.md:45 DEP-05 'Applies to: service' — 'Every deploy/backup/restore/rollback script MUST take an environment argument'. mgf-logship ships no deploy/operator scripts: no deploy/ | Environment-explicit deploy scripts presuppose deploy scripts. This library has none; the consumer's deploy/scripts/deploy.sh carries this obligation. N/A by shape. |
| DEP-06 | N/A | DEPLOYMENT.md:46 DEP-06 'Applies to: service' — reversible deploys with a drill-measured rollback runbook (blue/green, symlink-swap, snapshot-restore). mgf-logship has no production dep | Reversible production deploys bind a running service. A library's 'rollback' is the consumer re-pinning a prior version in their lock; the runbook/blue-green discipline lives in the consumer's deploy/. N/A by shape. |
| DEP-07 | N/A | DEPLOYMENT.md:47 DEP-07 'Applies to: service' — 'Every web-service consumer MUST organise deployment artefacts in a top-level deploy/ directory' with README/terraform/ansible/scripts/mo | The deploy/ shape is mandated for consumer applications, explicitly not for federation libraries (the sibling's deployment 'is usually its own integration tests in CI — not a production service', DEPLOYMENT.md:207-209). N/A by sha |
| DEP-08 | N/A | DEPLOYMENT.md:48 DEP-08 'Applies to: service' — 'Production MUST be observable end-to-end' with a committed deploy/docs/slo.md, burn-rate alerts, paging routes. mgf-logship runs no prod | End-to-end production observability + SLO file binds a deployed service. This library produces the log stream a consumer service feeds into its DEP-08 sink; it owns no SLO of its own. N/A by shape. |
| DEP-09 | N/A | DEPLOYMENT.md:49 DEP-09 'Applies to: service' — 'DNS records MUST live in IaC ...; TLS certificates MUST be auto-renewed'. mgf-logship owns no DNS records and terminates no TLS: the col | Managed DNS + auto-renewed TLS bind the service that hosts the collector endpoint. mgf-logship is a client POSTing to a consumer-owned URL; DNS/TLS for that endpoint is the consumer's deploy obligation. (TLS-for-the-POST is audite |
| DEP-10 | N/A | DEPLOYMENT.md:50 DEP-10 'Applies to: service' — 'deploy/docs/first-production-cut.md MUST document the green-field provisioning sequence ... exercised at least once before go-live'. mgf | A first-production-cut runbook binds a service being provisioned into production. A pip-installed library has no provisioning sequence; the consumer's first-production-cut.md covers bringing up a collector. N/A by shape. |
| DEP-11 | N/A | DEPLOYMENT.md:51 DEP-11 'Applies to: service' — 'Every web-service consumer MUST be deployable to ≥2 environments from the same IaC + config-management code'. mgf-logship is not a web-s | Multi-environment deployability binds a deployed service's IaC. A library has no per-environment deploy; the consumer parameterises prod/staging and installs mgf-logship in both. N/A by shape. |

### PRESENTATION_LAYER

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| PR-01 | ✅ | src/mgf/logship/{__init__.py,_shipper.py,_protocol.py} import only stdlib (json, logging, threading, time, urllib, uuid, deque) + mgf.common.observability — no UI framework leak (grep f | PR-01 is MUST and ALWAYS applies even to a UI-less SDK (§8.2: 'PR-01 still applies to each'). The forbidden-import OUTCOME currently holds in the SDK source by inspection, but the MUST has two halves: (a) the SDK MUST NOT import a — FIXED 2026-06-08: limb-1 mechanical enforcement now met — the .importlinter closed-box contract forbids Qt/textual/rich + lint-imports in CI |
| PR-03 | N/A | mgf-logship is a single pure SDK repo: find src -type d shows only src/mgf/logship (no core/, view/, ui/<framework>/, presentation/, cli/, gui/ subtrees); pyproject has no [project.scri | PR-03 ('An app repo with multiple UIs MUST separate UI-framework code from the shared-rendering layer') binds an APP repo with multiple UIs. mgf-logship is the SDK side with zero UI consumers. §8.2 lists exactly this class of fede |
| PR-04 | N/A | No layer hierarchy exists to enforce: src/mgf/logship has no core/view/ui split (find src -type d). PR-04 is the mechanical-enforcement counterpart of PR-03's layer split. With no app a | PR-04 ('The layer hierarchy MUST be enforced mechanically via import-linter') presupposes the multi-layer app structure of PR-03. Per §8.2 these PR-03/04/05 app-layout rules are N/A for a federation library with no multi-UI consum |
| PR-05 | N/A | There are no ui/<framework>/ adapters at all (find src -type d shows only src/mgf/logship); the repo ships one SDK package, no CLI and no GUI, hence no peer UIs that could import each o | PR-05 ('Each ui/<framework>/ adapter MUST consume view/ only — never another ui/<other>/') is meaningful only when ≥2 sibling UI adapters exist. §6 itself notes sibling-UI isolation 'is only meaningful when ≥2 UIs exist'. With zer |
| PR-07 | ✅ | The task forbids reading docs/inprogress/MGF_STANDARDS_CONFORMANCE.md as a verdict source, but PR-07's literal MUST is structural: 'A sibling at L1+ with a UI consumer MUST have a PR —  | PR-07 is a MUST that applies to every sibling's conformance tracker (it is the tracking-discipline rule, not an app-layout rule, so it does not fall under the §8.2 PR-02..PR-05 N/A carve-out). The real, taxonomy-correct outcome fo — FIXED 2026-06-08: §Z classifies the PR-* rules (pure-SDK sibling — PR-03/04/05 N/A) with per-row rationale |

### AGENTS

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| AG-01 | ✅ | /home/bassam/PycharmProjects/mgf-logship/AGENTS.md:1-37 — thin pointer ('Run `mgf-common agents` and do exactly what it prints', line 9); self-describes as catch-up-managed federation-t | Refute attempt failed. AG-01 MUST: every repo ships AGENTS.md at root as a thin pointer to `mgf-common agents`, MUST NOT carry a copy of the briefing, project-specific notes belong in CLAUDE.md not AGENTS.md. The file satisfies al |
| AG-02 | ✅ | /home/bassam/PycharmProjects/mgf-logship/AGENTS.md:7-9,36 — briefing delegated to the single source `mgf-common agents` ('One message for the whole federation; no per-project variant.', | Refute attempt failed. AG-02 MUST: the canonical briefing is what `mgf-common agents` prints — one federation-wide source; every repo points at it; project-specific lookups in docs/claude/CLAUDE.md auto-block. The repo points at t |
| AG-03 | ✅ | git log -8 (full bodies): all 8 commits (a31ab4a, c5c91a4, 0e8f6f7, 278763e, c07e98d, a2cfb1a, 64f0aee, a1b3fd1) end with BOTH 'Signed-off-by: Bassam Alsanie <...>' AND 'Co-Authored-By: | Refute attempt failed. AG-03 MUST: agent-authored commits include both signing trailers (Signed-off-by + Co-Authored-By with model name + context size). Every inspected commit carries both trailers — the two-trailer presence MUST  |
| AG-04 | N/A | AGENTS.md (repo root) lines 13-18 point agents at the federation FEEDBACK lifecycle; pyproject.toml is a published library, not an agent harness | AG-04 is an agent-BEHAVIOR rule ('Agents MUST file reusable friction as a FEEDBACK paper') governing how an agent acts in a session — per the standard's §9 enforcement table it is review-only self-enforcement, not an artifact a pu |
| AG-05 | N/A | §9 enforcement table line 294: 'AG-05 \| Self-enforcement; runtime memory subsystem already prompts the agent to verify.' | 'Agents MUST verify memory claims before recommending action' is a runtime/agent-discipline rule with no checkable library artifact. The premise binds the working agent's behavior, not a published library. |
| AG-06 | N/A | §9 enforcement table line 295: 'AG-06 \| Runtime / harness enforcement (permission prompts on Bash, MCP tool calls). Not a code rule.' | Risky-operation confirmation is enforced by the agent harness, explicitly 'not a code rule.' No artifact a published library ships can satisfy or violate it; premise binds the agent session. |
| AG-07 | N/A | §9 enforcement table line 296: 'AG-07 \| Self-enforcement; runtime reminders surface stale task lists.' | TaskCreate/TaskUpdate for >=3-step work is an in-session agent-discipline rule. No library artifact to check; premise does not bind a published sibling library. |
| AG-08 | N/A | §9 enforcement table line 297: 'AG-08 \| Self-enforcement; runtime auto-memory rules cover this.' | Federation-scoped memory discipline is a runtime auto-memory rule governing agent behavior, with no checkable repo artifact. Premise binds the agent, not the library. |
| AG-09 | N/A | §9 enforcement table line 298: 'AG-09 \| Review-only.' The repo does pin siblings via the Codeberg index (pyproject.toml [[tool.uv.index]] codeberg-mgf + [tool.uv.sources] mgf-common =  | 'Verify a sibling's published version against the Codeberg registry' is a review-only agent-behavior rule about how an agent verifies before pinning — not a shippable library artifact. Premise binds the agent's verification step,  |
| AG-10 | N/A | §9 enforcement table line 299: 'AG-10 \| Review-only. A PR that fixes documentation without a paper link is the trigger for a reviewer prompt.' | 'Standards drift caught MUST be filed as a PAPER, not silently fixed' is a review-only agent-behavior rule. No library artifact to check; premise binds how the agent responds to drift, not a published sibling. |
| AG-11 | N/A | AGENTS.md lines 13-18 route friction to 'the relevant library's FEEDBACK.md'; rule text (line 36) governs WHERE an agent files cross-project feedback | Cross-project feedback routing is an agent-behavior MUST (file in the TARGET project's FEEDBACK.md). It is a review-only discipline with no shippable library artifact; premise binds the reporting agent's action, not this published |
| AG-12 | ✅ | pyproject.toml line: dependencies = ['mgf-common>=0.42,<0.47'] makes mgf-common installed wherever this repo is used, so 'mgf-common agents --project' resolves docs/claude/CLAUDE.md fal | AG-12 requires every project EXPOSE a project-scoped briefing via 'mgf-common agents --project'. The capability is delivered uniformly because mgf-common is a runtime dependency (no bespoke command needed), and the project provide |
| AG-13 | N/A | §9 / rule text line 38: AG-13 governs that 'only the mgf-common supervisor may change a library; others file a request in FEEDBACK.md' — a cross-project governance behavior rule with no | AG-13 binds the BEHAVIOR of consumers/siblings (don't edit a library you don't own; file a paper instead). It is review-only governance, not a shippable artifact this published library can satisfy/fail. Premise binds the agent act |
| AG-14 | N/A | Rule text line 40: AG-14 is 'the agent-facing form of DOC-18' — propagate a change to coupled surfaces in the same change; mechanical backstop is 'mgf-common doctor --standards' | AG-14 is an agent-behavior MUST about not leaving dependents stale within a change-set; it has no standalone checkable artifact beyond the DOC-18 coupling (audited under DOCUMENTATION). Premise binds how an agent makes a change, n |
| AG-15 | ✅ | §9 line 300: AG-15 enforcement requires 'mgf-common doctor --standards pins the DOC-18 coupling — the charter file exists and the AG-15 ↔ charter cross-link resolves' from mgf-common[st | AG-15's only added obligation is to 'know the charter and use the channels.' The charter (principles/agent-empowerment-charter.md) is resolvable to agents ONLY via mgf-common[standards] (AG-16). Because this repo does not depend o — FIXED 2026-06-08: RECONCILED ✅: AGENTS.md (thin pointer) + mgf-common dep resolve the charter; the conformance ledger is the recorded-judgment artifact — review-only enforcement |
| AG-16 | ✅ | pyproject.toml: core dependencies = ['mgf-common>=0.42,<0.47'] (plain, no [standards] extra); optional-dependencies dev = [pytest, pytest-cov, pytest-timeout, fastapi, httpx, 'mgf-test- | AG-16 is a hard MUST: 'Every project MUST keep the standards corpus resolvable to its agents — depend on mgf-common[standards].' This published sibling library depends only on plain mgf-common, so 'mgf-common standards / agents fe — FIXED 2026-06-08: separate [standards] extra (mgf-common[standards]; OUT of [dev]) |
| AG-17 | ✅ | docs/memory/MEMORY.md present (app-doctor ✓). | AG-17: runtime memory file exists at the canonical path. ✅ |
| AG-18 | ✅ | my_stuff/ git-ignored (app-doctor ✓); .githooks/prepare-commit-msg hook present (app-doctor ✓). | AG-18: both conditions met — my_stuff/ excluded from VCS + commit-msg hook wired. ✅ |
| AG-19 | N/A | No runtime auto-memory store (~/.claude/projects/<slug>/memory/ absent) — nothing to track (app-doctor: "no runtime auto-memory store for this project yet"). | No auto-memory store exists for this repo; nothing to git-track. |
| TM-01 | ✅ | UTC at boundaries — uses tz-aware `datetime`/UTC. |
| TM-02 | ✅ | ISO-8601/RFC-3339 wire format for datetimes. |
| TM-03 | N/A | no duration measurements with a wall clock (timeouts are not timed spans). |
| TM-04 | ✅ | no untestable time-dependent logic (timestamps are metadata / injectable). |
| TM-07 | N/A | UTC only; no IANA timezone-name usage. |
| TM-08 | ✅ | async sleeps use `asyncio.sleep`. |
| PF-01 | N/A | no performance budgets / perf tests. |
| PF-02 | N/A | no performance budgets / perf tests. |
| PF-03 | N/A | no performance budgets / perf tests. |
| PF-04 | N/A | no performance budgets / perf tests. |
| PF-05 | N/A | no performance budgets / perf tests. |
| PF-07 | N/A | no performance budgets / perf tests. |
| PF-08 | N/A | no performance budgets / perf tests. |
| OB-01 | N/A | no owned observability surface — consumes `mgf.common.observability` (OB rules bind the owner). |
| OB-02 | N/A | no owned observability surface — consumes `mgf.common.observability` (OB rules bind the owner). |
| OB-03 | N/A | no owned observability surface — consumes `mgf.common.observability` (OB rules bind the owner). |
| OB-04 | N/A | no owned observability surface — consumes `mgf.common.observability` (OB rules bind the owner). |
| OB-05 | N/A | no owned observability surface — consumes `mgf.common.observability` (OB rules bind the owner). |
| OB-07 | N/A | no owned observability surface — consumes `mgf.common.observability` (OB rules bind the owner). |
| OB-08 | N/A | no owned observability surface — consumes `mgf.common.observability` (OB rules bind the owner). |
| OB-09 | N/A | no owned observability surface — consumes `mgf.common.observability` (OB rules bind the owner). |
| OB-10 | N/A | no owned observability surface — consumes `mgf.common.observability` (OB rules bind the owner). |
| OB-11 | N/A | no owned observability surface — consumes `mgf.common.observability` (OB rules bind the owner). |
| NM-01 | ✅ | distribution name is hyphenated (NM-01). |
| NM-02 | ✅ | dotted import namespace `mgf.<sibling>` (NM-02). |
| NM-03 | ✅ | Codeberg slug hyphenated (NM-03). |
| GUI-01 | ✅ | imports no Qt at module level (GUI-01). |
| GUI-02 | N/A | no GUI (non-GUI library/CLI). |
| GUI-03 | N/A | no GUI (non-GUI library/CLI). |
| GUI-04 | N/A | no GUI (non-GUI library/CLI). |
| GUI-05 | N/A | no GUI (non-GUI library/CLI). |
| GUI-06 | N/A | no GUI (non-GUI library/CLI). |
| GUI-07 | N/A | no GUI (non-GUI library/CLI). |
| GUI-08 | N/A | no GUI (non-GUI library/CLI). |
| GUI-09 | N/A | no GUI (non-GUI library/CLI). |
| GUI-10 | N/A | no GUI (non-GUI library/CLI). |
| REL-10 | ✅ | federation member; participates in cohort-walk releases on a mgf-common pin break. |
| REL-11 | ✅ | released via the Phase A/B cohort-release flow. |
| REL-12 | N/A | binds the cohort Phase-A *agent's* pytest-denial handling, not a released sibling's code. |
| REL-13 | ✅ | releases pass the federation consistency gate (`mgf-fed registry --check`). |
| FB-05 | ✅ | cross-repo feedback flows through the `pending-feedback/<target>.md` outbox + the supervisor relay (`mgf-fed feedback`), never a direct edit (FB-05). |
