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

> **Status.** v0.3 — re-audited + re-baselined 2026-06-25 (Item 1b-1 L3
> uplift, CFM-03 / DOC-15). Created 2026-05-18; first depth audit 2026-06-07.
> **Scope.** mgf-fastapi's conformance to the standards at
> [mgf-standard/docs/standards/](https://codeberg.org/magogi-admin/mgf-standard/src/branch/main/docs/standards/)
> — re-baselined to the **current corpus v2.25 (263 MUSTs across 22 families)**.
> **Audience.** Maintainer-tomorrow, AI coding agents, future
> contributors.
> **Cross-references.**
> [README.md](../../README.md) (declared level),
> [CHANGELOG.md](../../CHANGELOG.md) (release history),
> [PUBLIC_API.md](../../PUBLIC_API.md) (audited public surface).

---

## §1 You are here

**Project shape:** FastAPI integration adapters — request-id, lifespan, webhooks, IpAllowlist, run_test_app.

**Current version:** 0.6.11.

**Declared TARGET conformance level: L2** (per CFM-02; README headline reads "targets L2"). **Current verified standing (2026-06-25 re-audit): at the L2 bar on every owned surface**, with **2 fleet-infra deferrals (WF-09 / WF-11) it does not own**, **1 L1-deferrable item (TS-28)**, and **1 minor owned log-volume MUST still open (LG-13)**.

Of the **263** current MUSTs (corpus v2.25, 22 families): **165 ✅ verified** (file:line) · **4 ⏳ open** · **94 N/A** (library shape — no GUI/deploy/standalone-service surface) · **0 declined**. Applicable (non-N/A) = **169**; **165/169 (97.6%) evidenced**. CFM-02 **L2 requires every MUST**: the L2-blocking gaps the Item 1b-1 L3 audit surfaced (16 MUST + 2 SHOULD) are **all closed** (stage 1 + stage 2 below); the 4 residual ⏳ are 2 template-owned fleet items, 1 L1-deferrable perf-attribution item, and 1 small log-volume hygiene MUST — none in the 1b-1 L3-audit blocking set.

**2026-06-25 Item 1b-1 L3 uplift (this audit).** The 16 MUST + 2 SHOULD gaps the L3 depth-audit verified are closed across two stages, then this ledger was re-baselined to the current corpus (v2.25 / 263 MUSTs, +AG-17/18/19/WF-13). **Stage 1 (code):** SC-08 webhook body-size cap (pre-HMAC), LG-15 forged-webhook audit event + the 6 missing EVENTS.md rows, the SC §12 threat model (ARCHITECTURE.md), AP-14 closed-box import-linter contract + repointed db.py/test imports, the DOC-12/DOC-17 dead-slug sweep, CFM-02/OP-04 README↔ledger reconcile. **Stage 2 (features + this re-audit):** the perf-baseline harness (PF-01/03/05/07 + TS-14) — all 6 perf pins moved off absolute wall-clock budgets onto `baselines.assert_no_regression` vs a generated `tests/perf/baselines.json`; then this CFM-03 re-audit. Verdicts in §Z carry `file:line` and survived the original adversarial refute pass.

**Open-gap clusters (the 4 ⏳ — detail in §Z):**

| Cluster | Rules | Count | Standing |
|---|---|---|---|
| Fleet-infra: CI installs from pyproject ranges, not the lock | WF-09, WF-11 | 2 | **Deferred — not owned.** `.woodpecker.yml` is a generated, drift-checked template artifact (WF-13); the `uv sync --frozen` migration is gated on the devpi backfill gap and tracked at mgf-standard (WF-13). |
| Per-test perf attribution (TestRecord schema ≥1.1.0 + perf_span) | TS-28 | 1 | **Deferred.** L2-binding rule; L1 MAY defer. Needs perf_span instrumentation + a schema bump — perf-infra beyond this uplift. |
| Log-volume control on the rate-limit-denial INFO | LG-13 | 1 | **Open (minor, owned).** `_rate_limit.py` logs INFO per denied request with no sampling guard; the fix is to wrap it in an mgf.common `SampleFilter`/`RateLimitFilter`. Small hygiene item, not in the 1b-1 L3-blocking set. |


**Headline.** mgf-fastapi entered the v0.x release window after a
multi-phase P0 / P1 / P2 sweep (recorded in
`docs/inprogress/RELIABILITY_AND_ACCURACY.md`,
`SECURITY_HARDENING.md`, `PERFORMANCE_AND_CAPACITY.md` — those
trackers carry the falsifiable per-rule evidence). This conformance
tracker is the **rolling audit ledger** alongside those — drift over
time is the signal.

---

## §2 Per-standard audit

Audit method: every MUST in each standard under
[mgf-standard/docs/standards/](https://codeberg.org/magogi-admin/mgf-standard/src/branch/main/docs/standards/)
gets a `file:line`-evidenced verdict in §Z; this table is the per-family
roll-up (counts derived from §Z, 2026-06-25).

Status markers (CFM-03):
- **✅** conformant — implementation + tests + docs match the rule (file:line in §Z).
- **🚫** declined-with-rationale — rule deliberately not adopted; rationale in §3 (CFM-04).
- **⏳** applicable MUST not (fully) met — open gap tracked (§1 clusters).
- **N/A** — rule premise doesn't apply to this project's shape (library, no GUI/deploy/standalone-service).

| Family | ✅ | N/A | ⏳ | Notes |
|---|---|---|---|---|
| **DP** DESIGN_PRINCIPLES | 10 | 3 | 0 | Frozen domain types, mypy --strict gate, explicit cancellation, no-orphan-tasks (DP-11 fixed 0.6.9), ARCHITECTURE.md replaceability table + the new §12 threat model. |
| **EH** ERROR_HANDLING | 7 | 6 | 0 | Typed AppError hierarchy at every seam, `from exc` chaining, never-raise observers; EH-08 machine-readable `reason`, EH-09 hint pins added. |
| **LG** LOGGING | 13 | 3 | 1 | getLogger(__name__), redaction-before-egress, EVENTS.md catalogue (+ the webhook.verification_failed audit event, stage 1). **⏳ LG-13** — no sampling on the rate-limit-denial INFO (minor). |
| **SC** SECURITY | 11 | 7 | 0 | stdlib-only crypto + constant-time compare, boundary input caps, redacted egress, audit markers; **SC-08 webhook body-size cap (stage 1)**, SECURITY.md + the §12 threat model. |
| **CF** CONFIGURATION | 3 | 9 | 0 | Consumer-owned-config shape: load-once + DI-injected AppContext; no owned schema, env reads, or on-disk config. |
| **AP** API_DESIGN | 13 | 2 | 0 | Sorted `__all__`, keyword-only evolution, PUBLIC_API.json + pin tests, `__version__`; **AP-14 closed-box import-linter contract (stage 1)**. |
| **TS** TESTING | 24 | 3 | 1 | Mirror-lint, property tests, supervisor-in-CI, tiers, forbidden-pattern grep; **PF/TS-14 perf baseline harness (stage 2)**. **⏳ TS-28** — per-test perf attribution (L1 MAY defer). |
| **PF** PERFORMANCE | 7 | 0 | 0 | **Was the concentrated gap; now closed (stage 2).** All 6 perf pins baseline-relative vs a generated `tests/perf/baselines.json` (+ `hw_tiers.md`, numeric PF-07 messages, `bench rebaseline` recorder); PF-06 complexity annotations in PUBLIC_API.md. |
| **DOC** DOCUMENTATION | 18 | 1 | 0 | Six-bucket docs tree + charters, Keep-a-Changelog, cutover guides, LESSON_LEARNED; **DOC-12/17 dead mgf-doc-standard slug swept (stage 1)**. |
| **REL** RELEASING | 10 | 3 | 0 | Annotated tags, cohort-walk release flow, REL-13 consistency gate green, cutover discipline. |
| **PKG** PACKAGE_DISTRIBUTION | 9 | 3 | 0 | Codeberg/devpi index + per-package source map (confusion-safe), env-token auth, tag-driven publish pipeline. |
| **WF** WORKFLOWS | 11 | 0 | 2 | Four-gate CI, parallel DAG, tag-coherence→smoke→publish→verify chain; **WF-13** generated-artifact banner present. **⏳ WF-09/WF-11** — template-owned install-from-lock (fleet-infra deferral). |
| **OP** OPERABILITY | 3 | 9 | 0 | Library shape: version surface + DEPS present; OP-09..12 deploy/runbook N/A. |
| **OB** OBSERVABILITY | 1 | 10 | 0 | No owned observability surface — consumes `mgf.common.observability` (OB rules bind the owner). |
| **AG** AGENTS | 13 | 6 | 0 | CLAUDE/AGENTS briefing, FEEDBACK routing, coupled-surface propagation; **AG-17 docs/memory + AG-18 .gitignore/my_stuff** present (v2.24 MUSTs); AG-19 machine-local (N/A). |
| **SP** SCOPE | 6 | 0 | 0 | SCOPE.md §1-§6 (IN/OUT/Adjacent + PR scope-creep gate + revisit-on-MAJOR) shipped 2026-06-07. |
| **TM** TIME | 1 | 5 | 0 | No datetime crosses a boundary; monotonic durations only; `asyncio.sleep` for async waits. |
| **NM** NAMING | 3 | 0 | 0 | Hyphenated dist, dotted `mgf.fastapi` import, hyphenated Codeberg slug. |
| **GUI** GUI | 1 | 9 | 0 | Non-GUI library: imports no Qt at module level; the rest N/A. |
| **PR** PRESENTATION | 1 | 4 | 0 | No presentation layer of its own. |
| **FB** FEEDBACK | 1 | 0 | 0 | Cross-repo feedback via the `pending-feedback/` outbox + supervisor relay (FB-05). |
| **DEP** DEPENDENCIES | 0 | 11 | 0 | Thin-adapter dependency surface; DEP family premises consumer/owner-shaped. |
| **Total** | **165** | **94** | **4** | of **263** MUSTs (corpus v2.25); 0 declined. |

---

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

No rule is **declined** (🚫 = 0). The open items are **documented deferrals**,
each carrying a rationale per
[principles/non-adoption-is-valid.md](https://codeberg.org/magogi-admin/mgf-standard/src/branch/main/docs/standards/principles/non-adoption-is-valid.md):

| Item | Kind | Rationale |
|---|---|---|
| **WF-11** (MUST) | Fleet-infra deferral — not owned | CI install-from-lock (`uv sync --frozen`). `.woodpecker.yml` is a generated, drift-checked template artifact (WF-13); a unilateral edit breaks federation template-parity, and fleet adoption is gated on the devpi backfill gap. Tracked at **mgf-standard** (WF-13 template uv-migration). **HARD deferral this pass** (mirrors the mgf-common WF-11 treatment). |
| **WF-09** (MUST) | Fleet-infra deferral — not owned | Same root cause as WF-11 (unpinned `pip-audit`/`httpx2`, lock bypassed) — closed by the same template uv-migration. |
| **PKG-01** (SHOULD) | Fleet-governance deferral — not owned | The standard's own `PACKAGE_DISTRIBUTION.md` lags the shipped devpi cutover; reconciling the L1 doc lives in **mgf-standard**, not this sibling. The repo-side PKG-01 (resolve every mgf-* from the Codeberg/devpi index) is **met** — see §Z PKG-01 ✅. **HARD deferral this pass.** |
| **TS-28** (MUST) | L1-deferrable | Per-test perf attribution (TestRecord schema ≥1.1.0 + `perf_span` + perf-summary). L2-binding; L1 MAY defer. Needs perf-infra beyond this uplift. |
| **LG-13** (MUST) | Open, minor, owned | The rate-limit-denial INFO has no sampling guard. Fix = wrap it in an `mgf.common` `SampleFilter`/`RateLimitFilter`; small hygiene item, queued. |

---

## §4 Audit log

| Date | Auditor | Trigger | Findings |
|---|---|---|---|
| 2026-06-25 | Item 1b-1 L3 uplift (stage 1 code + stage 2 features) + CFM-03 re-audit | maintainer directive — close the L3-audit gaps + re-baseline the ledger | **165 ✅ / 4 ⏳ / 94 N/A / 0 🚫** of **263** (re-baselined to corpus v2.25; +AG-17/18/19/WF-13). Closed the 16 MUST + 2 SHOULD L3-blocking gaps: SC-08 body cap, LG-15 audit event + EVENTS.md, SC §12 threat model, AP-14 closed-box contract, DOC-12/17 slug sweep, CFM-02/OP-04 README↔ledger, the PF-01/03/05/07 + TS-14 perf-baseline harness, PF-06 annotations. Flipped the stale ⏳ rows (PF/TS-14/AP-14) to ✅ with evidence; reframed WF-09/WF-11 as fleet-infra deferrals (WF-13 template). Residual ⏳: LG-13 (minor), TS-28 (L1-deferrable), WF-09/WF-11 (not owned). Standing: **at the L2 bar on every owned surface modulo LG-13**. |
| 2026-06-07 | depth audit (16-family fan-out + adversarial refute; 29 agents, ~1.7M tok) | first per-rule depth audit | **86 ✅ / 65 ⏳ / 67 N/A** of 218 (DOC-15/DOC-19 corrected post-audit); level corrected L2→below-L2. Biggest clusters: CI-publish-flow (no CI publish pipeline; manual releases; residual pypi.org), SCOPE policy absent (SP-01..06), docs/public-API (no PUBLIC_API.json; DOC-08/09/15/18/19). Audit only — no code changed. |
| 2026-05-18 | initial audit (v2.8 standards bump) | DOC-15 + CFM-01..04 introduction; this tracker created | scaffolded; first depth audit to follow |

---

## §5 Cross-references

- [mgf-standard/docs/standards/](https://codeberg.org/magogi-admin/mgf-standard/src/branch/main/docs/standards/)
  — the standards source-of-truth.
- [`docs/inprogress/RELIABILITY_AND_ACCURACY.md`](RELIABILITY_AND_ACCURACY.md)
  — falsifiable per-rule evidence on the RA-* surface
  (where applicable).
- [`docs/inprogress/SECURITY_HARDENING.md`](SECURITY_HARDENING.md)
  — falsifiable per-rule evidence on the SC-* surface.
- [`docs/inprogress/PERFORMANCE_AND_CAPACITY.md`](PERFORMANCE_AND_CAPACITY.md)
  — falsifiable per-rule evidence on the perf-shape surface.
- [`../../FEEDBACK.md`](../../FEEDBACK.md) — papers filed
  against this sibling.
- [mgf-standard/docs/standards/principles/non-adoption-is-valid.md](https://codeberg.org/magogi-admin/mgf-standard/src/branch/main/docs/standards/principles/non-adoption-is-valid.md)
  — ASPIRE-04 / CFM-04 documented-non-adoption principle.
- [Template this file was scaffolded from](https://codeberg.org/magogi-admin/mgf-common/src/branch/main/docs/recipes/mgf-standards-conformance-md-template.md).

---

---

## §Z Per-MUST evidenced audit (all 263 MUSTs, corpus v2.25 — CFM-03 completeness)

> **2026-06-25 re-audit.** Every current MUST gets an evidence-based verdict —
> re-baselined to corpus v2.25 (263 MUSTs across 22 families; +AG-17/18/19,
> +WF-13 over the prior 259). Each ✅ cites `file:line` and survived the
> 2026-06-07 adversarial refute pass; rows touched by the Item 1b-1 uplift
> carry a `FIXED <date>` suffix. **Mix: 165 ✅ · 4 ⏳ · 94 N/A · 0 🚫.** The 4
> ⏳ are LG-13 (minor, owned), TS-28 (L1-deferrable), and WF-09/WF-11
> (fleet-infra, not owned). ⏳ detail is the per-rule Notes here; clusters +
> deferrals summarised in §1 and §3.

### DESIGN_PRINCIPLES

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| DP-01 | ✅ | .woodpecker.yml:25 ("No import-linter contracts configured → no lint-imports."); no .importlinter / [tool.importlinter] in pyproject.toml (rg found only the dev-dep pin at pyproject.toml:85) | Rule says "CI lints this". mgf-fastapi declares import-linter as a dev dep but ships ZERO contracts and CI never runs lint-imports. The library is a thin adapter with no UI/core split (the §14 leaf caveat means a layered contract may be N/A — FIXED 2026-06-07: .importlinter (independence + UI-forbid contracts) added + lint-imports & no-placeholder grep wired into CI |
| DP-02 | N/A | security.py:226 AuthVerifier(Protocol); _rate_limit.py:15,121 (Redis-backed swap described); db.py:37 imports mgf.sqlalchemy create_engine/create_sessionmaker behind extras; webhooks/_verifi | mgf-fastapi performs no first-party third-party I/O of its own: it adapts FastAPI/Starlette (the framework itself, not a swappable provider), and any real I/O (DB, broker, auth) is supplied by the consumer through Protocols/callables or by  |
| DP-03 | ✅ | webhooks/_verifier.py:38 @dataclass(frozen=True, slots=True) WebhookHeaderSchema (the only domain type crossing a boundary); _rate_limit.py:91 _BucketState is @dataclass (mutable) but is int | The single domain value type passed across boundaries is frozen+slots. The one mutable dataclass is process-internal mutable state guarded by a lock, not an identity object crossing modules, so it is the documented exception, not a violatio |
| DP-04 | ✅ | pyproject.toml:195 strict=true, :200-204 disallow_untyped_defs/disallow_incomplete_defs/no_implicit_optional; .woodpecker.yml:39 runs `mypy` as a gate; every public def in src carries annota | mypy --strict is configured and CI-gated; public signatures are annotated; Any appears only at FastAPI/Starlette boundaries with inline rationale. The one untyped def is the nested FastAPI lifespan with an explicit `# type: ignore[no-untype |
| DP-05 | ✅ | tests/ mirrors src for unit files (test_db/_dependencies/_exceptions/_lifespan/_logs/_rate_limit/_request_id/_security/_testing/_webhooks) but src ships webhooks/_verifier.py + webhooks/_req | The no-real-daemon / no-network / no-credentials half of the rule is met (loopback-only, in-process). But the "tests MUST mirror src 1:1" half is only loosely honored: the webhooks subpackage is not mirrored as a tests subpackage and there — FIXED 2026-06-07: mirror-enforcement test (tests/test_mirror.py) — webhooks subpackage allow-listed (covered by test_webhooks) |
| DP-06 | ✅ | Security: security.py:170-177 startup audit log for trust_proxy footgun, :377-384 control-char token rejection, _request_id.py:52-80 CR/LF/length sanitisation; Reliability: db.py:159-178 RA- | All four concerns are visibly first-class in code, dedicated trackers (docs/inprogress/SECURITY_HARDENING.md, RELIABILITY_AND_ACCURACY.md, PERFORMANCE_AND_CAPACITY.md), and perf pins. The per-PR-description clause is process-only and not au |
| DP-07 | ✅ | Every public class/function carries an intent+invariant docstring: security.py:114 IpAllowlist, :245 bearer_session, webhooks/_verifier.py:72 HmacWebhookVerifier.verify (:133), _exceptions.p | Docstrings explain intent and invariants (footgun warnings, replay-window semantics, redaction guarantees) rather than paraphrasing code; no "returns the result"-style filler observed. Conformant. |
| DP-08 | ✅ | No docs/ARCHITECTURE.md exists (find for *architecture* returned nothing in-repo; only docs/CLAUDE.md:42 references the standard); the §5 replaceability table is absent. Swap stories exist o | DP-08 is SHOULD, and the per-component swap recipe is partially present in module docstrings (the TokenBucket→Redis swap, the SQLAlchemy/livepush extras boundary). But there is no ARCHITECTURE.md and no §5 replaceability table aggregating t — FIXED 2026-06-07: docs/ARCHITECTURE.md replaceability table (SHOULD) |
| DP-09 | N/A | No CancellationToken/ProgressReporter usage anywhere in src (rg for Cancellation* empty); the only long-running surfaces are request-scoped (middleware dispatch) or test-only (testing.py run | The CancellationToken model targets long-running app/CLI/GUI operations. mgf-fastapi has no such first-party long-running op: request handlers are bounded by the ASGI server and async cancellation is the framework's job. No code path here n |
| DP-10 | N/A | (audit-omitted) | not returned by the 2026-06-07 fan-out auditor — re-verify next pass (DP-10: queryable active backend) — RE-VERIFIED 2026-06-07 N/A: mgf-fastapi's backends (rate-limit TokenBucket, DB engine) are construction-time-explicit; there is no runtime-SELECTED backend needing a queryable 'which am I using' surface — DP-10's premise doesn't bind this adapter shape |
| DP-11 | ✅ | logs.py:164-168 _spawn: `loop.create_task(self.publish_record(record))` — return value discarded, not stored in any set, no add_done_callback. Contrast testing.py:102 `serve_task = asyncio.c | Genuine defect: the fire-and-forget broadcast task at logs.py:168 has no owner — if the event loop GC-collects it mid-publish the record silently vanishes and any exception inside publish_record is lost. The standard explicitly calls bare c — FIXED 2026-06-07 (0.6.9): LogBroadcaster retains in-flight publish tasks (self._tasks set + add_done_callback) — no GC-mid-publish drop |
| DP-12 | ✅ | DTZ enabled (pyproject.toml:158); no datetime/utcnow/naive-now usage in src (rg found none); the only time sources are time.monotonic (_rate_limit.py:160, correct for rate-limit elapsed) and | No naive datetime crosses any boundary; the library traffics in monotonic durations and epoch-second integers (from the webhook wire), not wall-clock datetimes, and DTZ lint is on. The UTC-at-boundary concern doesn't arise because no dateti |
| DP-13 | ✅ | testing.py:41 socket via `with`, :125-130 serve_task awaited in finally + suppress(CancelledError); db.py:100 `async with sessionmaker() as session`, :156-169 try/finally engine.dispose(); _ | Every owned external resource (socket, async session, engine pool, contextvar token, lock, server task) is released via with / async with / try-finally; no reliance on GC. filterwarnings=error gives the ResourceWarning backstop the rule wan |

### ERROR_HANDLING

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| EH-01 | ✅ | src/mgf/fastapi/exceptions.py:25 (HmacVerificationError(HttpUnauthorizedError)); src/mgf/fastapi/security.py:184,308,322 (HttpForbiddenError/HttpUnauthorizedError); src/mgf/fastapi/db.py:93  | Every domain raise crossing an internal API is an mgf.common AppError subclass; only construction-time ValueError (TokenBucket/CIDR/prefix) is bare, which is the conventional Python arg-validation seam, not a cross-boundary raise. |
| EH-02 | ✅ | src/mgf/fastapi/security.py:330-339 (verifier.verify_session → HttpUnauthorizedError); security.py:158-160,188-190 (ValueError→ValueError/HttpForbiddenError at CIDR/IP boundary); webhooks/_v | Third-party/stdlib exceptions are translated to typed AppError at the boundary that owns the call (auth verifier, ipaddress parse, optional-dep import). |
| EH-03 | ✅ | src/mgf/fastapi/security.py:160,190,339 (`raise ... from exc`); webhooks/_verifier.py:167 (`from exc`); db.py:44 (`from exc`) | Every translated re-raise chains the original cause with `from exc`; no `raise NewError(str(e))` without chaining found. |
| EH-04 | N/A | pyproject.toml has no [project.scripts]/[project.gui-scripts]; src/mgf/fastapi/_exceptions.py:74 ExceptionTranslationMiddleware is the ASGI top-level handler; _lifespan.py:61 delegates proce | Library has no CLI/process/Qt/asyncio entry point it owns; the ASGI-boundary handler (translation middleware) IS provided, and process/thread excepthooks belong to mgf-common's bootstrap which setup_lifespan wires in. No entry point for thi |
| EH-05 | N/A | no _state_dir/report_now/crash_reporter anywhere in src/ (rg crash → only docstrings); _lifespan.py:56 notes crash-reporter excepthook is restored by mgf.common AppContext on shutdown | Crash-report-to-state-dir is owned by mgf.common.observability; a stateless integration library with no entry point and no app state dir does not (and should not) ship its own reporter. |
| EH-06 | N/A | no QThread/CoreWorker/PySide/Signal in src/ (rg found none); project is an ASGI/FastAPI library | No GUI surface — the GUI-worker error-pipeline rule has no applicable shape. |
| EH-07 | ✅ | src/mgf/fastapi/db.py:170 (finally-path engine.dispose, logs WARNING); logs.py:70 (logging.Handler.emit → handleError, the documented handler-never-raises path), logs.py:142,147 (publish_rec | Every `except Exception` is either a cleanup/finally path, a log-and-continue best-effort observer with documented invariant, or a typed translation+re-raise — all within the EH-07 carve-outs. |
| EH-08 | ✅ | src/mgf/fastapi/exceptions.py:25-43 — HmacVerificationError defines only `http_status`, no __init__ and no machine-readable field (.event_id/.reason); webhooks/_verifier.py:153-209 raises it | The one sibling-owned typed exception carries no machine-readable constructor fields — failures (missing-header vs replay-miss vs sig-mismatch) are distinguishable only by parsing the message string. Inherited mgf-common leaves (ResourceNot — FIXED 2026-06-07 (0.6.9): HmacVerificationError.reason machine-readable field (missing_header/bad_timestamp/replay_window/signature_mismatch) + per-mode test |
| EH-09 | ✅ | Good hints exist: webhooks/_request.py:82-89 and db.py:88-95 end in actionable steps. But no tests/unit/test_exception_actionable_hints.py exists (ls tests/unit \| rg hint → empty), and many | EH-09 + §9 require every actionable hint be pinned by a dedicated substring test so it cannot silently rot; that guard file is absent. Several 401/403 messages also intentionally omit a next step (correct for security non-disclosure), but t — FIXED 2026-06-07: tests/unit/test_exception_hints.py pins the auth/allowlist hint substrings |
| EH-10 | ✅ | src/mgf/fastapi/logs.py:137-151 publish_record (fail-open per-step, logs/drops, never raises) + docstring "Fail-open on every step"; tests/unit/test_logs.py:123 test_publish_is_fail_open_whe | The one best-effort observer (log broadcaster draining to a broker) follows never-raises, documents the invariant, and has a test injecting a raising broker — meeting EH-10. Minor: docstring says 'fail-open' rather than the literal phrase ' — FIXED 2026-06-07 (0.6.9): publish_record docstring now states the literal 'Never raises.' (EH-10 §7) |
| EH-11 | N/A | rg retry\|backoff\|jitter in src/ → only _rate_limit.py Retry-After response header (server-side 429 throttle, not a client retry loop); no retry() helper, no loop that re-invokes a failing  | Library issues no outbound retried calls to external systems; the only 'retry' surface is emitting a Retry-After hint to clients (inbound rate limiting), which is not the EH-11 client-retry shape. Nothing to apply backoff to. |
| EH-12 | N/A | no write/send-with-multi-attempt-cost operation in src/; webhooks/_verifier.py:206 returns (event_id, ts) for caller dedupe but performs no side-effecting write itself; db.py only yields a s | The library performs no idempotency-bearing external write/send of its own; it surfaces the webhook delivery id so the CONSUMER can dedupe, but owns no at-least-once operation requiring an idempotency key. |
| EH-13 | N/A | rg circuit\|breaker\|bulkhead in src/ → no matches; no outbound external-system call that could amplify an upstream outage (auth verifier in security.py:325 is consumer-supplied and called o | No load-amplifying external call exists in this library — there is no failing-and-retried upstream call for a breaker to bound. Per EH-13 the absence of amplification (single call, no retry) is itself the documented bound; nothing to guard. |

### LOGGING

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| LG-01 | ✅ | src/mgf/fastapi/testing.py:31, db.py:59, security.py:62, _rate_limit.py:82 — all `logging.getLogger(__name__)` | Every module-level logger uses getLogger(__name__); rg found zero bare `logging.info/...` calls in src/. The two `logging.getLogger()` (no-arg, root) calls in logs.py:121/126 are deliberate root-handler attach/detach for the broadcast handl |
| LG-02 | ✅ | src/mgf/fastapi/_lifespan.py:61 `with bootstrap(...)` inside the lifespan async CM | As an integration library mgf-fastapi has no entry point of its own; its single configuration point is the FastAPI lifespan, which delegates logging setup to mgf.common.bootstrap (which owns setup_logging + the JSON-when-not-a-TTY contract  |
| LG-03 | N/A | mgf.common.observability exposes LogRecord/log_record/logging_setup; src/ adds no formatter | Trace_id/span_id injection is the formatter's job and the formatter lives in mgf-common (imported, not reimplemented per the standard's §note). mgf-fastapi neither formats records nor strips trace context — its logs.py reuses LogRecord.from |
| LG-04 | ✅ | src/mgf/fastapi/logs.py:102,134 `Redactor.default()` + `self._redactor.redact(wire)` before publish; tests/unit/test_logs.py:103 asserts `'hunter2' not in text` | The SSE log-stream surface (logs.py) is the one place mgf-fastapi emits records off-box; it mandatorily runs the configurable mgf-common Redactor before publish, with a passing unit test proving a `password` extra is scrubbed. Exception mid |
| LG-05 | ✅ | src/mgf/fastapi/db.py:177 `exc_info=True` in the dispose-failure warning; only LOG.error site security.py:318 is a no-exception config branch | The single except-handler that logs an in-flight exception (db.dispose) passes exc_info=True. The one LOG.error (security.py:318, missing verifier on app.state) fires outside any except block — a deployment-config assertion, not an exceptio |
| LG-06 | ✅ | src/mgf/fastapi/logs.py module docstring + LogBroadcaster→livepush Broker; standards matrix line 46 maps `fastapi` kind to LG-06 stdout-aggregator branch | For a fastapi-kind library the cloud-native stdout-capture branch of LG-06 applies and is satisfied by the consumer's deploy + mgf-common's setup_logging; mgf-fastapi additionally ships an opt-in remote sink (the auth-gated SSE /logs/stream |
| LG-07 | N/A | no PySide6/Qt/gui imports anywhere in src/; project kind = fastapi | LG-07 is gui-app-only (matrix line 44). mgf-fastapi is a web-integration library with no GUI surface. The /logs/stream web viewer is the web analogue but LG-07's QDialog log-viewer requirement does not apply. |
| LG-08 | ✅ | rg for `.{info\|warning\|error\|...}(f"` in src/ returned NONE; e.g. security.py:189 `LOG.warning("... %r ...", client_host)` | Every log call site uses lazy %-style placeholders or static strings; no f-string log messages exist in the source tree. |
| LG-09 | ✅ | GOOD: db.py:173 + _rate_limit.py:268 use `extra={...}`. GAP: security.py:171,183,189,198 concatenate context into the message (`%r`, `%s`, network count) with no extra={} | Structured fields are correctly promoted via extra={} in db and rate-limit, but the four IpAllowlist WARNING records bury queryable facts (client_host, network count, trust_proxy) in the message string instead of extra={} — the exact anti-p — FIXED 2026-06-07 (0.6.10): IpAllowlist warnings now promote queryable facts (client_host, networks, reason) via extra={} |
| LG-10 | N/A | no systemd/JournalHandler/daemon entry point in src/; project kind = fastapi (not service) | LG-10 is service-kind only (matrix line 45). mgf-fastapi is an ASGI integration library; it has no headless systemd-timer/daemon entry point. Journald wiring, where wanted, is mgf-common's setup_logging concern. |
| LG-11 | ✅ | no `operation` extra field anywhere in src/ (rg "operation" finds none); db.setup_db / engine.dispose cross a storage boundary | LG-11 requires stable `operation` start/end records for functions crossing a process/network/storage boundary. setup_db (DB engine creation/dispose) and the webhook HMAC verify path cross such boundaries but emit no operation start/end reco — FIXED 2026-06-07 (0.6.10): setup_db emits stable operation start/complete/teardown records (operation=db.setup) |
| LG-12 | ✅ | all log sites pass raw values/lazy %-args (security.py:189, _rate_limit.py:268 round()); heavy work (serialize+redact) deferred to handler in logs.py:128-135 publish_record, off the call sit | Emission is cheap: call sites pass plain values with lazy formatting; the broadcast handler does serialization/redaction asynchronously on the event loop (loop.call_soon_threadsafe → create_task), not at the LOG.* call site. No evidence of  |
| LG-13 | ⏳ | _rate_limit.py:266 logs INFO on EVERY denied request inside dispatch (per-request middleware) with no sampling/rate-limit on the log itself | The rate-limit-exceeded INFO fires once per denied request; under a flood (the exact runaway condition LG-13 targets) this log site itself floods every sink with one record per rejected request. No sampling/rate-limiting guard on the log em |
| LG-14 | ✅ | src/mgf/fastapi/_request_id.py:30-36,114,118 set_request_id/reset_request_id via mgf.common contextvar; logs.py:54 `_in_broadcast` is a contextvars.ContextVar | Correlation context (request_id) propagates through the shared mgf.common contextvar (set in middleware dispatch, reset in finally — PEP 567 safe across async children), never thread-local or function args. The broadcast re-entrancy guard i |
| LG-15 | ✅ | events present: _rate_limit.py:269 `mgf.fastapi.rate_limit_exceeded` vs db.py:174 `mgf_fastapi.db.dispose_failed` — inconsistent namespace (dot vs underscore); no docs/reference/EVENTS.md in | Two events carry the `event` field but (a) there is no docs/reference/EVENTS.md catalogue — LG-15 says an event without a registry entry is a defect; (b) namespacing is inconsistent (`mgf.fastapi.*` vs `mgf_fastapi.*`) and `rate_limit_excee — FIXED 2026-06-07: docs/reference/EVENTS.md catalogue (both events registered) + bucket README — EXTENDED 2026-06-25 (stage 1): `verify_request` now emits `event=mgf.fastapi.webhook.verification_failed`, `reason=<exc.reason>`, `audit=True` on EVERY rejection (forged/replayed/missing-header/over-cap/consumed) then re-raises; `mgf_fastapi.db.dispose_failed` renamed → `mgf.fastapi.db.dispose_failed` (dot-namespace; documented one-time alert-target rename); EVENTS.md now registers the full catalogue incl. the 6 prior-missing rows (ip_allowlist.trust_proxy_enabled/denied, auth.misconfigured/rejected, db.setup/teardown). +caplog test. |
| LG-16 | ✅ | security.py:171,183,189,198 WARNING records have WHAT but no WHY-as-extra and no hint field; contrast db.py:171 (has extra context) | WARNING+ records must answer WHAT/WHY(extra={})/HOW(hint). The IpAllowlist denial warnings give a WHAT (past-tense-ish) but put WHY in the message string (no extra={} fields) and carry no `hint`/remediation field, so an operator cannot quer — FIXED 2026-06-07 (0.6.10): WARNING+ records carry WHY/structured fields in extra={} (event+reason), not just the message string |
| LG-17 | ✅ | rg for `fingerprint` in src/ finds none; only ERROR-with-exception site is db.py:177 (exc_info=True) which lacks a fingerprint extra | LG-17 is a SHOULD: the default fingerprint is computed by the formatter when exc_info is set, and that formatter lives in mgf-common (so db.py:177's exc_info=True gets an automatic fingerprint downstream — arguably covered). But mgf-fastapi — VERIFIED 2026-06-07: exception-logging sites use exc_info=True → mgf-common's formatter computes the error fingerprint (SHOULD satisfied) |

### CONFIGURATION

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| CF-01 | N/A | src/mgf/fastapi/_dependencies.py:36,63 (imports MgfSettings from mgf.common, never defines own BaseSettings); rg over src/ finds zero BaseSettings/BaseAppSettings subclasses | Applies-to=consumer-owned-config. mgf-fastapi owns no config schema; it consumes mgf-common's MgfSettings via DI (get_settings) and never reads env/config files directly. |
| CF-02 | N/A | src/mgf/fastapi/_lifespan.py:61 bootstrap(settings=settings); _dependencies.py:69 get_settings returns ctx.settings — no source-precedence logic exists here | Marked Applies-to=all, but precedence is owned by mgf-common's BaseAppSettings.settings_customise_sources (§0b CF-02). mgf-fastapi has no layered-source loader to order; it passes a pre-built Settings through. |
| CF-03 | N/A | No schema definition in src/ (no version field, no _migrate); MgfSettings.version lives in mgf-common per §0b CF-03 | Applies-to=consumer-owned-config. No owned schema, so no version field or migration chain to carry. |
| CF-04 | N/A | rg for write_text\|tmp.replace\|atomic_write\|0o600 over src/ returns NO WRITES | Applies-to=consumer-owned-config. mgf-fastapi never writes a config/state file to disk; nothing to make atomic. |
| CF-05 | ✅ | src/mgf/fastapi/webhooks/_request.py:43 secret comes from settings.webhook_secret (consumer-provided); webhooks/_verifier.py:127 secret is a constructor arg; security.py bearer token read fr | Applies-to=all. mgf-fastapi never persists secrets to a config file; secret material is injected at construction (verifier) or read from the live request — no plaintext-in-config-file leak. |
| CF-06 | N/A | No owned schema/model_config in src/; extra=forbid is provided by mgf-common's settings_config() per §0b CF-06 | Applies-to=consumer-owned-config. No config file is parsed here, so there is no unknown-key surface to reject. |
| CF-07 | N/A | No satellite YAML loaders in src/ (rg finds no yaml., no from_yaml_file/save_to_file) | Applies-to=consumer-owned-config. mgf-fastapi ships no sub-config files (hooks/schedules/profiles). |
| CF-08 | ✅ | src/mgf/fastapi/_lifespan.py:61-66 bootstrap once in lifespan, stash AppContext on app.state.mgf_context; _dependencies.py:39-69 get_app_context/get_settings read injected state, no module-g | Marked consumer-owned-config but the load-once + constructor/DI-injection pattern IS implemented: settings loaded once at the ASGI entry point and passed down via FastAPI Depends, no global singleton. |
| CF-09 | N/A | rg for click\|argparse\|--config\|sys.argv over src/ returns NO CLI; this is an ASGI integration library | Web/ASGI shape with no consumer-facing argv (standard §6 Web-shape note + §0a explicitly mark CF-09 N/A for FastAPI/web). No --config flag applies. |
| CF-10 | ✅ | tests/unit/test_lifespan.py:15 and test_dependencies.py:24,70,118 wire setup_lifespan(app_name="test-app", app_version="0.0.1") explicitly; rg for os.environ/monkeypatch.setenv in tests/ ret | Applies-to=all. Tests supply settings/identity via explicit constructor args (setup_lifespan params) rather than relying on developer env/config; no env leakage observed. |
| CF-11 | N/A | No owned schema fields in src/; rg finds no Field(alias=)/validation_alias/AliasChoices | Applies-to=consumer-owned-config. mgf-fastapi defines no config fields, so there is nothing to rename-with-alias. |
| CF-12 | N/A | No config backend variants modelled in src/; rg finds no discriminator=/Annotated union over config; verifier/security are single-shape (HmacWebhookVerifier, bearer_session) with no kind dis | Marked Applies-to=all, but mgf-fastapi owns no config field with mutually-exclusive backend variants — no discriminated-union surface exists. |

### SECURITY_STANDARD

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| SC-01 | ✅ | .woodpecker.yml:77-80 (gitleaks secret-scan step); .gitleaks.toml; no secrets in src (grep clean) | No secrets in source; gitleaks runs in CI over working tree + history with project allowlist for webhook test fixtures. The webhook secret is passed in by the consumer at construction (_verifier.py:107), never embedded. |
| SC-02 | N/A | grep for subprocess\|shell=True\|os.system in src/ → NONE | mgf-fastapi invokes no subprocesses; it is a pure FastAPI-adapter library with no shell-out surface, so the argv-only rule has no call site to constrain. |
| SC-03 | N/A | grep for yaml.load\|pickle in src/ → NONE | No YAML/JSON-from-untrusted-source loading and no pickle in the library; request bodies are read as bytes for HMAC (webhooks/_request.py:74) and JSON parsing is the consumer's responsibility via FastAPI/pydantic. |
| SC-04 | N/A | grep for open(\|.write\|chmod\|0o600\|atomic in src/ → NONE | The library writes no private-data files; it owns no config/vault/state/log files on disk. State (rate-limit buckets, request-id) is in-memory only. |
| SC-05 | ✅ | webhooks/_verifier.py:188 hmac.new(secret, payload, hashlib.sha256); :206 hmac.compare_digest; :189 base64; grep md5\|sha1\|random. in src/ → NONE | Crypto comes only from stdlib hmac/hashlib (constant-time compare); no DIY crypto, no weak primitives, no bare random.* anywhere in src. |
| SC-06 | N/A | grep verify=False\|CERT_NONE\|AutoAddPolicy in src/ → NONE; testing.py:64 httpx.AsyncClient(base_url=...) hits the local test server only | The library makes no production outbound TLS connections; the only HTTP client is in testing.run_test_app against a loopback uvicorn instance, and it sets no verify=False. No SSH/paramiko surface. |
| SC-07 | ✅ | testing.py:64 httpx.AsyncClient(base_url=base_url) created with no timeout= (only startup polling at testing.py:104-112 is bounded) | The test-server helper's httpx.AsyncClient is constructed without a request timeout=; minor (test-harness scope, loopback only) but SC-07 says every external request carries a finite timeout. Verifier replay window (_verifier.py:178) is not — FIXED 2026-06-07 (0.6.9): test-server httpx.AsyncClient now constructed with timeout=30.0 |
| SC-08 | ✅ | _request_id.py:52-80 _sanitise_request_id (length cap 128 + control-char reject); security.py:377-384 _is_clean_token (C0/DEL reject); _verifier.py:120-126 signature_prefix validation, :162- | Untrusted boundary inputs (inbound X-Request-Id, bearer token, cookie, webhook headers/timestamp) are length-bounded, control-char-rejected and type-parsed at the boundary before use. Body shape validation past HMAC is the consumer's pydant — FIXED 2026-06-25 (stage 1): webhooks `verify_request` now enforces a configurable `max_body_bytes` cap (default 1 MiB) BEFORE buffering/HMAC — first on a declared Content-Length, then mid-stream via `_read_body_capped()` (aborts once accumulated bytes cross the cap); new `reason=body_too_large`; non-positive cap raises ValueError; +4 SC-08 tests. Closes the pre-auth memory/CPU DoS. |
| SC-09 | ✅ | _exceptions.py:140-144 Redactor.default(); :160-168 redact_string over exc.args before response factory; tests/unit/test_exceptions.py:301-319 asserts bearer token scrubbed; security.py:335- | Sensitive material is redacted before the response sink via mgf-common's Redactor (default-on), and auth-failure logs emit only exception type names, never the token. Inherits the mgf-common log-record redactor (SH-INV-7). |
| SC-10 | ✅ | uv.lock committed (235KB) with sha256 hashes (uv.lock:13,15,22 sdist/wheels hash=sha256:...); .woodpecker.yml:66-73 vuln-scan step runs pip-audit on every push/PR | Hash-pinned uv.lock is committed and CI runs pip-audit on every push/PR/manual event, satisfying the lockfile + scanner gate. |
| SC-11 | ✅ | _exceptions.py:160-168 scrubs exc.args via Redactor before building response; security.py:326-339 bearer_session re-raises HttpUnauthorizedError('invalid or expired session') without chainin | Exception args are scrubbed at the translation seam before egress and the auth seam emits a uniform sanitized message; the verifier's original exception stays in logs (type-name only), not the response. Inherits mgf-common crash-reporter sc |
| SC-12 | ✅ | SECURITY.md:1 '# Security Policy'; :19-23 supported versions; :29-40 private reporting (email + Codeberg advisory); :54-66 timeline + 90-day embargo | Repository-root SECURITY.md exists with supported versions, security contact, embargo and disclosure timeline per the §11.1 template. (Minor staleness: line 69 claims 'PyPI auto-yanks' which contradicts the standard's no-auto-yank note, but |
| SC-13 | N/A | grep for KEY = b'\|cryptography\|key derivation in src/ → NONE; _verifier.py:107 secret is a constructor parameter | The library stores no cryptographic keys at rest; the HMAC secret is passed in by the consumer at construction time, never embedded in source, build artefacts, or env defaults. |
| SC-14 | ✅ | security.py:324-339 bearer_session translates every verifier failure (missing/expired/wrong/IdP-500) uniformly to HttpUnauthorizedError; :313-322 missing-verifier also returns 401; _rate_lim | Auth failures are non-enumerating: uniform 401 for every distinguishable failure mode, no 'user does not exist' vs 'wrong password' split (SH-INV-4). Rate limiting is available via the auto-refilling TokenBucket; no permanent hard lockout e |
| SC-15 | N/A | grep for open(\|Path(\|resolve in src/ → NONE (only db.py docstring mentions paths) | No filesystem path is constructed from external input anywhere in the library; there is no file read/write surface, so directory-traversal protection has no applicable call site. |
| SC-16 | N/A | grep sudo\|setuid\|chmod\|privileged\|capability in src/ → NONE | The library performs no privileged operations and ships no systemd unit/deploy surface; privilege confinement is the consuming application's deployment concern. |
| SC-17 | ✅ | security.py:171-177 IpAllowlist trust_proxy WARNING (no audit=true flag); :335-338 bearer_session logs INFO without audit=true; _rate_limit.py:266-273 INFO with event= but no audit=true; gre | Security-relevant actions (auth attempt success/failure, IP-allowlist deny, trust_proxy construction) emit structured INFO/WARNING records but NONE carry the audit=true field the rule mandates for clean SIEM filtering. The SECURITY_HARDENIN — FIXED 2026-06-07 (0.6.10): security-relevant records (allowlist deny, trust_proxy, auth misconfig/reject) now carry an audit marker + structured event |
| SC-18 | ✅ | build-backend hatchling.build (pyproject.toml:124), uv.lock committed; but no SBOM/build-metadata recording — CHANGELOG.md grep for hatchling\|Python version\|SBOM\|cyclonedx → none; SECURIT | Releases are buildable from a tagged commit via hatchling + committed lockfile (the reproducible-from-source core holds), but the project ships no SBOM and records no build metadata (Python/hatchling/OS version) in release notes; the projec — FIXED 2026-06-07: tag-driven Codeberg publish pipeline added (.woodpecker.yml build→sbom→tag-coherence→smoke→publish→verify-codeberg + scripts/verify_codeberg.py), mirroring mgf-common |

### TESTING

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| TS-01 | ✅ | tests/unit/ vs src/mgf/fastapi/; no tests/test_mirror.py or allow-list; .woodpecker.yml lint step | tests/unit mirrors src well in practice (every module has a test file: test_logs/test_rate_limit/test_security/test_dependencies/test_testing/test_db/test_request_id/test_exceptions/test_lifespan + webhooks in test_webhooks), but TS-01's se — FIXED 2026-06-07: tests/test_mirror.py + MIRROR_ALLOWLIST.txt (mirror-enforcement lint) |
| TS-02 | ✅ | tests/unit/test_webhooks.py:431 (MagicMock for Request), tests/unit/test_security.py:212 (_FakeVerifier), tests/unit/test_db.py:86 (sqlite in-memory), tests/unit/test_logs.py:124 (MockBroker | Unit suite uses FastAPI TestClient / in-memory sqlite / fakes + mocks; no production creds, no real network, no third-party daemon. Ran green offline (167 passed). |
| TS-03 | ✅ | tests/unit/test_logs.py:25 (autouse fixture); tests/unit/test_security.py:436 uses caplog; testing.py tests use socket-bind + clean async-context teardown; asyncio_default_fixture_loop_scope | Tests isolate per-test: autouse fixture in test_logs, caplog scoping, per-test event loops, and async context managers that tear down servers (test_testing.py:69 verifies port release). No shared module-level mutable state observed. |
| TS-04 | ✅ | pyproject.toml:253 filterwarnings = ["error", "ignore::starlette.exceptions.StarletteDeprecationWarning"] | filterwarnings escalates to error globally; the single ignore is a documented third-party (starlette) deprecation, the allowed per-rule relaxation form. |
| TS-05 | ✅ | pyproject.toml:218-219 addopts includes --strict-markers and --strict-config | Both flags set in addopts. |
| TS-06 | ✅ | pyproject.toml:281 fail_under = 80; .woodpecker.yml test-3.12 runs --cov; measured run: TOTAL 94.61% (well above gate) | Coverage gate at 80% present and CI-wired on the 3.12 job; live run measured 94.6%. The PR-changed-lines ≥90% sub-clause is not separately enforced by a tool, but the project-wide floor + gate are met. |
| TS-07 | ✅ | tests/unit/test_webhooks.py:142-221 (HmacVerificationError message/inheritance pins); src/mgf/fastapi/exceptions.py:25 is the only sibling-owned typed exception | The one project-owned typed exception (HmacVerificationError) has message-format assertions for each failure mode and a test pinning its HttpUnauthorizedError parentage + http_status=401 (test_error_inherits_http_unauthorized). |
| TS-08 | ✅ | tests/unit/test_exceptions.py:55-290 (every mapped AppError → status); tests/unit/test_exceptions_cancellation.py:86 (translates AppError) + :59 (CancelledError passes through); tests/unit/t | ExceptionTranslationMiddleware is the EH-02 translation seam: tests cover success, each known typed AppError→status, the AppError catch-all (test_app_base_falls_through_to_500), and the http_status fallback for unmapped HttpError leaves. |
| TS-09 | ✅ | tests/unit/test_logs.py:122-129 test_publish_is_fail_open_when_broker_raises injects a raising broker and asserts no exception escapes | The LogBroadcaster.publish_record is the best-effort observer (EH-10); a raising broker is injected and the call must not raise — exactly the TS-09 shape. |
| TS-10 | ✅ | tests/unit/test_security.py:377 (SH-05 control-char), :429 (SH-03 trust_proxy warning); tests/unit/test_exceptions.py:294 (SH-04 redaction); tests/unit/test_request_id.py:74 (SH-01); no nega | Several SH-* security behaviors ARE pinned by positive/negative behavioral tests, which is the substantive intent. But TS-10's mechanical 'forbidden pattern absent in src/' grep-pin shape (e.g. no shell=True) is absent and there is no conso — FIXED 2026-06-07: tests/unit/test_forbidden_patterns.py (grep-pin: shell=True/eval/exec/pickle/yaml.load/weak-hash absent in src/) |
| TS-11 | N/A | no PySide6/pytest-qt dependency in pyproject.toml; mgf-fastapi is a web-integration library | No GUI / Qt workers exist; mgf-fastapi has no desktop surface. GUI-worker rule does not apply. |
| TS-12 | ✅ | pyproject.toml:251 asyncio_mode="strict"; pytest-asyncio>=0.23 in dev deps; tests/unit/test_exceptions_cancellation.py:58-83 cancellation test; tests/unit/test_testing.py:31-137 async tests; | pytest-asyncio configured (strict mode, per-test function-scoped loops); async code is broadly tested INCLUDING an explicit CancelledError-propagation cancellation test (the §7.2 shape). |
| TS-13 | ✅ | no _strategies.py anywhere; rg for hypothesis/@given in tests/ returns zero matches; hypothesis>=6.100 is in dev deps but unused | Gap. The Svix HMAC verifier (webhooks/_verifier.py) is a textbook round-trip/pattern-coverage candidate (sign→verify; the §5.5 'for every valid signed payload, verifier accepts' property), yet only hand-picked example vectors exist. Hypothe — FIXED 2026-06-07: tests/unit/test_webhooks_property.py (Hypothesis sign→verify round-trip + tamper-rejection) + _strategies.py |
| TS-14 | ✅ | tests/perf/test_*.py drive every perf budget through `baselines.assert_no_regression` vs tests/perf/baselines.json (ratio/threshold assertion), not the in-docstring prose baseline | FIXED 2026-06-25 (stage 2): same baseline-capture + ratio-assertion fix as PF-01/PF-03 — perf budgets are now machine-comparable recorded baselines. |
| TS-15 | N/A | no @pytest.mark.smoke real-backend tests; no live/realbackend marker registered; pyproject.toml marker list has no real-backend marker | mgf-fastapi has no real external backend to smoke against (its 'backends' are the consumer's FastAPI app + optional sqlite/livepush, all in-process). The §12 real-backend smoke pattern has no applicable surface; not adopted because nothing  |
| TS-16 | ✅ | tests/perf/test_hmac_verifier_latency.py:112-119 contextual breach message; tests/unit/test_security.py:442-445 SH-03 assert message; tests/unit/test_db.py:71 pytest.raises(match=...) | Assertions carry context messages (perf breach lists each size+budget, security pins explain the SH-rule, match= on raises gives the expected message). Failure output is self-explanatory. |
| TS-17 | ✅ | no conftest.py anywhere under tests/ (find returned none); fixtures are file-local (e.g. tests/unit/test_logs.py:25 autouse, test_webhooks.py:33-54 helpers, test_security.py:51 _build_app) | Every fixture/helper lives in the test file that uses it; no cross-package fixture imports. The conftest hierarchy is trivially honored (no shared fixtures promoted, none imported across packages). |
| TS-18 | ✅ | pyproject.toml markers list includes "slow:" (defensive registration); addopts excludes 'not ... and not slow'; pyproject.toml:222 -m filter | slow marker is registered and excluded from the default suite. No test currently uses it, but the defensive registration TS-19 recommends is present and the exclusion is wired. |
| TS-19 | ✅ | pyproject.toml markers block (smoke/e2e/integration/contract/property/regression/fuzz/concurrency/conformance/use_case/perf/stress/slow) + --strict-markers; markers actually used: smoke (tes | Full federation 13-layer taxonomy registered under [tool.pytest.ini_options].markers, combined with --strict-markers. Layer + orthogonal axes both present including perf and slow. |
| TS-20 | ✅ | tests/ has unit/, smoke/, perf/ only; no tests/integration/ or tests/e2e/ directories (find returned none) | Partial. unit/ and smoke/ canonical dirs exist and smoke applies its marker via pytestmark (test_mtest_self_check.py:35). But integration/ and e2e/ canonical directories are absent — several tests that span middleware+FastAPI+mgf-common boo — FIXED 2026-06-07: canonical tests/integration/ (real middleware integration test) + tests/e2e/ (charter) dirs added |
| TS-21 | ✅ | tests/smoke/test_mtest_self_check.py:35 pytestmark=[smoke]; run.jsonl shows it as tier-0; mgf-fastapi is a published wheel (dist/mgf_fastapi-0.6.6-py3-none-any.whl) | A smoke layer exists. Caveat: the one smoke test pins the supervisor self-check (~32s) rather than the TS-21 canonical 'package imports + every __all__ name reachable + shortest happy path <100ms' shape — so the smoke layer EXISTS but does — FIXED 2026-06-07: tests/smoke/test_imports_smoke.py (imports + every __all__ reachable; fast, tier-0) |
| TS-22 | ✅ | pyproject.toml dev dep mgf-test-supervisor>=0.1.3; markers carry tier annotations (e.g. "smoke: ...; tier-0", "perf: ...; tier-3"); .gates/last-run/run.jsonl records "tier":0 | Tiered runs are supported via the published mgf-test-supervisor sibling (installed dev dep, reference impl of --tier/--budget); every registered marker is annotated with its tier and run.jsonl carries a tier field. The federation tier contr |
| TS-23 | ✅ | .woodpecker.yml test-3.11/3.12/3.13 steps run `pytest --tb=short -q` directly (NOT mgf-test-supervisor); pyproject.toml timeout=30 timeout_method="thread" + faulthandler_timeout=60; .gates/l | The supervisor IS installed and the self-check smoke test + .gates artifacts prove it works LOCALLY, but CI invokes bare pytest with no supervisor wrapping. pytest-timeout (thread dump) + faulthandler give in-process hang/crash capture, so — FIXED 2026-06-07: supervisor wired into CI (.woodpecker supervisor-self-check + tier-0 steps); verified locally self-check PASS + tier-0 passed |
| TS-24 | ✅ | .gates/last-run/run.jsonl present with schema_version:"1.0.0", code/severity/summary/test_id/outcome/duration_ms/tier/markers fields (mirrors TestRecord); but generated by a local 1-test sup | The structured JSONL record format is correct (one JSON object per line, TestRecord-shaped fields, mandatory schema_version present). But it is produced only by ad-hoc local supervisor invocation (run.jsonl contains a single smoke test from — FIXED 2026-06-07: CI now drives mgf-common test → structured JSONL run record produced in-pipeline (supervisor steps) |
| TS-25 | ✅ | .gates/quarantine.json present (empty {}); mgf-test-supervisor>=0.1.3 dev dep provides quarantine; project is Development Status :: 3 - Alpha | Quarantine machinery (the published supervisor + .gates/quarantine.json with atomic-write) is available and the state file exists. But TS-25 is L2-binding (L1 MAY defer); since CI does not drive the supervisor (TS-23), auto-quarantine never — FIXED 2026-06-07: supervisor-in-CI exercises auto-quarantine (.gates/quarantine.json) in the pipeline |
| TS-26 | N/A | pyproject.toml build-backend hatchling, pure-Python wheel (mgf_fastapi-0.6.6-py3-none-any.whl is py3-none-any); no Zig/Go/Rust/C/Swift artifacts; src/ is all .py | mgf-fastapi ships no compiled artifact (none-any wheel, pure Python). TS-26 explicitly lets pure-Python libraries declare N/A. |
| TS-27 | ✅ | .gates/last-run/failures/ directory is empty (ls returned nothing); failure-bundle writer lives in mgf-test-supervisor (dev dep) | The single-file failure-bundle capability ships in the installed supervisor, and the .gates/last-run/ tree exists, but no bundle has ever been produced (failures/ empty — the only recorded run passed) and CI does not run the supervisor (TS- — FIXED 2026-06-07: supervisor-in-CI writes single-file failure bundles (.gates/last-run/failures/) on failure |
| TS-28 | ⏳ | run.jsonl schema_version is "1.0.0" (TS-28 requires ≥1.1.0 for the perf field); no perf_span usage in src/ or tests (rg found none); no perf-summary.md in .gates | L2 rule (L1 MAY defer). No evidence of opt-in per-test perf attribution: the run record is schema 1.0.0 (below the ≥1.1.0 the TestRecord.perf field needs), no perf_span context manager appears in source or tests, and no run-level perf-summa — DEFERRED: opt-in per-test perf attribution — needs perf_span instrumentation + TestRecord schema ≥1.1.0 + perf-summary.md (perf-infra; L2, L1 may defer) |

### API_DESIGN

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| AP-01 | ✅ | src/mgf/fastapi/__init__.py:63; _request_id.py:125; _exceptions.py:223; security.py:53; webhooks/__init__.py:38; webhooks/_verifier.py:31; db.py:181; testing.py:29; logs.py:264; _rate_limit. | Every package and module that consumers import from declares an explicit, alphabetically-sorted string __all__ at the boundary. |
| AP-02 | ✅ | src/mgf/fastapi/_dependencies.py:40 (get_app_context: intent + Raises); webhooks/_verifier.py:138 (verify: returns + raises); _rate_limit.py:106 (TokenBucket: intent + params + example); sec | Public names carry substantive docstrings stating intent, invariants, raises, and usage examples — well above the 'returns the X' floor. |
| AP-03 | ✅ | pyproject.toml:3 version="0.6.7"; CHANGELOG.md:7-13 (declares SemVer + 0.x MINOR-may-break window, pin-tightly guidance); Development Status :: 3 - Alpha classifier | Follows MAJOR.MINOR.PATCH with the SemVer 0.x clause explicitly invoked; version is a clean release form (no .dev/+local). |
| AP-04 | N/A | rg over src/ finds no DeprecationWarning/PendingDeprecationWarning emit; CHANGELOG.md has no own-surface ### Deprecated entries (only mgf-common pin-walk removal notes at 0.3.1:244-250) | mgf-fastapi has deprecated none of its own public names, so the announce→survive→remove cycle has no subject yet. Premise hasn't arisen. |
| AP-05 | ✅ | src/mgf/fastapi/_lifespan.py:28 setup_lifespan(*, ...) keyword-only; security.py:146 IpAllowlist(cidrs, *, trust_proxy); _rate_limit.py:241 RateLimitMiddleware(app, *, bucket, key); webhooks | New/optional parameters are keyword-only with defaults across the surface; mypy --strict runs in CI as the signature-stability proxy. |
| AP-06 | ✅ | PUBLIC_API.md:38-47 pins concrete return types (e.g. current_request_id()->str, get_app_context->AppContext, verify->(event_id,ts)); mypy strict gate (.woodpecker.yml:33-39) | Return types are narrowly pinned in the contract doc and enforced by mypy --strict; no observed widening/narrowing. Same mechanical gate as AP-05. |
| AP-07 | ✅ | src/mgf/fastapi/exceptions.py:25 HmacVerificationError(HttpUnauthorizedError) with http_status=401; security.py:40 raises HttpForbiddenError/HttpUnauthorizedError (mgf-common typed hierarchy | Public functions raise subclasses of mgf-common's typed AppError/HttpError hierarchy and document them in docstrings; concretes inherit existing categories (non-breaking). |
| AP-08 | ✅ | Verified all 14 non-pycache src .py files contain 'from __future__ import annotations' (loop over find src -name '*.py'); ruff lint select includes 'TCH'/'UP' (pyproject.toml:142-161); TYPE_ | Every public and internal module imports future annotations; runtime-unused imports are gated behind TYPE_CHECKING. |
| AP-09 | ✅ | src/mgf/fastapi/py.typed exists (0 bytes); pyproject.toml:34 'Typing :: Typed' classifier; [tool.hatch.build.targets.wheel] packages=["src/mgf"] (pyproject.toml:130) walks the tree so the ma | PEP 561 marker present and the hatchling wheel config picks it up. Note: CI runs no post-build `unzip -l dist/*.whl \| grep py.typed` check (the AP-09 mechanical check from §14 is absent), but the marker itself satisfies the MUST. |
| AP-10 | ✅ | src/mgf/fastapi/logs.py:264 exports DEFAULT_LOG_CHANNEL/LogBroadcaster/build_log_stream_service/create_logs_router — NONE appear in PUBLIC_API.md (rg confirmed 'NONE FOUND'); __init__.py:63  | PUBLIC_API.md is stale: the entire mgf.fastapi.logs module (4 names, added 0.6.4) and the rate-limit names (added 0.6.0) are in __all__ but missing from the contract doc, and there is no CI sync test — the exact drift AP-10's check exists t — FIXED 2026-06-07 (0.6.8): __version__ + mgf-common[standards] in [dev] + PUBLIC_API.json/generator/pin-tests + PUBLIC_API.md logs/rate-limit sections + README off-pypi.org |
| AP-11 | ✅ | PUBLIC_API.md:16-20 defines the three tiers; every listed row carries 'experimental' (lines 38-47, 60, 74-77, 96-99, 117-118, 134); but logs.py + rate-limit names have NO tier row at all (th | Tiers are declared for the names that ARE listed, but the names missing from PUBLIC_API.md (logs, rate-limit) have no declared tier, and no mechanical pin enforces the tier column. Partial. — FIXED 2026-06-07 (0.6.8): __version__ + mgf-common[standards] in [dev] + PUBLIC_API.json/generator/pin-tests + PUBLIC_API.md logs/rate-limit sections + README off-pypi.org |
| AP-12 | ✅ | CHANGELOG.md:1-16 (Keep a Changelog + SemVer header); 0.2.0 §Changed (351), 0.3.1 §Changed (252-256), 0.3.0 §Dependency bump (301-306) document pin-walk/dependency changes; mgf-common remova | CHANGELOG follows Keep a Changelog with per-release entries; mgf-fastapi has shipped no own-public-surface breaking change requiring the four-piece (symptom/before/after/why) recipe, so the rule is met structurally. |
| AP-13 | ✅ | rg '__version__' over the tree returns ONLY PUBLIC_API.md prose — there is no __version__ attribute in src/mgf/fastapi/__init__.py or anywhere in the package; no tests/unit/test_version_in_s | The MUST is unmet: the top-level package exposes no __version__ attribute mirroring pyproject, and CI has no version-in-sync lint. Consumers cannot read mgf.fastapi.__version__. — FIXED 2026-06-07 (0.6.8): __version__ + mgf-common[standards] in [dev] + PUBLIC_API.json/generator/pin-tests + PUBLIC_API.md logs/rate-limit sections + README off-pypi.org |
| AP-14 | ✅ | .importlinter ships 3 contracts incl. `mgf.fastapi does not import mgf.common private internals (AP-14, closed-box)` (forbidden `mgf.common._*`); db.py docstring + the 2 test imports repointed onto the public mgf.common surface; `lint-imports` runs in CI (.woodpecker.yml) and locally KEEPS 3/3 | FIXED 2026-06-25 (stage 1): the closed-box rule is now mechanically enforced — the private-internal import contract bans `mgf.fastapi -> mgf.common._*`, and the prior docstring/test leaks onto `mgf.common._*` are gone. |
| AP-15 | N/A | rg over src/ finds no back-compat re-export shim module that survives removal and warns; the only re-export (current_request_id from mgf.common in __init__.py:56) is a live contract name, no | mgf-fastapi ships no backward-compatibility shim that outlives removed code, so the warn-on-import + INFO-audit-log requirement has no subject. |

### SCOPE

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| SP-01 | ✅ | README.md:19-25 ('## What this provides' submodule table); no SCOPE.md in repo (find . -iname '*scope*' returns only .venv + docs/standards/README.md pointer to mgf-common's SCOPE.md) | An implicit IN-scope list exists as the README 'What this provides' submodule table, but it is not framed as a published, explicit IN-scope policy that 'new modules must clear' — no SCOPE.md or equivalent scope section. SP-01 partially met — FIXED 2026-06-07: SCOPE.md §1 published in-scope policy |
| SP-02 | ✅ | No OUT-of-scope feature list anywhere; SECURITY.md:85-95 'Out of scope:' is vulnerability-disclosure scope (excludes mgf-common/fastapi/starlette CVEs), NOT a feature OUT-of-scope list with  | No explicit OUT-of-scope feature list with per-entry reasoning. The only 'Out of scope' block (SECURITY.md) governs vuln reporting, not feature cuts. SP-02 unmet. — FIXED 2026-06-07: SCOPE.md §2 out-of-scope list with per-entry reasons |
| SP-03 | ✅ | CONTRIBUTING.md (full read: AP/FB/EH discipline columns, FEEDBACK flow) and AGENTS.md contain no requirement that a PR adding a module/function justify against an IN/Adjacent/OUT scope; rg ' | No documented PR scope-justification gate (the §4 'scope creep review question'). Without a published scope policy (SP-01/02) there is nothing for a PR to justify against. SP-03 unmet. — FIXED 2026-06-07: SCOPE.md §3 PR scope-creep gate (3 questions) |
| SP-04 | ✅ | rg 'demand signal\|consumer demand\|promotion criterion' over all repo .md (excl .venv) returns nothing; no Adjacent-scope section exists to attach a demand signal to | No Adjacent-scope list and no documented consumer-demand-signal-before-promotion rule. The repo does run a FEEDBACK.md flow (the raw input SP-04 would consume), but the SP-04 discipline itself is absent. Unmet. — FIXED 2026-06-07: SCOPE.md §4 adjacent-scope + consumer-demand-signal rule |
| SP-05 | ✅ | No feature OUT-of-scope list exists (see SP-02), therefore no ecosystem-alternative citations per cut; SECURITY.md:88-92 cites upstream report targets but for CVEs, not feature deferrals | SP-05 is contingent on SP-02's OUT list, which does not exist. No ecosystem-alternative citations for feature cuts. Unmet (consequent of SP-02). — FIXED 2026-06-07: SCOPE.md §5 alternatives for out-of-scope needs |
| SP-06 | ✅ | No scope-policy document exists to revisit; docs/cutover/{v0.1.0,v0.2.0,v0.3.0}.md and CHANGELOG.md carry no 'scope policy revisited' step; project is pre-1.0 (PUBLIC_API.md:19, README Statu | No scope policy to revisit on a MAJOR boundary, and no MAJOR release has occurred (still 0.x). SP-06 cannot be satisfied without the underlying SCOPE doc; unmet rather than N/A because the standard (SCOPE.md:84) says siblings ship their own — FIXED 2026-06-07: SCOPE.md §6 revisit-on-MAJOR clause |

### WORKFLOWS

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| WF-01 | ✅ | .woodpecker.yml:19-22 (when: event: push / pull_request / manual) | CI triggers on every push and PR (plus manual); no local-only path — all gates run in fresh containers. |
| WF-02 | ✅ | .woodpecker.yml:25-63 (lint, typecheck, test-3.11/3.12/3.13); .woodpecker.yml:25 comment 'No import-linter contracts configured → no lint-imports'; no .importlinter file exists (find returne | Three of four gates present (ruff lint, mypy --strict, pytest+cov80), but the import-linter gate is absent — no .importlinter contracts and CI explicitly skips lint-imports; ruff format --check also not run. Branch-protection (platform side — FIXED 2026-06-07: .importlinter (independence + UI-forbid contracts) added + lint-imports & no-placeholder grep wired into CI |
| WF-03 | ✅ | .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'); rg for tag/publish/twine in .woodpecker. | mgf-fastapi publishes an mgf-* package (dist/ wheels exist, version 0.6.7) yet has NO tag-driven publish workflow at all — releases are cut outside CI, the exact footgun WF-03 forbids. — FIXED 2026-06-07: tag-driven Codeberg publish pipeline added (.woodpecker.yml build→sbom→tag-coherence→smoke→publish→verify-codeberg + scripts/verify_codeberg.py), mirroring mgf-common |
| WF-04 | ✅ | No publish step and no codeberg_package_token / from_secret reference anywhere in .woodpecker.yml (rg for codeberg/twine/upload negative) | No tokens leak in the workflow (good), but since there is no CI publish path the required secret-store-backed credential model is simply not implemented — the rule applies (it publishes) and is unmet rather than safely declined. — FIXED 2026-06-07: tag-driven Codeberg publish pipeline added (.woodpecker.yml build→sbom→tag-coherence→smoke→publish→verify-codeberg + scripts/verify_codeberg.py), mirroring mgf-common |
| WF-05 | ✅ | No tag-coherence step in .woodpecker.yml; rg '__version__' across src/ returns nothing — package exposes no __version__ (src/mgf/fastapi/__init__.py has none) | No CI coherence check exists, and the check is structurally impossible as written: the package defines no __version__ to compare against the tag and pyproject (version lives only in pyproject.toml:3). — FIXED 2026-06-07: tag-driven Codeberg publish pipeline added (.woodpecker.yml build→sbom→tag-coherence→smoke→publish→verify-codeberg + scripts/verify_codeberg.py), mirroring mgf-common |
| WF-06 | ✅ | No build step in .woodpecker.yml; stale dist/ wheels present (dist/ dir, mtime Jun 4) built outside any publish run | No publish flow means no 'wheel built fresh in the publish run' guarantee; the only wheels present are local dist/ artifacts — precisely the drift-prone re-use WF-06 prohibits. — FIXED 2026-06-07: tag-driven Codeberg publish pipeline added (.woodpecker.yml build→sbom→tag-coherence→smoke→publish→verify-codeberg + scripts/verify_codeberg.py), mirroring mgf-common |
| WF-07 | ✅ | No smoke/ephemeral-venv step in .woodpecker.yml (no venv install of a built wheel anywhere; CI only does editable `pip install -e .`) | Pre-publish wheel smoke is absent; CI exercises the source tree via editable install, never the built wheel — packaging-metadata bugs would not surface. — FIXED 2026-06-07: tag-driven Codeberg publish pipeline added (.woodpecker.yml build→sbom→tag-coherence→smoke→publish→verify-codeberg + scripts/verify_codeberg.py), mirroring mgf-common |
| WF-08 | ✅ | No post-publish verify step; comment at .woodpecker.yml:6 lists 'verify-pypi' only as a future TODO | No Codeberg REST-API poll exists because nothing is published in CI; the verification watchdog is unimplemented. — FIXED 2026-06-07: tag-driven Codeberg publish pipeline added (.woodpecker.yml build→sbom→tag-coherence→smoke→publish→verify-codeberg + scripts/verify_codeberg.py), mirroring mgf-common |
| WF-09 | ⏳ | Pinned: python:3.11/3.12/3.13-slim, zricethezav/gitleaks:v8.30.1 (.woodpecker.yml). Unpinned: `pip install --quiet pip-audit` + the ad-hoc `httpx2` install; uv.lock bypassed | **Fleet-infra deferral — same root cause as WF-11.** Image/plugin pins are correct; the residual tool/dep-pinning leak (unpinned `pip-audit`, ad-hoc `httpx2`, lock bypassed) is the install-from-pyproject-ranges step that the WF-13 template uv-migration closes fleet-wide. Tracked with WF-11 at mgf-standard; deferred this pass (templated CI file). |
| WF-10 | ✅ | .woodpecker.yml:24-81 — all steps (lint, typecheck, three test jobs, vuln-scan, secret-scan) declared with no depends_on, so Woodpecker runs them in parallel | Independent steps fan out in parallel by Woodpecker's default DAG; no gratuitous serialization, and no genuine dependencies exist to require ordering (no build/publish chain present). |
| WF-11 | ⏳ | uv.lock present (checked in) but CI installs `pip install -e ".[...]"` from pyproject ranges, not from the lock (.woodpecker.yml) | **Fleet-infra deferral — not mgf-fastapi-owned (HARD deferral this pass).** Identical to the mgf-common WF-11 framing: `.woodpecker.yml` is a generated, drift-checked artifact owned by the `mgf-standard/templates/woodpecker/` template — switching CI to `uv sync --frozen` unilaterally would break the federation template-parity invariant (`mgf-fed drift-check`), and fleet-wide adoption is blocked by the **devpi backfill gap** (the full-graph fleet lock doesn't resolve). Tracked at **mgf-standard** as the WF-13 template uv-migration. Deferred per non-adoption-is-valid (don't break a stronger invariant to "fix" an owned-but-templated file). |
| WF-12 | ✅ | No `\|\| true` anywhere in .woodpecker.yml (grep negative); each step runs explicit commands (ruff check, mypy, pytest, pip-audit, gitleaks) whose non-zero exit fails the step and surfaces i | No silent-failure suppression and no no-op green steps; failures propagate loudly. Minor: pip-audit lacks the documented explicit-retry-with-logged-context pattern, but that is not a swallowed failure. |
| WF-13 | ✅ | .woodpecker.yml:1-2 `# GENERATED — do not hand-edit. Source: mgf-standard/templates/woodpecker/` + `# Regenerate after editing the template; never patch this file in place.` | NEW MUST (v2.24). The CI file is a generated artifact rendered from the canonical `mgf-standard` template, carrying the generation banner; generation-currency is `mgf-fed drift-check`-enforced fleet-wide. (This is also why WF-09/WF-11 are template-owned, not hand-fixable here.) |

### RELEASING

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| REL-01 | ✅ | git cat-file -t v0.6.7 → 'commit' (lightweight); v0.6.0..v0.6.6 + v0.4.0 → 'tag' (annotated). v0.6.7 points at HEAD release commit 349a8cb. | Every release tag exists and the latest points at the exact release commit, BUT the current release v0.6.7 is a LIGHTWEIGHT tag, not the annotated `v<version>` REL-01 mandates (all prior tags v0.4.0–v0.6.6 are correctly annotated — this is — FIXED 2026-06-09: release tags re-annotated (v0.6.7) on the same build commits + force-pushed (annotated, GC-safe; no content change). 0 lightweight v-tags remain. |
| REL-02 | ✅ | pyproject.toml:3 version="0.6.7"; dist/ holds only mgf_fastapi-0.6.6-*.whl/.tar.gz (one version's artifacts, not a reused string). git tags are monotonic v0.4.0→v0.6.7 with no version reused | No evidence of a reused version string; dist/ contains a single (prior 0.6.6) version's artifacts. Minor hygiene note: dist/ is stale (0.6.6 while HEAD is 0.6.7) but that is not a REL-02 reuse violation. |
| REL-03 | ✅ | PUBLIC_API.md:2-7 carries the master-HEAD/'verify with pip show + __version__ field in PUBLIC_API.json' banner. BUT no PUBLIC_API.json exists (find PUBLIC_API.json → none outside .venv) and  | The REL-03 banner is present and correctly worded, but the verification mechanism it points consumers to is broken: there is no PUBLIC_API.json to carry __version__ and the package exposes no __version__ attribute, so `pip show` cross-check — FIXED 2026-06-07 (0.6.8): __version__ + mgf-common[standards] in [dev] + PUBLIC_API.json/generator/pin-tests + PUBLIC_API.md logs/rate-limit sections + README off-pypi.org |
| REL-04 | ✅ | .woodpecker.yml has no publish step and no TWINE_PASSWORD/token references; git ls-files shows no .env/.pypirc/secret/token tracked; rg for pypirc/codeberg_package_token/write:package across | No tokens are committed, echoed in the workflow file, or passed via env in any tracked artifact. Trivially satisfied because there is no publish automation yet, but no violation is present. |
| REL-05 | ✅ | Monotonic tag sequence v0.4.0→v0.6.0..v0.6.7 (git tag); each CHANGELOG release (0.6.4..0.6.7) is a distinct new version, no content-swap of an existing version. dist/ shows single-version ar | Bug fixes / pin walks ship as new versions (0.6.5→0.6.6→0.6.7), never as a content swap of a published version. No evidence of immutability violation. |
| REL-06 | ✅ | .woodpecker.yml:5-6 explicitly: 'No publish flow yet — when a release is cut, copy the tag-coherence -> smoke -> publish -> verify-pypi chain from mgf-common.' No bump-regen-build-test-twine | The strictly-ordered release flow is not implemented/enforced — there is no CI publish pipeline and no release script encoding the order; the ordering exists only as a prose TODO pointing at mgf-common. Cannot verify ordered execution; the — FIXED 2026-06-07: tag-driven Codeberg publish pipeline added (.woodpecker.yml build→sbom→tag-coherence→smoke→publish→verify-codeberg + scripts/verify_codeberg.py), mirroring mgf-common |
| REL-07 | N/A | FEEDBACK.md has exactly one entry, PAPER-01 (Status \| OPEN, line 45); rg SHIPPED FEEDBACK.md finds only the process-doc legend, no shipped rows. The 0.6.x releases are federation pin-walks  | No FEEDBACK entry was shipped by any recent release, so the 'close every FEEDBACK entry it ships' obligation has no triggering work. PAPER-01 is legitimately still OPEN (unfixed). Historically the project did flip+document closures (e.g. PA |
| REL-08 | ✅ | .woodpecker.yml has no smoke step (rg smoke → only the line-5 TODO comment); no `pip install dist/*.whl` into an ephemeral venv anywhere in CI or scripts. dist/ holds 0.6.6 while HEAD is 0.6 | Pre-release wheel smoke is not implemented — neither CI nor a release script installs the freshly-built wheel into a venv and imports public names. (Compounded by absent __version__: the canonical smoke `import mgf.fastapi; print(__version_ — FIXED 2026-06-07: tag-driven Codeberg publish pipeline added (.woodpecker.yml build→sbom→tag-coherence→smoke→publish→verify-codeberg + scripts/verify_codeberg.py), mirroring mgf-common |
| REL-09 | N/A | FEEDBACK.md: sole entry PAPER-01 is OPEN (line 45); no entry flipped to SHIPPED/DEFERRED/REJECTED in the recent (0.6.x) release window. docs/cutover/ holds historical guides (v0.1.0/v0.2.0/v | No FEEDBACK status flip occurred in the audited release wave, so there is no closure requiring a consumer-facing notification. The cutover-guide pattern (docs/cutover/) is established, evidencing the practice when it does apply. |

### PACKAGE_DISTRIBUTION

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| PKG-01 | ✅ | uv.lock:588-596 (mgf-common source = codeberg.org/api/packages/magogi-admin/pypi/simple/); uv.lock:662/674/687 (mgf-livepush/sqlalchemy/test-supervisor same); pyproject.toml:116-120 [tool.uv | Every mgf-* dependency is resolved from the Codeberg registry; lockfile confirms actual resolution, not just config. Third-party deps still from pypi.org (allowed). |
| PKG-02 | ✅ | pyproject.toml:113 url = "https://codeberg.org/api/packages/magogi-admin/pypi/simple/"; uv.lock:594/596 files endpoint under same base | Consumer-side index uses the canonical simple-index endpoint verbatim; no hand-typed variants. (No publish/verify steps exist to check the upload/REST endpoints — see PKG-04/05.) |
| PKG-03 | ✅ | pyproject.toml:107-114 comment 'Auth via UV_INDEX_CODEBERG_MGF_USERNAME / _PASSWORD env vars (never committed)'; no token literals found in repo (gitleaks step .woodpecker.yml:77-80) | Token-via-env model documented; no plaintext token committed; matches read:package consumer discipline. No publish token to assess since no publish flow. |
| PKG-04 | ✅ | .woodpecker.yml:5-6 'No publish flow yet — copy ... verify-pypi chain from mgf-common'; docs/claude/CLAUDE.md:398-417 release sequence; CONTRIBUTING.md:51-53 | No ordered Codeberg publish flow exists. The only documented flow (CLAUDE.md:411,415-416 'twine upload dist/*'; CONTRIBUTING.md:53 'upload') is the OLD pypi.org-targeted REL-06 ritual — non-conformant; gates->build->check->upload-to-Codeber — FIXED 2026-06-07: tag-driven Codeberg publish pipeline added (.woodpecker.yml build→sbom→tag-coherence→smoke→publish→verify-codeberg + scripts/verify_codeberg.py), mirroring mgf-common |
| PKG-05 | ✅ | .woodpecker.yml:5-6 references 'verify-pypi'; docs/claude/CLAUDE.md:416 post-publish smoke = 'pip install --upgrade mgf-fastapi==X.Y.Z' (default pypi.org index) | No post-publish verification against the Codeberg REST API. Documented smoke verify installs from pypi.org's default index, and CI verify step is absent/named verify-pypi — directly violates PKG-05 ('never pypi.org'). — FIXED 2026-06-07: tag-driven Codeberg publish pipeline added (.woodpecker.yml build→sbom→tag-coherence→smoke→publish→verify-codeberg + scripts/verify_codeberg.py), mirroring mgf-common |
| PKG-06 | ✅ | pyproject.toml:111-114 [[tool.uv.index]] name=codeberg-mgf, explicit=true; pyproject.toml:116-120 [tool.uv.sources] maps mgf-common/sqlalchemy/livepush/test-supervisor; uv.lock:588 resolves  | Canonical dependency-confusion-safe shape: named explicit index + per-package source mapping for every mgf-* dep. No committed bare --extra-index-url. |
| PKG-07 | N/A | No CHANGELOG.md entry for delete/re-upload; no release tooling present in repo | Immutability/re-upload-recovery is a release-operation policy; mgf-fastapi has no publish flow yet (PKG-04 gap), so there is nothing to enforce or violate here for this consumer-state repo. |
| PKG-08 | N/A | No retention/prune script or scheduled quota job in scripts/ (only scripts/__init__.py) | Retention against the magogi-admin account quota is a registry-owner/operational concern, not a per-sibling-library obligation. mgf-fastapi neither owns the quota nor runs the prune job. |
| PKG-09 | ✅ | .woodpecker.yml:24-80 (steps: lint, typecheck, test-3.11/12/13, vuln-scan, secret-scan only); :5-6 'No publish flow yet' | No tag-driven, build-in-CI, secret-token publish step exists. CI never publishes (no event: tag step, no codeberg_package_token secret). Applies (this is a published library) but is unmet. — FIXED 2026-06-07: tag-driven Codeberg publish pipeline added (.woodpecker.yml build→sbom→tag-coherence→smoke→publish→verify-codeberg + scripts/verify_codeberg.py), mirroring mgf-common |
| PKG-10 | ✅ | pyproject.toml:2 name='mgf-fastapi' (hyphenated dist); :130 packages=['src/mgf'] dotted import mgf.fastapi; :103-105 [project.urls] point to codeberg.org/magogi-admin/mgf-fastapi | Hyphenated distribution name, dotted mgf.fastapi import, and project URLs use the Codeberg page — defers correctly to NM-01..04. (README badges still link pypi.org project page — see PKG-12.) |
| PKG-11 | N/A | No agent/version-recommendation logic in src/** (FastAPI adapters only); scripts/ holds only __init__.py | PKG-11 binds agents/tooling that resolve a sibling's published version. mgf-fastapi ships no such version-check tooling, so the rule has no surface to apply to here. |
| PKG-12 | ✅ | README.md:3-4 pypi.org badges (shields.io/pypi/v + pypiversions) + :8,13 pypi.org/project links for mgf-fastapi/mgf-common; SECURITY.md:3 pypi.org/project/mgf-common; CONTRIBUTING.md:53 'upl | Residual pypi.org distribution surface for mgf-* persists: 'where to find it' badges/links target pypi.org/project/mgf-* (PKG-10 says these become the Codeberg page), and the documented publish+smoke flow targets pypi.org. The reference no- — FIXED 2026-06-07 (0.6.8): __version__ + mgf-common[standards] in [dev] + PUBLIC_API.json/generator/pin-tests + PUBLIC_API.md logs/rate-limit sections + README off-pypi.org |

### DOCUMENTATION

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| DOC-01 | ✅ | repo root listing: PUBLIC_API.json absent; LESSON_LEARNED.md absent | 9 of 10 baseline root docs + docs/ present (README/LICENSE/CHANGELOG/CONTRIBUTING/SECURITY/FEEDBACK/AGENTS/STARTHERE/DEPS/FEDERATION all exist), but the library-only PUBLIC_API.json companion (required for siblings) and the v2.21-MUST LESSO — CORRECTED 2026-06-07: the two 'missing' docs now exist — PUBLIC_API.json (batch B) + LESSON_LEARNED.md (seeded); audit read a stale local checkout |
| DOC-02 | ✅ | docs/ buckets: claude/ cutover/ inprogress/ recipes/ standards/ (+ additional testing/); docs/standards/README.md is pointer-only | Six-bucket hierarchy applicable to a sibling is present; docs/standards/README.md carries the NOT-a-duplicate framing per §2.0.4; reference/ correctly omitted (thin surface). |
| DOC-03 | ✅ | docs/testing/ contains only DIAGNOSING_HANGS.md, no README.md (docs/testing/ -> NO_README) | The additional docs/testing/ bucket lacks the required charter README.md; all six canonical buckets do have READMEs. — FIXED 2026-06-07: docs/testing/README.md bucket charter added |
| DOC-04 | ✅ | CHANGELOG.md:1-20 Keep-a-Changelog header + [Unreleased] + dated [0.6.7]/[0.6.6] entries; cutover links at CHANGELOG.md:267,324,373 | CHANGELOG maintained in Keep-a-Changelog format with per-release entries; breaking entries reference cutover guides. Same-commit discipline not directly verifiable here but the structure is conformant. |
| DOC-05 | ✅ | docs/cutover/v0.1.0.md, v0.2.0.md, v0.3.0.md; v0.3.0.md:1-40 has At-a-glance table + 'Closes FEEDBACK PAPER-29' + before/after blocks | Per-release cutover guides exist, named after the TO version, with old->new tables, codemod notes, copy-paste migration blocks, and FEEDBACK back-links. |
| DOC-06 | ✅ | docs/inprogress/RELIABILITY_AND_ACCURACY.md has §1..§6 (lines 46,83,101,153,532,587); RA-01 evidence src/mgf/fastapi/_lifespan.py:57-63; SECURITY_HARDENING.md + PERFORMANCE_AND_CAPACITY.md s | All three living trackers follow the six-section shape with file:line evidence and falsifiable concrete outputs in §4 work units. |
| DOC-07 | ✅ | docs/claude/CLAUDE.md:306-461 — 'Dense lookup document. No prose.' + File map + Cross-cutting patterns + Build and release + Common pitfalls + Recently-touched areas | CLAUDE.md ships in the mandated dense-lookup format with all §4.1 sections; managed auto-block above the project-specific addenda. |
| DOC-08 | ✅ | no PUBLIC_API.json at root; no tests/unit/test_public_api*.py (tests/unit/ listing shows none); PUBLIC_API.md:4-7 references a PUBLIC_API.json that does not exist | PUBLIC_API.md exists but the machine companion PUBLIC_API.json is absent and there is NO CI drift pin tracking __all__ -> PUBLIC_API.md or MD<->JSON. PUBLIC_API.md even cites the missing JSON's __version__ field — a live drift. — FIXED 2026-06-07 (0.6.8): __version__ + mgf-common[standards] in [dev] + PUBLIC_API.json/generator/pin-tests + PUBLIC_API.md logs/rate-limit sections + README off-pypi.org |
| DOC-09 | ✅ | rg 'See: docs/reference\|# See:' src/ -> no matches; no tests/unit/test_public_api*.py CI lint; docs/inprogress/README.md states 'mgf-fastapi has no consumer-facing FEEDBACK.md of its own' w | Neither drift-detection direction is wired: no name-existence CI lint for PUBLIC_API.md and no See:/# See: back-references in modules. A concrete drift already exists (inprogress/README.md denies the FEEDBACK.md that ships). — CORRECTED 2026-06-07: PUBLIC_API.md name-existence lint is the md↔__all__ pin test (batch B); inprogress/README FEEDBACK drift fixed |
| DOC-10 | ✅ | FEEDBACK.md:41-60 PAPER-01 with Status/Severity/Submitter/Submitted metadata + OPEN lifecycle; docs/inprogress/* hold RA/SH/PC internal audits | Consumer-facing FEEDBACK.md and internal docs/inprogress/ trackers are distinct surfaces with non-overlapping content; FEEDBACK uses severity tags + lifecycle states. |
| DOC-11 | ✅ | docs/inprogress/README.md Index marks all three trackers 'v0.1 — active'; no fully-closed-and-stale tracker present; no docs/archive/ needed yet | All trackers are active workspaces, not graveyards; archival cadence not yet triggered (no fully-closed tracker sitting a release cycle). |
| DOC-12 | ✅ | docs/recipes/fastapi.md:1-202 hierarchical headings (no level skips); root README.md uses relative links for in-repo (docs/recipes/fastapi.md) + absolute codeberg URLs only cross-repo; CLAUD | Cross-refs are relative for in-repo and absolute only across repos; headings are hierarchical and tables used for enumerables. Minor: recipe headings use '## Title' not the '## §N' form, but that is a SHOULD-style nuance, not a violation of |
| DOC-13 | ✅ | FEEDBACK.md:1-30 repo-scoped header routing sibling-specific issues here, federation-wide to mgf-common/FEEDBACK.md; PAPER numbering local-per-repo declared | FEEDBACK.md is correctly repo-scoped with explicit where-to-file-what routing and local PAPER-NN numbering per FB-01. |
| DOC-14 | N/A | DOCUMENTATION.md ADP-04 carve-out names mgf-fastapi explicitly; no docs/inprogress/MGF_ADOPTION.md present | mgf-fastapi is a federation sibling — the ADP-04 carve-out exempts siblings from MGF_ADOPTION.md; their adoption relationship lives in federation_roadmap + CHANGELOG. |
| DOC-15 | ✅ | docs/inprogress/MGF_STANDARDS_CONFORMANCE.md:64-98 — every per-standard section is the placeholder '(initial audit: ✅ structurally; depth pass on first standards-bump trigger)'; README.md:78 | The tracker file exists and is linked with an L2 declaration, but it is a scaffold: §2.8 DOC and all other groups carry NO per-rule ✅/🚫/N/A rows — it fails the CFM-03 completeness gate (no Index-of-MUSTs coverage). Located correctly, conten — CORRECTED: this 2026-06-07 synthesis IS the per-rule evidenced ledger DOC-15 requires |
| DOC-16 | ✅ | DEPS.md §1 (mgf-common/fastapi/uvicorn/httpx/mgf-sqlalchemy deps table) + §2 (PlasmaMapper consumer, 8 import sites) + §3 import-survey methodology; 'Last verified: 2026-05-28' | Bidirectional DEPS.md present with both §1 (depends-on) and §2 (depended-on-by) populated and an import-survey method. Minor staleness: DEPS §1 shows mgf-common >=0.40,<0.42 while CHANGELOG walked to <0.47 — a drift, but the manifest exists |
| DOC-17 | ✅ | FEDERATION.md present at root (837 bytes — pointer-stub size); README.md + docs/standards/README.md treat mgf-standard standards as source of truth | FEDERATION.md ships as a pointer stub (canonical only in mgf-standard); project links to the canonical contract rather than copying it. |
| DOC-18 | ✅ | PUBLIC_API.md:4-7 references PUBLIC_API.json that does not exist; DEPS.md §1 pin <0.42 vs CHANGELOG [0.6.7] mgf-common <0.47; docs/inprogress/README.md denies the existing FEEDBACK.md | Multiple coupled surfaces are stale: the public-symbol<->PUBLIC_API.json coupling is broken (no JSON, no pin), the pin-bump did not ripple to DEPS.md, and a bucket README contradicts the FEEDBACK.md that ships. The ripple discipline is not — FIXED 2026-06-07: PUBLIC_API.json coupling restored (batch B) + DEPS.md pin ripple bumped >=0.41,<0.47 |
| DOC-19 | ✅ | repo root listing shows no LESSON_LEARNED.md | DOC-19 is MUST since v2.21 (presence is the bar, a seeded empty file is conformant). The file is entirely absent at repo root — genuine gap. — CORRECTED: LESSON_LEARNED.md IS present on origin/main (audit read a stale local checkout 3 commits behind) |

### OPERABILITY

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| OP-01 | N/A | pyproject.toml (no [project.scripts]); src/mgf/fastapi/__init__.py:63 (__all__ = adapters only, no CLI/version surface); logs.py:129 uses mgf.common.app_version() | Library, not an app: no CLI (--version) and no /version endpoint of its own. Rich-version reporting belongs to the consumer's app; mgf-fastapi just forwards mgf.common.app_name()/app_version() into LogRecord. |
| OP-02 | N/A | pyproject.toml — no [project.scripts]; grep for 'doctor' across src/ returns no hits | No app state of its own to probe (no persistent stores, config files, lock files). mgf-common ships `mgf-common doctor` as the reference; a pure adapter library has nothing distinct to self-diagnose. |
| OP-03 | ✅ | pyproject.toml:102-105 (Homepage/Issues/Changelog → codeberg.org/magogi-admin/mgf-fastapi — real, well-formed); .woodpecker.yml has NO curl/HEAD/placeholder URL-resolve lint (grep returned o | URLs point at the real repo and are plausibly live, but OP-03's MUST-half (CI verifies — HTTP-HEAD or a no-placeholder grep) is not wired in .woodpecker.yml. Same open gap mgf-common itself records. — FIXED 2026-06-07: .importlinter (independence + UI-forbid contracts) added + lint-imports & no-placeholder grep wired into CI |
| OP-04 | ✅ | README.md:6-13 (defers mgf-common pin to PyPI metadata, so no stale pin — good); but docs/inprogress/MGF_STANDARDS_CONFORMANCE.md:24 still says pin '>=0.34,<0.35' and §1:21 'Current version: | README headline facts are deliberately drift-proof (pin sourced from PyPI), but the in-repo conformance tracker carries a stale version (0.4.0 vs 0.6.7) and stale pin (0.34 vs 0.41-0.47); no CI dep-pin/test-count lint. Partially met. — FIXED 2026-06-07: ledger §1 version field synced to 0.6.8 |
| OP-05 | N/A | No CLI 'explain'; no /config endpoint in src/. Config is mgf.common.MgfSettings, introspected by `mgf-common explain` (per standard §2.5) | Library does not own a running config surface. The consumer's app exposes active config; mgf-fastapi neither defines nor owns the settings object (it accepts an MgfSettings in setup_lifespan). |
| OP-06 | ✅ | src/mgf/fastapi/logs.py:213-261 create_logs_router mounts GET <prefix>/info (describe_logging: where/levels/sinks) + GET <prefix>/stream (live SSE); auth-gated by design (logs.py:228-229) | Although a library, mgf-fastapi ships the OP-06 service-shape surface as a consumer-mountable router factory: recent logs reachable over HTTP without source-tree path knowledge. This is the strongest OP fit for its shape. |
| OP-07 | N/A | grep 'bundle' across src/ returns no hits; no CLI to host a `bundle` command | Diagnostic-tarball is an app-level command needing config+logs+crash-reports+doctor+sysinfo. A library has no CLI to host it and no own crash dir; this is consumer-side (mgf-common provides the redacted-snapshot building blocks). |
| OP-08 | N/A | No state directories created by src/ (no crash dir, no log-file sink, no cache written); logs.py streams in-memory/over-broker, never to a growing on-disk store | mgf-fastapi writes no unbounded state of its own. Crash-report/log-rotation retention is owned by mgf.common (crash dir, RotatingFileHandler) and the consumer; nothing here to bound. |
| OP-09 | N/A | Rules box: OP-09 'applies to: service'; no validate_production() and no environment/use_mock config owned by src/ (grep found only doc comments in security.py:66, _rate_limit.py:199, webhook | Service-only. Library defines no environment=production config validator; production safety guards live in mgf.common's settings validation and the consumer's service. Security.py defaults (open allowlist) are documented as override-for-pro |
| OP-10 | N/A | Rules box: OP-10 'applies to: service'; no Dockerfile/compose/k8s manifest in repo file listing; ships as a wheel (dist/mgf_fastapi-0.6.6-py3-none-any.whl) versioned via pyproject.toml:3 | Service-only. No deployable image/manifest exists; the artefact is a versioned PyPI wheel (REL territory, not OP-10). Rollback of a deployed image is the consuming service's concern. |
| OP-11 | N/A | Rules box: OP-11 'applies to: service'; no docs/runbooks/ directory, no Prometheus/alertmanager config in repo | Service-only. A library has no alerting pipeline, on-call, or paging path. Alerts + runbooks are owned by the deployed consumer service. |
| OP-12 | N/A | Rules box: OP-12 'applies to: service'; no backup script, no docs/drills/ directory, no S3/R2/GCS target in repo | Service-only. mgf-fastapi has no persistent datastore or backup target; backup/restore drills belong to the consuming service that owns the data. |

### DEPLOYMENT

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| DEP-01 | N/A | DEPLOYMENT.md:41 (Applies to = service); DEPLOYMENT.md:20-21 (Out of scope: Federation libraries, no runtime); pyproject.toml:2-3 + classifiers 'Topic :: Software Development :: Libraries';  | IaC rule is service-shaped; mgf-fastapi is a packaged library with no cloud resources of its own. |
| DEP-02 | N/A | DEPLOYMENT.md:42 (Applies to = service); no ansible/chef/salt/Dockerfile anywhere (ls + rg); no production VM owned by this repo | Config-as-code targets production VMs/images; the library owns no VM or container. |
| DEP-03 | N/A | DEPLOYMENT.md:43 (service); no release-artefact deploy path — distributed as a wheel/sdist (dist/) governed by RELEASING/PACKAGE_DISTRIBUTION, not a deployed artefact; uv.lock exists for dev | Immutable-deploy-artefact rule is for deployed services; library release immutability is PKG-07/REL scope, not DEP-03. |
| DEP-04 | N/A | DEPLOYMENT.md:44 (service); no production secrets store — only test fixtures allowlisted in .gitleaks.toml; secret-scan runs in CI (.woodpecker.yml secret-scan step) | No production deployment means no production secrets to vault; SC-01 secret hygiene is handled at the source/CI layer, not DEP-04. |
| DEP-05 | N/A | DEPLOYMENT.md:45 (service); no deploy/backup/restore/rollback scripts (no deploy/scripts; scripts/ holds dev tooling only) | No environment-explicit deploy scripts because the library is never deployed to prod/staging/dev environments. |
| DEP-06 | N/A | DEPLOYMENT.md:46 (service); no release-rollback runbook (rg 'rollback' hits are DB-session commit/rollback in db.py:70 and bootstrap-rollback test in test_lifespan.py:44, not deploy rollback | Reversible-deploy discipline applies to deployed services; library version pins are reverted via dependency bumps, not a deploy rollback. |
| DEP-07 | N/A | DEPLOYMENT.md:47 (service) + §3 'Every web-service consumer'; no deploy/ directory exists at repo root (ls top-level shows NO deploy/) | The deploy/ shape is mandated for web-service consumers only; a library has none and should have none. |
| DEP-08 | N/A | DEPLOYMENT.md:48 (service); no deploy/docs/slo.md, no alert rules, no monitoring/ (rg found no slo.md/runbook.md) | End-to-end production observability + SLO contract is for a running service; the library exposes logging helpers (logs.py) but operates no production sink. |
| DEP-09 | N/A | DEPLOYMENT.md:49 (service); no DNS/TLS surface — no terraform DNS records, no dns.md, no cert config | DNS+TLS are properties of a deployed endpoint; the library has no hostname or certificate of its own. |
| DEP-10 | N/A | DEPLOYMENT.md:50 (service); no deploy/docs/first-production-cut.md (rg found none) | Green-field provisioning runbook is for a service going to production; the library is never provisioned to a prod environment. |
| DEP-11 | N/A | DEPLOYMENT.md:51 (service); no IaC environments (no terraform/environments/{prod,staging}); CI matrix (.woodpecker.yml test-3.11/3.12/3.13) tests Python versions, not deploy environments | Multi-environment-from-same-code is a deployed-service property; the library has no environments, only a CI test matrix. |

### PRESENTATION_LAYER

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| PR-01 | N/A | src/mgf/fastapi/_exceptions.py:49 (from starlette.middleware...), src/mgf/fastapi/security.py:46 (from fastapi import ...); PRESENTATION_LAYER.md:466-470 (§8.2 'Not on the candidate list: mg | PR-01 fences a framework-NEUTRAL domain SDK that 'exists to serve UIs; MUST NOT pick one'. mgf-fastapi is the inverse: a FastAPI-integration adapter library whose entire purpose is to import fastapi/starlette. The canonical no-UI-framework  |
| PR-03 | N/A | find src -type d => only src/mgf/fastapi{,/webhooks}; no core/, view/, ui/, presentation/ layout. PRESENTATION_LAYER.md:42 (PR-03 governs 'An app repo with multiple UIs'); standard §8.2:466- | PR-03 governs an <app> repo with 2+ UI consumers. mgf-fastapi is a single integration library with no app/UI-consumer shape; the multi-UI layer-split premise does not apply. |
| PR-04 | N/A | .woodpecker.yml:25 ('No import-linter contracts configured'); pyproject.toml has no [tool.importlinter] section; standard PRESENTATION_LAYER.md:43 ties PR-04 to 'The layer hierarchy' from PR | PR-04 mechanically enforces the PR-03 core/view/ui layer hierarchy. With no app/multi-UI layer hierarchy (PR-03 N/A), there is no hierarchy to enforce. (Separately, no import-linter exists at all — see PR-01 gap note.) |
| PR-05 | N/A | No ui/<framework>/ adapters exist (find src -type d shows only mgf/fastapi + webhooks); PRESENTATION_LAYER.md:44 + §6:330 ('sibling-UI isolation is only meaningful when >=2 UIs exist'); §8.2 | PR-05 forbids one ui/<framework>/ adapter importing a peer adapter. mgf-fastapi has zero UI adapters and no sibling-UI relationship, so the rule has no subject. |
| PR-07 | ✅ | docs/inprogress/MGF_STANDARDS_CONFORMANCE.md §2 (lists DP/EH/LG/SC/CF/AP/TS/DOC/REL only; no PR / PRESENTATION_LAYER section); rg 'PRESENTATION_LAYER\|PR-01' docs/ => no hits. Standard PRESE | The tracker MAY mark PR-02..PR-05 as N/A (no UI consumer), but PR-07 states 'PR-01 always applies'; the tracker currently has no PR section addressing even PR-01 — a documented gap to add a short PR section recording the N/A classifications — FIXED 2026-06-07: PR-* classified in §Z (PR-01 + PR-03/04/05 N/A by shape); PR section recorded |

### AGENTS

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| AG-01 | ✅ | AGENTS.md:1-37 (thin pointer, no embedded briefing); byte-identical to /home/bassam/PycharmProjects/mgf-common/AGENTS.md (diff returned IDENTICAL) | Root AGENTS.md is a thin pointer to `mgf-common agents`, carries no briefing copy, and matches the federation template exactly; project notes live in docs/claude/CLAUDE.md. |
| AG-02 | ✅ | AGENTS.md:9 ("Run `mgf-common agents`"); docs/claude/CLAUDE.md:11 same instruction; auto-block markers at docs/claude/CLAUDE.md:1-4,304 | Canonical briefing is delegated to `mgf-common agents`; project-specific lookup lives in the catch-up-managed CLAUDE.md auto-block, not in AGENTS.md. |
| AG-03 | ✅ | .gitmessage:9-10 (both trailers, canonical identity); git config commit.template=.gitmessage; user.email=bassam@magogi.org; commit 349a8cb shows both trailers | Canonical dual-trailer template is wired via .gitmessage; recent substantive agent commits carry Signed-off-by Bassam Alsanie <bassam@magogi.org> + Co-Authored-By Claude. Only auto-generated merge commits (0863ffe, 444044e) lack trailers, w — CORRECTED 2026-06-07: dual-trailer mechanism + git config + .gitmessage are canonical (bassam@magogi.org); current commits canonical; historical commits immutable — going-forward conformant |
| AG-04 | ✅ | FEEDBACK.md:41-68 (PAPER-01 run_test_app cancellation friction filed with concern/impact/repro/suggested-shape); docs/claude/CLAUDE.md:235-264 feedback-discipline block | Reusable friction is filed as a structured FEEDBACK paper in this library's own FEEDBACK.md rather than buried as a code workaround. |
| AG-05 | N/A | Runtime/session behavioral rule; AGENTS.md §9 marks it self-enforcement | Verify-memory-before-acting is an agent-session behavior, not a checkable artifact in a library repo; no static surface to audit. |
| AG-06 | N/A | AGENTS.md §9 enforcement table: "Runtime / harness enforcement ... Not a code rule"; .claude/settings.json present for permission scoping | Risky-action confirmation is harness/runtime-enforced, not a property of the repo under audit. |
| AG-07 | N/A | AGENTS.md §9: "Self-enforcement; runtime reminders surface stale task lists" | TaskCreate/TaskUpdate usage is a per-session agent discipline with no durable repo artifact to audit. |
| AG-08 | N/A | AGENTS.md §9: "Self-enforcement; runtime auto-memory rules cover this" | Federation-scoped memory discipline is a session/runtime concern, not auditable in the repo's files. |
| AG-09 | ✅ | pyproject.toml:107-120 (explicit codeberg-mgf index + per-package tool.uv.sources mapping); commit 9bb9e88 "resolve mgf-* from the Codeberg registry (PKG-06)"; b5f9e28 body cites PKG-06/Code | Sibling versions are resolved and verified against the Codeberg registry (explicit index, no pypi.org fallback), evidencing the registry-is-the-contract discipline rather than local dist/. |
| AG-10 | ✅ | FEEDBACK.md:72-119 (§3 cross-cutting standards-design observation routed as feedback, not a silent doc edit); FEEDBACK.md process §1:28-30 | Standards-level observations are routed through the FEEDBACK/PAPER channel rather than silently editing standards docs to match code; behavior consistent with AG-10. |
| AG-11 | ✅ | FEEDBACK.md:6-13 ("mgf-fastapi-specific → file HERE"; federation-wide → mgf-common/FEEDBACK.md); §3 vm-vmanager-app entry filed here as the target sibling; PAPER-01 carries what-hurts/why/ev | Routing is correct (sibling-specific friction in this repo's FEEDBACK.md, cross-cutting to mgf-common) and papers carry the required triage-and-act detail. |
| AG-12 | ✅ | docs/claude/CLAUDE.md exists with project-scoped content (lines 306-461); AGENTS.md:20-22 points to it; `mgf-common agents` resolved OK in env | Project-scoped briefing content lives in docs/claude/CLAUDE.md, the source `mgf-common agents --project` reads; the uniform CLI capability is available via the mgf-common dependency. |
| AG-13 | ✅ | FEEDBACK.md:1-13,28-30 (consumers file PAPER-NN here; maintainer triages); no cross-repo edits — dependencies in pyproject.toml:43-46 are pin-walks of its own surface only | Library changes are requested via this repo's FEEDBACK.md and the supervisor owns edits; repo shows pin-walks of its own deps, not edits to sibling libraries. |
| AG-14 | ✅ | docs/claude/CLAUDE.md:398-417 release sequence couples version+CHANGELOG+PUBLIC_API in same commit; commit 349a8cb release walks pin + CHANGELOG together (CHANGELOG.md mtime matches); PUBLIC | Coupled-surface propagation is documented and practiced — releases bundle version, CHANGELOG, and PUBLIC_API updates in one change rather than deferring. |
| AG-15 | N/A | AGENTS.md §9: "Review-only (the charter grants license, not a checkable artifact)" | AG-15 grants agent standing/empowerment; it is reviewer-enforced with no durable artifact in a consumer/sibling repo to audit beyond the (separately-checked) channels. |
| AG-16 | ✅ | pyproject.toml:43-100 — dependencies declare "mgf-common>=0.41,<0.47" with NO [standards] extra; rg "standards]" across pyproject.toml/uv.lock returned nothing | AG-16 requires depending on `mgf-common[standards]` so the corpus is resolvable; mgf-fastapi pins plain `mgf-common` (and dev extras) with no `[standards]` extra. CLI resolves only because the auditing environment happens to have mgf-doc-st — FIXED 2026-06-07 (0.6.8): __version__ + mgf-common[standards] in [dev] + PUBLIC_API.json/generator/pin-tests + PUBLIC_API.md logs/rate-limit sections + README off-pypi.org |
| AG-17 | ✅ | docs/memory/MEMORY.md present (in-repo agent memory bucket) | NEW MUST (v2.24). In-repo agent memory (`docs/memory/`) travels with the code; the bucket + index exist. |
| AG-18 | ✅ | .gitignore:64 `my_stuff/` (always-ignored private scratch) + the canonical generated `.gitignore` shape | NEW MUST (v2.24). Standardized git configuration: the canonical `.gitignore` is in place and `my_stuff/` is always-ignored per the AG-18 safety clause. |
| AG-19 | N/A | binds the operator's machine-local runtime auto-memory store (`~/.claude/projects/<slug>/memory/`), not a repo-tracked artifact; the standard itself notes it is "not CI-enforceable (machine-local)" | NEW MUST (v2.25). AG-19 governs the private runtime auto-memory store on the maintainer's machine (git-tracked to a private `<project>-memory` remote), which is not a surface this sibling repo carries — N/A for the repo's own tree. |
| TM-01 | N/A | no datetime crosses a boundary in this repo's code. |
| TM-02 | N/A | no datetime wire format emitted. |
| TM-03 | N/A | no duration measurements with a wall clock (timeouts are not timed spans). |
| TM-04 | N/A | no `datetime.now()` in production logic. |
| TM-07 | N/A | UTC only; no IANA timezone-name usage. |
| TM-08 | ✅ | async sleeps use `asyncio.sleep`. |
| PF-01 | ✅ | tests/perf/test_*.py call `baselines.assert_no_regression(...)` against tests/perf/baselines.json — no absolute wall-clock budget remains (the old `_PER_SIZE_BUDGETS_US`/`_LIFESPAN_BUDGET_MS`/`_NET_OVERHEAD_BUDGET_US`/`_PER_CALL_BUDGET_US` constants are gone) | FIXED 2026-06-25 (stage 2): all 6 perf pins moved to baseline-relative thresholds (regression % vs a RECORDED baseline), copying the mgf-common reference harness. The PAPER-50 absolute-budget anti-pattern is removed. |
| PF-02 | ✅ | perf tests marked `@pytest.mark.perf` (pytestmark / class marker); pyproject.toml addopts excludes `perf` from the default gate. |
| PF-03 | ✅ | tests/perf/baselines.json present (8 metrics: value/units/recorded_at/recorded_commit/regression_threshold_pct/description + `$hw_tier`) — generated by the rebaseline run, never hand-edited | FIXED 2026-06-25 (stage 2): baselines.json + the `Baselines.load/save` recorder in conftest.py. |
| PF-04 | ✅ | tests/perf/conftest.py:`_rebaselining()` (MGF_PERF_REBASELINE) records instead of asserting; README.md documents `mgf-fed bench rebaseline` as a deliberate, separate PF-04 commit | FIXED 2026-06-25 (stage 2): explicit re-baseline path; the baseline file is the recorder's output. |
| PF-05 | ✅ | tests/perf/conftest.py ships the `bench` (warm-up + median `measure`) and `baselines` (assert/record) fixtures — a statistical harness, copied from mgf-common | FIXED 2026-06-25 (stage 2): the plain-pytest timing loops are replaced by the shared harness. |
| PF-07 | ✅ | conftest.py:`assert_no_regression` raises with metric + baseline value(units) + measured value(units) + regression % + recorded commit/hw_tier + a `baselines.json` re-baseline pointer | FIXED 2026-06-25 (stage 2): auto-satisfied once PF-01/PF-03 landed; the failure message is fully numeric. |
| PF-08 | ✅ | tests/perf/baselines.json carries `$hw_tier: dev-local-2026`; tests/perf/hw_tiers.md defines the tier (+ a `ci-amd64-small` placeholder) | FIXED 2026-06-25 (stage 2): baselines are tier-tagged; was N/A under the absolute-budget regime. |
| 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). |
