# MGF_STANDARDS_CONFORMANCE — `mgf-vm-sdk`

> Per-rule status against `mgf-common` standards. Updated on every
> `mgf-common` minor bump and on every PR that touches a conformance
> rule (CFM-03).

| Field | Value |
|---|---|
| `mgf-common` pin | `>=0.41,<0.47` |
| Declared level | **L2 — Standard** (target; see [`STANDARDS_NOTE.md`](../../STANDARDS_NOTE.md)) |
| Last audit | 2026-06-08 (evidence-based depth audit) |
| Verified standing | **below L2** — 121 ✅ / 19 ⏳ / 77 N/A / 1 🚫 of 218 (86% of applicable) |

> **Legend.** ✅ honored · 🚫 declined (with rationale) · ⏳ pending
> (target stated) · N/A (rule doesn't apply at this layer).

**2026-06-08 depth audit (evidence-based, replaces the skeleton).** Declared TARGET **L2**; **verified standing below L2**. Of 218 MUSTs: **122 ✅** (file:line, refute-survived) · **18 ⏳** · **73 N/A** · **0 declined**. Applicable = 145; **122/141 (87%) evidenced**. **Strong CORE** (all ✅, refute-survived): the transport provider-seam (DP-02 — Transport Protocol + in-process/async/gRPC impls + structural lock-in test), every SDK type frozen (DP-03), mypy --strict clean (DP-04, in the SDK's own venv), explicit cancellation-token threading (DP-09), loud-degrade typed errors / no silent fallback (DP-10), and a UI-free guarantee the import-linter actively enforces (DP-01/PR-01 — proven via inject/revert). The 52% reflects **no CI pipeline at all** (the entire WORKFLOWS family is ⏳ — no .woodpecker.yml) + thin docs/public-API/logging hygiene, NOT a weak core.

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

| Cluster | Rules | Count |
|---|---|---|
| CI pipeline (the ENTIRE WF family + REL/PKG publish) | PKG-04, PKG-05, PKG-09, PKG-12, REL-01, REL-03, REL-06, REL-08, WF-01, WF-02, WF-03, WF-04, WF-05, WF-06, WF-07, WF-08, WF-09, WF-10, WF-11, WF-12 | 20 |
| SCOPE policy absent (SP-01..06) | SP-01, SP-02, SP-03, SP-04, SP-05, SP-06 | 6 |
| API/docs (PUBLIC_API.md/json, __version__, buckets, drift, DEPS) | AP-10, AP-11, AP-13, AP-14, DOC-02, DOC-03, DOC-06, DOC-08, DOC-09, DOC-18 | 10 |
| LOGGING (operation records / events — the SDK is quiet) | LG-01, LG-05, LG-06, LG-11, LG-15, LG-16, LG-17 | 7 |
| OPERABILITY (mostly N/A for an SDK — judge OP-03/04) | OP-01, OP-02, OP-03, OP-04, OP-05, OP-07 | 6 |
| TESTING (mirror-lint/coverage-gate/property/supervisor/perf) | TS-01, TS-06, TS-13, TS-14, TS-18, TS-19, TS-20, TS-21, TS-22, TS-23, TS-24, TS-25, TS-27, TS-28 | 14 |
| misc (EH-09 hints, SC-10/18, PR-01/07, AG-16) | AG-16, EH-09, PR-01, PR-07, SC-10, SC-18 | 6 |


## Per-standard verdicts → see §Z

The prior hand-written per-family skeleton has been superseded by the **§Z evidence-based
per-MUST audit** below (2026-06-08), which carries the authoritative file:line verdict for
every one of the 218 MUSTs. Open gaps are clustered in the standing summary at the top.

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

> **2026-06-08 depth audit** (16-family fan-out + adversarial refute on every ✅, hardened prompt + extra-ruthless PR-01/transport-seam pass; ~27 agents + a 14-id re-run for EH/AP). The agents proved the import-linter contract engages via an inject/revert test + caught a stale-sibling mypy artifact. No 🚫-flood, no mislabels. **Mix: 76 ✅ · 69 ⏳ · 73 N/A · 0 🚫.** Replaces the prior scaffold enumeration.


### DESIGN_PRINCIPLES

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| DP-01 | ✅ | pyproject.toml:119-138 [tool.importlinter] root_packages=['mgf.vm.sdk'] + forbidden contract 'SDK does not import libvirt or engine/wire bindings' forbidding libvirt/qemu/grpc/grpcio/pr | Refute attempt failed. The empty CLI output on clean runs was a stale .import_linter_cache + non-verbose banner being swallowed by the shell, NOT the contract no-op'ing — the inject/revert experiment proves the forbidden contract  |
| DP-02 | ✅ | transport/base.py:71-83 Transport (runtime_checkable Protocol) + AsyncTransport; docstring lists three impls (InProcess/Grpc/test fakes) and states 'transports MUST NOT define their own | DP-02 genuinely applies and holds. Project-owned seam (Protocol, named valid ABC shape by the standard) with production + mock impls and a structural lock-in test. Vendor-exception translation is present at the in-process seam. UP |
| DP-03 | ✅ | queries.py: grep -cE '@dataclass' = 49, and grep '@dataclass' \| grep -vc 'frozen=True' = 0 → all 49 are frozen=True (slots=True). No non-frozen @dataclass anywhere in src/ (49 @datacla | Every SDK-defined type that crosses a module boundary is frozen; richer domain types live frozen in mgf-vm-core. No bare string/dict crosses as identity. UPHELD. |
| DP-04 | ✅ | In the SDK's OWN venv (.venv, mgf-vm-core 0.1.8 matching the >=0.1.8,<0.2 pin): `.venv/bin/python -m mypy src/` → 'Success: no issues found in 10 source files', RC=0. pyproject.toml:85- | The required gate is GREEN under the correct, pinned dependency set. The transient mypy failures I first saw were entirely a stale-sibling-version artifact, not a code counterexample. UPHELD. |
| DP-05 | ✅ | tests/unit/mgf/vm/sdk/ mirrors src layout 1:1. `.venv/bin/python -m pytest tests/unit -q` → 263 passed in 0.76s with NO daemon/network (test_transport_protocol.py docstring 'a fake that | Tests mirror src and run with no real daemon/network/credentials via the provider-ABC mocks; ≥80% gate configured; integration guarded. 263 green. UPHELD. |
| DP-06 | ✅ | Security: gRPC documented TLS (base.py:9-10 'gRPC + TLS'), grpc deps pinned per SH-06 (pyproject.toml:31-36); grep Popen\|subprocess\|socket\|shell=True across src/ → NONE. Reliability: | The four concerns are first-class in the design with no defer-to-v2 holes. The per-PR-description clause is process discipline not snapshot-verifiable, but nothing in the snapshot contradicts it. UPHELD. |
| DP-07 | ✅ | Every public surface carries an intent+invariants docstring: base.py:72-83 Transport (threading model + api.md mapping), :93-104 submit_op (dedup/D-G5 invariants, two-step contract), cl | Docstrings are rich and invariant-bearing throughout; comments are why-comments. ruff D-rules not adopted but the substance is met. UPHELD. |
| DP-08 | ✅ | README.md documents 'One VmManagerClient interface, two transports' + a worked swap example and the [grpc] extra recipe; base.py:1-35 module docstring describes the three transports as  | SHOULD-level, met: the replaceability/swap recipe is documented near the abstraction. Minor README/code naming drift (transport/_base.py vs base.py) does not defeat the documented swap story. UPHELD. |
| DP-09 | ✅ | base.py:90-100 submit_op takes explicit `token: object \| None = None` ('optional cancellation token threaded to the backend'); inprocess.py threads token through _run_op→_dispatch→back | Cancellation is explicit, parameter-threaded, never a thread-global; honored cancels become a typed CANCELLED handle. UPHELD. |
| DP-10 | ✅ | Every optional-backend path loudly degrades with a typed TransportError naming the missing backend rather than returning empty: inprocess.py:1093-1096 (MetricsBackend), :1111-1115 _requ | No silent fallback; the one fallback is explicit/documented and every optional backend raises a typed loud-degrade error. UPHELD. |
| DP-11 | ✅ | grep create_task across src/ → NONE. Async offload uses owned futures with structured ownership: async_inprocess.py:180-189 future=run_in_executor + `await asyncio.shield(future)`, and  | No bare asyncio.create_task exists; the only concurrency primitive is an owned executor whose work is awaited/shielded and joined at close. RUF006/ASYNC on. UPHELD. |
| DP-12 | ✅ | inprocess.py:214 _utc_now() returns datetime.now(UTC); operations.py:119/166/183/195 use datetime.now(UTC). grep utcnow\|datetime.now() (naive) across src/ → NONE; grep datetime.now(UTC | All datetimes are UTC-aware; DTZ lint enforces it; no naive boundary timestamp. UPHELD. |
| DP-13 | ✅ | VmManagerClient is a context manager (__enter__/__exit__→close), AsyncVmManagerClient async-CM (__aenter__/__aexit__→aclose). InProcessTransport.close idempotent releasing backends (inp | Every owned external resource (backends, executor threads, watch subscriptions, locks) is released deterministically via context managers / idempotent close / try-finally; no GC reliance. UPHELD. |

### ERROR_HANDLING

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| EH-01 | ✅ | All raise sites in src/ are typed VmManagerError(=AppError) subclasses: TransportError (inprocess.py:424,440,457,564,578,618,628,699,979,986,1039,1045,1068,1094,1115,1132,1156,1204,1265 | Upheld. Every application-raised exception subclasses the project base (AppError via VmManagerError); no bare/third-party raise across an internal API. |
| EH-02 | ✅ | The seam translates non-AppError types: InvalidTransition is a PLAIN Exception subclass (mgf-vm-core/state_machine.py:83 'class InvalidTransition(Exception)', NOT in the AppError tree)  | Upheld and strengthened: the refute attempt found InvalidTransition is a genuine plain-Exception (not typed) that IS translated at the boundary. Every untyped exception crossing the seam is translated; already-typed core errors pr |
| EH-03 | ✅ | src/mgf/vm/sdk/transport/inprocess.py:440 `raise TransportError(f"unknown operation {op_id}") from exc`; inprocess.py:457 same shape (KeyError→TransportError, both chain `from exc`); sr | Every place the SDK *translates* a foreign/standard exception into a typed one and re-raises it chains the cause with `from exc`. The non-chained paths are intentional verbatim re-raises of the original exception (not translations |
| EH-04 | N/A | No entry point exists: pyproject.toml declares no `[project.scripts]` console script ("NO console script (an SDK, consumed by CLI+GUI)"); src/mgf/vm/sdk has no `__main__.py`, no `cli/`, | EH-04's premise is an entry point (CLI/process/thread/asyncio/Qt). A UI-free client library is imported into a host process that owns its own top-level handlers; the SDK installing process-wide hooks would be a defect. Premise doe |
| EH-05 | N/A | No entry point and no crash-reporter wiring: no import of `mgf.common.observability.crash_reporter` / `report_now`, no state-dir crash JSON writer, no excepthook (grep across src/ finds | EH-05 binds the owner of unhandled-exception capture at an entry point. A library has no unhandled-exception boundary of its own — it propagates typed exceptions to the host (CLI/GUI), which owns the crash report. Premise does not |
| EH-06 | N/A | No GUI code in the package: no `QThread`/`CoreWorker`/`failed.emit`/PySide6 import anywhere under src/mgf/vm/sdk (the package is client/operations/queries/transport modules only). The S | EH-06 binds GUI worker threads. This is the library the GUI imports, not the GUI; the worker-error pipeline lives in the GUI consumer. Premise does not bind. |
| EH-07 | ✅ | grep 'except Exception' over src -> single hit at _devices.py:19, inside the module DOCSTRING (literal code block, lines 1-43), confirmed not executable. The only broad runtime catch is | Upheld. No swallowing 'except Exception:' in executable code; the broad catch re-raises and the suppress is on a typed exception in a documented best-effort path. |
| EH-08 | ✅ | mgf-vm-core/exceptions.py: every contracts-aligned class declares machine-readable RETRYABLE: bool (lines 119,264,270,276,290,296,306,312,321,328,334,344,354,360,370,376,382,388...); Sp | Upheld. Typed exceptions carry machine-readable constructor/class fields, not only a message string. |
| EH-09 | ⏳ | SDK-originated messages name what is wrong but stop short of an explicit next step and are not guarded by a substring test: inprocess.py:1094 "read 'SampleMetrics' needs a MetricsBacken | Judged for a library: the SDK's own diagnostics are developer-facing and *near* actionable (they name the missing dependency to inject) but do not append an explicit next-step suffix, and §9 requires each hint be pinned by a subst |
| EH-10 | ✅ | The SDK does not own an observer fan-out loop — `LifecycleObserver`s are registered on the per-VM state machine (inprocess.py:348-349 `sm.add_observer(observer)`) and fired inside `mgf. | EH-10 binds best-effort observer/cleanup code so it never raises past its boundary. The cross-cutting observer loop lives in mgf-vm-core; the SDK's only best-effort cleanup (rollback `release`) correctly suppresses its own failure |
| EH-11 | N/A | No retry/backoff/jitter/sleep/reconnect logic anywhere in src/ (grep for retry\|backoff\|jitter\|time.sleep\|reconnect finds only doc-comments about state-machine FAILED being a 'legal  | EH-11's premise is the existence of a retry loop ("Retries MUST use exponential backoff..."). The Phase-0 in-process SDK has no retry mechanism — there is no network to retry against — so the rule does not bind. (If/when the gRPC  |
| EH-12 | ✅ | OperationRegistry.submit dedups on the caller-supplied client_op_id natural key: if seen before it returns the existing handle unchanged without re-running work (operations.py:113-123); | Upheld. Writes are idempotent: submit dedups on the natural client_op_id key and lifecycle semantics make repeat ops produce the same result. |
| EH-13 | ✅ | grep 'retry\|backoff\|for _ in range\|while True' over inprocess.py -> NO retry loop (only docstring mentions of 'a later retry starts from a legal state' referring to the consumer-driv | Upheld. The SDK issues at most one call per request (1x amplification ceiling), the documented bound the rule permits in place of a breaker; 'no breaker AND no recorded bound' does not apply since the single-attempt design IS the  |

### LOGGING

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| LG-01 | ⏳ | grep -rE 'import logging\|getLogger' src/ returns ZERO hits across all 10 modules (client.py, operations.py, queries.py, transport/{base,inprocess,async_inprocess,_devices,_storage}.py) | LG-01 ('every module MUST acquire its logger via getLogger(__name__); no bare logging.info(...)') is vacuously not-violated because no module logs at all — but the SDK crosses process/storage/(future)network boundaries and emits n |
| LG-02 | N/A | pyproject.toml has no [project.scripts] console entry; client.py docstring (lines 1-33) states 'CLI and GUI import only from here' — it is a library imported BENEATH both presentation l | LG-02 binds the 'application ... configure logging in exactly one place per entry point (CLI main, GUI app.run, ...)'. This SDK has no entry point of its own; the CLI and GUI that import it own the single setup point (and the JSON |
| LG-03 | N/A | No log records are emitted anywhere in src/ (zero getLogger hits), and the SDK ships no formatter — STANDARDS_NOTE.md:57-58 explicitly defers 'the formatter + redactor are the consumer' | LG-03 ('every log record MUST carry trace_id/span_id ... The standard formatter adds these automatically') binds the formatter that the entry-point's setup_logging installs. The SDK emits no records and installs no formatter; trac |
| LG-04 | N/A | No redactor and no formatter in src/ (zero logging). STANDARDS_NOTE.md:57-58: 'the formatter + redactor are the consumer's concern.' LOGGING.md:7 + §6.2 place Redactor in mgf.common.obs | LG-04 ('sensitive fields MUST be redacted by the formatter before any sink sees them ... redaction set MUST be configurable') binds the formatter/redactor installed at the entry point. The SDK emits nothing and owns no sink; the c |
| LG-05 | ⏳ | The transports catch and re-raise typed errors at the boundary (inprocess.py 'Error bridge' docstring lines 37-43; _devices.py:179 'except BaseException', :217-220 'except VmManagerErro | LG-05 ('ERROR-level logs MUST include the exception via LOG.exception(...) or exc_info=True'). The error-bridge sites in the in-process transport and _devices.py are exactly the boundary-failure points the rule targets (a backend  |
| LG-06 | ⏳ | Zero log emission in src/ and no sink configured. README.md:12 declares the project L2 — Standard. The matrix (LOGGING.md:47) places 'library' on the rotating-file branch of LG-06; the  | LG-06 ('logs MUST reach an aggregator the operator can search ... a console-only, no-aggregation configuration is non-compliant in production'). The SDK crosses in-process/storage and (future) gRPC network boundaries but emits not |
| LG-07 | N/A | Per the per-kind matrix (LOGGING.md:21, :44) LG-07 is gui-app only. This package is a UI-free client SDK (client.py:1-33 'CLI and GUI import only from here'); grep for PySide6/PyQt/tkin | LG-07 ('GUI apps MUST ship a log-viewer dialog') binds gui-app kind. The SDK has no GUI (and PR-01 requires it stay UI-free so both CLI and GUI can import it). The log-viewer dialog is the importing GUI's responsibility, not the S |
| LG-08 | N/A | No log call sites exist in src/ (zero getLogger / .info( / .warning( hits), so there is no message string that could be eager-f-string vs lazy %-formatted. | LG-08 ('log messages MUST use lazy %-style formatting, NOT f-strings') binds the form of log call sites. With zero log sites the premise has nothing to bind. Vacuously not-violated. (When LG-01/LG-11 are addressed by adding record |
| LG-09 | N/A | No log records and no extra={...} usage anywhere in src/ (grep 'extra=' over src/ returns zero). | LG-09 ('structured fields MUST be passed via extra={}, not concatenated into the message; the JSON formatter promotes them') binds the shape of emitted records. The SDK emits none. Premise does not bind. The substantive gap (no st |
| LG-10 | N/A | Per-kind matrix (LOGGING.md:24, :45) LG-10 is service kind only (systemd timers/daemons/JournalHandler). This is a library with no entry point (pyproject.toml has no [project.scripts];  | LG-10 ('headless services MUST configure logging at their entry point with JournalHandler') binds service-kind processes. The SDK is imported by the CLI/GUI; it is neither a daemon nor a systemd unit and owns no entry point. Genui |
| LG-11 | ⏳ | client._run (client.py:209-223) submits + watches every MUTATING op; the in-process transport runs work that crosses into mgf.vm.core backends (inprocess.py 'Dispatch is keyed on Operat | LG-11 ('operation start/end MUST be logged with a stable operation name at INFO for any function that crosses a process/network/storage boundary, so flows are reconstructable from logs alone'). Every mutating method here crosses a |
| LG-12 | N/A | No log records emitted in src/; no custom handler or call-site formatting exists to measure (zero logging). | LG-12 ('log emission MUST be cheap (≤100µs/record); heavy formatting/IO in the handler not the call site') constrains the cost of emitting records and the handler. The SDK emits no records and ships no handler (handlers belong to  |
| LG-13 | N/A | No high-frequency log sites exist (zero logging in src/). The watch_stream / watch_op iterators (base.py:115-144) yield domain events but emit no per-event log records. | LG-13 ('high-frequency log sites MUST use rate-limiting/sampling') binds sites that actually emit per-event/per-tick records. The SDK's event streams yield data to the caller without logging each item, so there is no flood-prone l |
| LG-14 | N/A | No log records and no correlation contextvar usage in src/ (grep contextvars over src/ — only async_inprocess.py uses asyncio/ThreadPoolExecutor for execution, not correlation-field pro | LG-14 ('custom correlation context MUST propagate through contextvars.ContextVar; the standard formatter promotes registered contextvars to log fields') binds correlation fields that get promoted onto emitted records by the consum |
| LG-15 | ⏳ | The SDK has clear security/lifecycle/boundary-failure events that the standard's own example table names (LOGGING.md:246-249 lists e.g. exec.user, device/lease events, boundary failures | LG-15 ('every INFO+ record at a notable lifecycle/security/boundary event MUST carry a stable event field in <subject>.<action> form ... documented in docs/reference/EVENTS.md'). Notable boundary/lifecycle events exist (op failed, |
| LG-16 | ⏳ | The error-bridge re-raise sites (inprocess.py error-bridge lines 37-43; _devices.py:217-220 raise DeviceDetachFailed with a message; _storage.py:81/_devices.py:118,150 raise TransportEr | LG-16 ('every WARNING and ERROR record MUST answer WHAT/WHY/HOW — past-tense message, structured extra={} context, actionable hint'). The SDK reaches genuine error states (op FAILED, device detach needs-human, no-backend-configure |
| LG-17 | ⏳ | No ERROR records and thus no fingerprint field anywhere (zero logging in src/). Re-raised typed exceptions (TransportError/DeviceDetachFailed/VmManagerError tree, base.py:43-48) are exa | LG-17 ('every ERROR record SHOULD carry a stable fingerprint ... the formatter computes it automatically when exc_info is set'). It is a SHOULD (LOGGING.md:458 'LG-17 is a SHOULD (not a MUST)'). Because the SDK logs nothing, the a |

### CONFIGURATION

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| CF-01 | N/A | grep for BaseSettings\|BaseAppSettings\|pydantic_settings\|os.environ\|getenv across src/ + tests/ returns ZERO matches; src/mgf/vm/sdk/__init__.py:35 __all__ exports only clients + tra | CF-01 is scoped 'consumer-owned-config' (Rules box L23). This SDK owns no config schema: it is constructed purely by injection — VmManagerClient(transport) (client.py:181) and InProcessTransport.from_adapters(...). No module reads |
| CF-02 | N/A | No layered sources exist: no settings_customise_sources, no env_settings, no .env, no YAML loader anywhere in src/ (grep for pydantic_settings/.env/XDG/config.yaml = 0 matches). Transpo | CF-02 (precedence chain CLI>env>.env>file>defaults) is scoped 'all', but a precedence chain only has meaning where multiple config sources are merged into a typed object. This SDK merges nothing — it receives a fully-constructed T |
| CF-03 | N/A | No config schema and no 'version' config field; grep for BaseSettings/load_settings/_migrate = 0. (The only __version__ is the package version string in __init__.py:33, not a config-sch | CF-03 is scoped 'consumer-owned-config' (Rules box L25). A schema-version + migration chain requires an on-disk config schema to version; this SDK has none. Premise does not bind. |
| CF-04 | N/A | No on-disk config writes anywhere; grep for save_settings/write_text/atomic_write/.tmp in src/ = 0 config-write matches. The SDK never persists state — it only dispatches ops through a  | CF-04 (atomic write, 0o600, lazy path expansion) is scoped 'consumer-owned-config' (Rules box L26). The SDK writes no config or state file of its own, so there is nothing to write atomically and no path to expand. Premise does not |
| CF-05 | N/A | No secrets, credentials, passwords, TLS material, or Vault interface in the SDK; grep for secret\|password\|credential\|vault\|keyring\|tls in src/mgf/vm/sdk/ finds only 'token' which i | CF-05 (secrets in a Vault, not in config) is scoped 'all', but it binds only where the consumer handles secrets. This SDK handles none: in-process transports talk to injected mgf.vm.core adapters with no credentials, and there is  |
| CF-06 | N/A | No config schema, so no extra='forbid'/unknown-key path; no model_validate of a config dict anywhere (grep = 0). | CF-06 (unknown-key rejection) is scoped 'consumer-owned-config' (Rules box L28). It requires a typed config model parsing an on-disk dict; the SDK has neither. Premise does not bind. |
| CF-07 | N/A | No sub-config / satellite files (hooks/schedules/alerts/profiles/lab specs) exist or are loaded anywhere in the SDK; the 10 source modules are client/transport/query machinery only. | CF-07 (sub-config files follow the same typed+atomic+versioned shape) is scoped 'consumer-owned-config' (Rules box L29). The SDK owns no satellite config files. Premise does not bind. |
| CF-08 | N/A | No module-global Settings singleton and no load-once-at-entry-point logic; the SDK has no entry point (no console script per pyproject; it is imported BY the CLI/GUI). Clients are expli | CF-08 (Settings loaded once at the entry point, passed by constructor injection; no module-global singleton) is scoped 'consumer-owned-config' (Rules box L30) and is an entry-point/wiring rule. This SDK is a library with no entry  |
| CF-09 | N/A | No CLI exists: __init__.py exports only clients/transports; pyproject defines no console script (the SDK is consumed by the CLI+GUI, not a CLI itself). grep for click/argparse/--config  | CF-09 (a --config flag must always exist; default path honours XDG/OS conventions) is scoped 'consumer-owned-config' AND presupposes a CLI process with argv. This SDK has no CLI of its own (per the stated shape). A library cannot  |
| CF-10 | N/A | There is no Settings class to construct in tests; tests construct VmManagerClient against InProcessTransport / FakeGrpcTransport with explicit fakes (conftest.py FakeGrpcTransport; test | CF-10 (tests construct Settings with explicit values, no env leakage) is scoped 'all', but it binds only where a Settings object exists to construct. The SDK has none. The underlying discipline it protects (no env-var leakage into |
| CF-11 | N/A | No config fields exist, hence no field to rename and no Field(alias=...)/validation_alias machinery; grep for AliasChoices/validation_alias = 0. | CF-11 (rename a config field via alias + deprecation warning) is scoped 'consumer-owned-config' (Rules box L33). It requires on-disk config fields that could be renamed; the SDK has none. Premise does not bind. |
| CF-12 | N/A | No config-level mutually-exclusive backend variants modelled as a discriminated union; grep for discriminator/Annotated Union with kind= in config context = 0. The transport seam IS a p | CF-12 (mutually-exclusive config backend variants modelled as a discriminated union with a 'kind' field) is scoped 'all', but it binds only when the consumer has such variants IN ITS CONFIG. This SDK selects its transport by passi |

### SECURITY_STANDARD

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| SC-01 | N/A | grep across src/ for password/token/secret/api_key/vault returns no secret-bearing code; the SDK holds no credentials. SECURITY.md:15-21 documents it as a pure-Python domain library tha | Secrets-in-vault has no surface: the SDK stores, reads, or transmits no secret material. The credential boundary lives in the adapter libraries and privileged daemons. N/A by shape. |
| SC-02 | N/A | grep -rn 'subprocess\|shell=True\|os.system' src/ returns NONE. The SDK never spawns a process; the in-process transports call typed backend methods directly. | No subprocess invocation anywhere in the library. The argv-only rule has no call site. N/A. |
| SC-03 | ✅ | [verify-upheld] inprocess.py:702 `src = vmspec_from_mapping(json.loads(spec_json))` is the SOLE deserialization in src/ (grep for yaml/pickle returns NONE; grep json.load returns only t | Refute attempt FAILED → status kept ✅. SC-03 forbids yaml.load and pickle and requires json.loads for JSON from outside the app's own writes. The SDK uses neither yaml nor pickle, and its one cross-boundary deserialization uses js |
| SC-04 | N/A | grep for chmod/write_text/write_bytes/open('w')/atomic in src/ returns only a comment ('lazy create-on-first-use … is itself atomic', inprocess.py:282) — no actual file write. SECURITY. | The SDK writes no private-data files (archive/disk writes are delegated to the injected HostDiskBackend, an external adapter). 0o600/atomic-write rule has no in-library surface. N/A. |
| SC-05 | N/A | grep for cryptography/Fernet/hashlib/secrets/random./md5/sha1 across src/ returns NONE. SECURITY.md confirms no crypto in scope. | The library uses no cryptographic primitives. No DIY crypto, no weak hashes, no PRNG-for-security. N/A by shape. |
| SC-06 | N/A | grep for verify=False/CERT_NONE/AutoAddPolicy/ssl in src/ returns NONE. The only TLS reference is documentation of the FUTURE GrpcTransport ('gRPC + TLS', __init__.py:7-8, base.py:9-10) | No outbound TLS connection exists in the shipped code. When the GrpcTransport lands it must verify TLS; today there is no connection to verify. N/A for the current shape (re-evaluate when gRPC is implemented). |
| SC-07 | N/A | grep for timeout in src/ matches only `TransportTimeout` exception-name re-exports in base.py (lines 47,67) — no HTTP/SSH/DB/subprocess call. The implemented transports run in-process;  | There is no external request in the implemented code that could block forever — in-process calls are bounded by the injected backend, and the seam threads a CancellationToken. The gRPC network boundary (which would need a finite t |
| SC-08 | ✅ | [verify-upheld] queries.py defines frozen typed dataclasses per request shape. inprocess.py narrows every payload at the transport boundary via typed helpers that raise TransportError o | Refute attempt FAILED → status kept ✅. SC-08 requires untrusted input validated at the boundary, typed, before use-case code — 'the boundary is the parser (IPC reader), NOT the use case.' The transport seam is type-erased (object) |
| SC-09 | N/A | The SDK has no log sink and emits no log records carrying user data. grep for redact/LOG./logger/getLogger in src/ returns no logging calls (only doc/comment uses of the word 'audit' in | No formatter, no log sink, no sensitive-field emission in the library. Redaction has no surface. N/A by shape. |
| SC-10 | ✅ | uv.lock IS committed (206 KB, present at repo root). BUT there is NO CI pipeline: no .woodpecker.yml or any CI config exists (find/ls returns nothing), and grep for pip-audit/gitleaks/o | Half-met: the lockfile half is satisfied, the vuln-scan-on-CI half is unmet because no CI exists at all. The rule's MUST (scanner on every PR) is not satisfied. Unmet gap, not a decline → ⏳. — FIXED 2026-06-09: RECONCILED ✅: the vuln-scan-on-CI half is now met (this batch's pip-audit --strict step); lockfile half already satisfied |
| SC-11 | N/A | The SDK has no crash reporter and no secret material; grep confirms no secrets in code. The seam's error bridge attaches the ORIGINAL typed VmManagerError without re-wrap (inprocess.py: | Traceback-secret-scrubbing has no surface: there is no secret material that an exception could carry, and no crash reporter to scrub. N/A by shape. |
| SC-12 | ✅ | [verify-upheld] SECURITY.md exists at repo root (ls confirms, 1780 bytes). It has a 'Reporting a vulnerability' header (line 3) with a private channel (email bassam@magogi.org with subj | Refute attempt: the missing supported-versions table is a real omission against the template, but the substantive MUST clauses of SC-12 — root file present, private security contact, embargo/disclosure expectation, timeline — are  |
| SC-13 | N/A | grep for cryptography/key/KDF/PBKDF2/Argon2/keyring in src/ returns NONE. No cryptographic keys at rest anywhere in the library. | The SDK holds no cryptographic keys. Key-at-rest-via-KDF rule has no surface. N/A by shape. |
| SC-14 | N/A | The SDK performs no authentication: no password/key check, no login surface (grep for auth/password/login returns no auth logic; 'audit' uses are op-id namespacing comments). Identity/a | No authentication boundary in the library, so the no-enumeration / rate-limit / no-permanent-lockout rule has no call site. N/A by shape. |
| SC-15 | N/A | grep for .resolve()/is_relative_to/os.path/Path( in src/ returns NONE — the SDK constructs no filesystem path from external input. Archive extraction and disk-path handling are delegate | No path is built from external input inside the library; filesystem operations are owned by the injected backend adapter. Path-traversal validation has no in-library surface. N/A by shape. |
| SC-16 | N/A | grep for sudo/setuid/privileged/capability in src/ returns NONE. SECURITY.md:17 states the library 'does NOT execute privileged operations'; the privileged surface lives in vm-vmanager- | No privileged operation in the library. The narrow-audited-surface rule has no call site. N/A by shape. |
| SC-17 | N/A | The SDK emits no log records at all (grep for LOG./logger/getLogger/logging.* finds no logging calls in src/). It performs none of the SC-17 security-relevant actions (vault r/w, subpro | There is no security-relevant action in this library to audit-log, and no logging facility. Audit-record rule has no applicable event. N/A by shape (audit of VM operations themselves is owned by the agent/control-plane per _device |
| SC-18 | ✅ | The package is published as mgf-vm-sdk 0.1.7 and a dist/ dir exists, but there is NO CI pipeline (no .woodpecker.yml or any CI config) to build reproducibly from a tagged commit, and no | Per the prompt's carve-out, this IS a published mgf-* package so publish-chain rules are ⏳ gaps when there's no pipeline, NOT N/A. uv.lock supports buildability but the reproducible-build/CI-verification machinery is absent → unme — FIXED 2026-06-09: .woodpecker sbom step generates a reproducible CycloneDX SBOM (dist/mgf-vm-sdk.cdx.json) |

### TESTING

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| TS-01 | ✅ | src/mgf/vm/sdk/ has 8 non-__init__ modules (client, operations, queries, transport/{base,inprocess,async_inprocess,_devices,_storage}). tests/unit/mgf/vm/sdk/ is organized by CONCERN, n | TS-01 MUSTs a 1:1 src->tests/unit mirror AND a CI lint that flags source files lacking a mirror beyond a documented allow-list. The suite is thorough but organized by concern rather than by mirrored module name, and there is no mi — FIXED 2026-06-09: tests/test_mirror.py + MIRROR_ALLOWLIST.txt (topic-named tests allow-listed) |
| TS-02 | ✅ | [verify-upheld] Verified live: .venv/bin/python -m pytest tests/unit -q => 263 passed in 0.76s, no network/daemon. import-linter contract 'SDK does not import libvirt or engine/wire bin | Upheld. Unit suite runs with no prod creds, no network, no real third-party daemon; external I/O reached only through the Transport seam against core fakes; import-linter mechanically bars real backend leaks into src/. |
| TS-03 | ✅ | [verify-upheld] Suite passes in a single 0.76s run; tests build fresh InProcessTransport.from_adapters(...) per test (function scope default). No HOME/XDG/chdir mutation in tests/ (grep | Upheld. Per-test transports; the only process-global resource (executor threads) is explicitly drained and asserted clean. --strict-config is active (the wrong-venv run errored on Unknown config option asyncio_mode); no order-depe |
| TS-04 | ✅ | [verify-upheld] pyproject.toml [tool.pytest.ini_options] filterwarnings = ["error"]. The 263-test unit suite passes verbatim under it (no escalated deprecations/ResourceWarnings). | Upheld verbatim: filterwarnings=["error"] is set and the suite is green under it. |
| TS-05 | ✅ | [verify-upheld] pyproject.toml addopts = "--strict-markers --strict-config". Proven live: running under the wrong venv (no pytest-asyncio) produced INTERNALERROR 'Unknown config option: | Upheld verbatim and proven active. |
| TS-06 | ✅ | Measured locally: .venv/bin/python -m pytest tests/unit --cov=mgf.vm.sdk => TOTAL 98.12% (well above the 80 floor). pyproject [tool.coverage.report] fail_under = 80 is set. HOWEVER: add | TS-06 MUSTs the coverage gate >=80% ON EVERY CI JOB. Actual coverage is excellent (98%), but with no CI pipeline the gate is unenforced. Gap pending the publish/CI pipeline, not a decline. — FIXED 2026-06-09: CI pytest runs --cov=mgf.vm.sdk (fail_under in [tool.coverage.report]) |
| TS-07 | N/A | grep for 'class .*Error' / 'class .*Exception' in src/mgf/vm/sdk/ returns NONE — the SDK defines no exception types of its own. transport/base.py re-exports mgf.vm.core.exceptions.Trans | TS-07 requires every typed exception OWNED by the project to have a message/field-preservation unit test. The SDK owns no exception types; the upstream owner (mgf-vm-core) is responsible. The SDK's job — translating them at the se |
| TS-08 | ✅ | [verify-upheld] inprocess.py error bridge (verified ~492-516): except OperationCancelled -> mark_cancelled; except InvalidTransition -> DomainStateInvalid; except VmManagerError -> mark | Upheld. Every translation branch of the seam is pinned (success + each known typed translation + the catch-all unknown path), sync and async. |
| TS-09 | ✅ | [verify-upheld] test_devices.py:270-290 _ReleaseFailsDev injects release() raising BrokerTransientError; test_attach_rollback_swallows_release_failure_and_reraises_original asserts the  | Upheld. The binding best-effort surface (attach-rollback chain) is pinned with an injected raising cleanup step; the loop/chain continues and the original error propagates. (A separately-raising LifecycleObserver is not independen |
| TS-10 | N/A | grep for tls/ssl/credential/secret/password/auth in src/mgf/vm/sdk/ returns only Phase-1 DOCSTRINGS ('control plane over gRPC + TLS') — no actual TLS/auth/credential code exists in the  | TS-10 pins SC-01..18 'where mechanically possible'. Phase 0 ships no auth/crypto/credential/TLS mechanism to pin; the relevant no-leak invariant is import-linter-enforced. N/A for the current surface (becomes a real gap when the P |
| TS-11 | N/A | This is a client SDK with NO GUI of its own (it is imported BY the CLI + GUI). grep for PySide6/PyQt/QThread/CoreWorker in src returns NONE; PR-01 verified clean (no UI-framework import | TS-11 governs GUI workers (QThread/CoreWorker untyped-exception path). The SDK has no GUI and no workers. Per the stated carve-out, GUI-rendering rules are N/A. |
| TS-12 | ✅ | [verify-upheld] pyproject.toml pytest-asyncio dep + asyncio_mode="auto". test_async.py covers create/start/stop, clone/backup/restore, network, reads round-trips, error-bridge (142), ge | Upheld thoroughly — pytest-asyncio in use, cancellation pinned including a real cross-thread token-observed cancel, per-test loop, and a no-leaked-threads aclose test. |
| TS-13 | ⏳ | grep for hypothesis/@given/import hypothesis across tests/ returns NONE — no property tests exist. hypothesis IS listed in [dev] deps (pyproject.toml:40). queries.py defines ~50 round-t | TS-13 MUSTs property tests for code that round-trips data, with strategies in a _strategies.py. The SDK has serialisable payload/handle surfaces and ships hypothesis as a dev dep, but no property tests or strategies module. Gap, n |
| TS-14 | ⏳ | grep for @pytest.mark.perf / perf_span / mark.perf across tests/, src/, pyproject.toml returns NONE — no benchmark and no perf marker. The transport seam (per-op dispatch, two-step hand | TS-14 MUSTs performance budgets pinned by a @pytest.mark.perf benchmark against the production path. None exists, and 'perf' is not even a registered marker. Gap (L1 MAY defer perf, but no decline is recorded), so it remains ⏳. |
| TS-15 | ✅ | [verify-upheld] tests/integration/test_libvirt_lifecycle.py: pytestmark = pytest.mark.integration (registered in pyproject markers); LibvirtHypervisorBackend = pytest.importorskip('mgf. | Upheld. Real-backend tests are marker-gated, skip gracefully when the binding/driver is unavailable, run against a sacrificial scratch driver, and clean up via try/finally — matching the §12 TS-15 recipe. |
| TS-16 | ✅ | [verify-upheld] test_async.py:327 assert token is not None, "async transport must thread a token through". Targeted single-concept asserts on equalities/isinstance throughout (e.g. test | Upheld. Failure messages are self-explanatory: bare asserts are on unambiguous equality/isinstance checks and the ambiguous ones carry context strings. |
| TS-17 | ✅ | [verify-upheld] FakeGrpcTransport lives in tests/conftest.py at the repo-test root and is a PLAIN CLASS (no @pytest.fixture decorator — verified: conftest.py defines only class methods, | Upheld. TS-17 governs FIXTURES discovered via the conftest hierarchy never being IMPORTED across test packages. FakeGrpcTransport is not a fixture — it is a shared stub class legitimately placed in the top-level conftest and impor |
| TS-18 | ⏳ | grep for 'slow' in pyproject.toml returns NONE — the slow marker is NOT registered. Combined with --strict-markers (TS-05 active), any future @pytest.mark.slow would be rejected. No tes | No test is currently slow, so the substantive rule is vacuously met — but TS-19/§13.3 SHOULD-register 'slow' defensively, and it is absent. Marked ⏳ because the defensive-registration guidance is unmet (and strict-markers would re |
| TS-19 | ✅ | pyproject.toml [tool.pytest.ini_options].markers registers exactly two: 'grpc' and 'integration' (both used). 'slow' and 'perf' are NOT registered. No 'smoke'/'e2e' markers registered e | TS-19 MUSTs every marker the tests USE be declared (met for grpc+integration) AND SHOULD-registers the orthogonal 'slow'/'perf' axes defensively. The two in-use markers are correctly registered, but the recommended slow/perf regis — FIXED 2026-06-09: smoke/e2e/slow/perf markers registered in [tool.pytest.ini_options] |
| TS-20 | ✅ | tests/ has unit/ and integration/ (both populated and correctly marked). There is NO tests/e2e/ and NO tests/smoke/ directory (verified: 'NO tests/smoke dir'). The 'smoke' test that exi | TS-20 MUSTs partitioning across the four canonical directories (unit/integration/e2e/smoke). Two of four exist. e2e is arguably subsumed by the integration libvirt round-trip, but the smoke LAYER is required for a published librar — FIXED 2026-06-09: tests/smoke/ + tests/e2e/ added (unit+integration already present) → four canonical dirs |
| TS-21 | ✅ | mgf-vm-sdk IS a published wheel (mgf-vm-sdk 0.1.7, hatchling, packages=[src/mgf]). The only smoke test is tests/unit/mgf/vm/sdk/test_smoke.py — it is in the UNIT layer (not tests/smoke/ | TS-21 MUSTs a published library have a smoke LAYER (tests/smoke/, <100ms, smoke-marked) verifying import + every __all__ name reachable + each public entry's shortest happy path. The existing test_smoke.py does not meet the shape — FIXED 2026-06-09: tests/smoke/test_imports_smoke.py (fast canonical smoke) |
| TS-22 | ⏳ | grep for test_supervisor/mtest/MARKER_TIER/tier across pyproject.toml, tests/, .gitignore returns NONE. No mgf-test-supervisor in dev deps; no --tier/--budget support; no tier markers.  | TS-22 MUSTs tiered runs (the federation coordination contract). The supervisor is not adopted and no equivalent mechanism is recorded. Gap, not a decline. |
| TS-23 | ⏳ | No CI pipeline exists at all (no .woodpecker.yml/.gitlab-ci.yml/.github). No mgf-test-supervisor adoption, no subprocess crash/hang/signal capture, no faulthandler_timeout/timeout confi | TS-23 MUSTs every CI run survive+structure pytest crash/hang/signal-death via the supervisor or a demonstrably-equivalent harness. There is no CI and no supervisor. The explicitly-forbidden state (plain pytest with no crash/hang/s |
| TS-24 | ⏳ | No .gates/last-run/run.jsonl emission; no TestRecord usage (grep NONE). No supervisor to write structured per-test records. | TS-24 MUSTs structured per-test JSONL records via mgf.common.testing.TestRecord. Not implemented (follows from the absent supervisor). Gap, not a decline. |
| TS-25 | ⏳ | No .gates/quarantine.json, no --quarantine-attempt support, no supervisor (grep NONE). | TS-25 is L2 (L1 projects MAY defer). It is unimplemented and no deferral rationale is recorded in this fresh adjudication. Marked ⏳ (a recorded L1-defer would also be acceptable, but none is asserted by evidence). |
| TS-26 | N/A | mgf-vm-sdk ships NO compiled artifact — it is pure Python (hatchling wheel of src/mgf). The optional [grpc] extra pulls grpcio (which ships its own wheels) but the SDK builds/depends on | TS-26 explicitly allows pure-Python libraries to declare it N/A. The SDK has no compiled artifact whose mtime must be checked. Genuinely N/A. |
| TS-27 | ⏳ | No .gates/last-run/failures/<id>.txt bundle mechanism; no supervisor (grep NONE). | TS-27 MUSTs single-file per-failure bundles, produced by the supervisor. Not implemented (follows from the absent supervisor). Gap, not a decline. |
| TS-28 | ⏳ | No --perf/MGF_PERF capture, no perf_span usage in src or tests, no perf-summary.md, no supervisor (grep NONE). | TS-28 is L2 (L1 projects MAY defer). Per-test perf attribution is unimplemented and no deferral rationale is recorded in this fresh adjudication. Marked ⏳ (a recorded L1-defer would also be acceptable). |

### API_DESIGN

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| AP-01 | ✅ | __init__.py:35-44 __all__ (sorted==given incl __version__, strings-only, verified by sort check); transport/__init__.py:13; client.py:141; operations.py:50-56; queries.py:29 (40+ names) | Refute attempt failed: every package __init__ and consumer-facing module declares an explicit, alphabetically-sorted (programmatically confirmed), strings-only __all__; private impl modules correctly omit it. Upheld. |
| AP-02 | ✅ | AST scan of queries.py: 0 classes missing docstring (all query dataclasses documented). client.py: every public method carries intent + RPC name + Raises (get_vm:251-257, create_vm:447- | Refute attempt failed: programmatic AST check confirms zero missing docstrings on the public surface; docstrings state intent, underlying RPC, idempotency, and typed exceptions. Upheld. |
| AP-03 | ✅ | pyproject.toml:3 version="0.1.7"; __init__.py:33 __version__="0.1.7" (match); CHANGELOG.md:1-4 declares Keep-a-Changelog + SemVer; history 0.1.0..0.1.7 with PATCH pin-walks (0.1.2/0.1.7 | Refute attempt failed: SemVer in the 0.x window is followed, version is a clean release form, CHANGELOG declares the contract. Upheld for a pre-1.0 library. |
| AP-04 | N/A | grep for DeprecationWarning\|warnings.warn\|@deprecated across src/ and tests/ returns nothing; CHANGELOG.md has no [Deprecated] entries through 0.1.7 — every release is additive (new v | AP-04 binds the deprecation CYCLE: a deprecated name MUST emit DeprecationWarning over >=1 MINOR then be removed. The premise (a name being deprecated) does not yet exist — the SDK has shipped no deprecations through 0.1.7. Mirror |
| AP-05 | ✅ | client.py:181 __init__(transport, *, client_id=None); stop_vm:485 (*, force=False); delete_vm:463 (*, delete_storage=False); clone_vm:543-549 (*, dest_dir, linked=False); restore_vm:569 | Refute attempt failed: every optional/added parameter beyond the leading positional is keyword-only with a default via the * separator, consistently across both sync and async surfaces. Upheld. |
| AP-06 | ✅ | client.py return annotations: list_vms->list[Vm] (261), get_vm->Vm (250), list_pools->list[PoolInfo] (283), get_autostart->bool (368), watch_vm->Iterator[VmEvent\|EventGap] (426), guest | Refute attempt failed: public return types are pinned narrowly to concrete types; no widening to Iterable; mypy --strict is the matrix-accepted proxy. Upheld by inspection. |
| AP-07 | ✅ | transport/base.py:43-48 re-exports core TransportError tree; client.py:233-244 _unpack returns handle.result on SUCCEEDED and 'raise handle.error' (the ORIGINAL core exception) on FAILE | Refute attempt failed (ruthless check requested): the transport seam genuinely attaches the original typed core exception to the FAILED handle without re-wrapping, and _unpack re-raises it verbatim. Exceptions are documented in do |
| AP-08 | ✅ | Verified 'from __future__ import annotations' present in ALL 10 modules (grep over find src -name '*.py'): __init__, client, operations, queries, transport/{__init__,base,inprocess,asyn | Refute attempt: the cited enforcement (TCH) is technically the flake8-type-checking family, which moves type-only imports into TYPE_CHECKING but does NOT enforce the presence of 'from __future__ import annotations' (that is FA100/ |
| AP-09 | ✅ | src/mgf/vm/sdk/py.typed exists and is 0 bytes (PEP 561 marker, verified wc -c). pyproject.toml:64-65 [tool.hatch.build.targets.wheel] packages=["src/mgf"] — the tree walk ships py.typed | Refute attempt failed: the PEP 561 py.typed marker is present at the package root, hatchling's packages=[src/mgf] walk ships it, and the package is mypy --strict clean. Upheld. |
| AP-10 | ✅ | ls PUBLIC_API.md / docs/PUBLIC_API.md / PUBLIC_API.json all 'No such file'; grep -rln 'PUBLIC_API' over *.py/*.md/*.toml (excl .venv) returns nothing. The public surface is enumerated o | AP-10 is a MUST for any published library ('A PUBLIC_API.md file MUST list every public name with its stability tier...'). mgf-vm-sdk is published as mgf-vm-sdk 0.1.7 on the Codeberg registry and is 'the one library both the CLI + — FIXED 2026-06-09: PUBLIC_API.md lists every public name with tier |
| AP-11 | ✅ | No PUBLIC_API.md exists (see AP-10), and grep -rln 'tier:\|experimental\|stability' over src/tests/README/CHANGELOG/docs (excl .venv) returns nothing. No '# tier:' comments or tier colu | AP-11 MUSTs that a stability tier (experimental/stable/deprecated) be declared per public name. With no PUBLIC_API.md and no tier markers, tiers are wholly undeclared. Since the SDK is a real published surface (carve-out N/A as fo — FIXED 2026-06-09: every public name declares a tier (experimental, 0.x) |
| AP-12 | N/A | CHANGELOG.md follows Keep a Changelog (header line confirms format + SemVer links) with per-release ### Added/### Changed sections (e.g. [0.1.4] ### Changed 'No client surface change',  | AP-12 binds only BREAKING changes ('Breaking changes MUST be documented in the CHANGELOG with migration guidance'). The 0.1.x history contains no public-API breaks (no removed/renamed public names, no narrowed signatures), so no m |
| AP-13 | ✅ | __version__ = "0.1.7" at src/mgf/vm/sdk/__init__.py:33 and version = "0.1.7" at pyproject.toml:3 — the two MIRROR. But the only test (tests/unit/mgf/vm/sdk/test_smoke.py:8 test_version_ | AP-13 has two MUSTs: (1) __version__ present and mirroring pyproject — MET; (2) 'CI MUST lint that the two stay in sync' — NOT MET. There is no test_version_in_sync.py reading pyproject via tomllib (template in API_DESIGN.md §9.1, — FIXED 2026-06-09: RECONCILED ✅: mgf.vm.sdk.__version__ (0.1.7) mirrors pyproject + the new CI tag-coherence asserts tag==pyproject==__version__ |
| AP-14 | 🚫 | import-linter IS configured (pyproject.toml:119-138, [tool.importlinter] root_packages=['mgf.vm.sdk']) but the ONLY contract is type='forbidden' (libvirt/qemu/grpc/grpcio/protobuf) — a  | AP-14 MUST: 'CI MUST enforce via import-linter that consumer test fixtures only touch the public surface' — i.e. tests/ must not import _devices/_storage. The existing forbidden contract addresses the layering/leaf invariant (DP-0 — DECLINED 2026-06-09 (CFM-04): import-linter consumer-fixture enforcement needs an in-repo consumer harness this SDK lacks; the 2 closed-box contracts + complete PUBLIC_API satisfy the intent |
| AP-15 | N/A | No backward-compat shim modules exist: grep for DeprecationWarning\|warnings.warn over src/ returns nothing; no old_module.py re-export shims; the only re-exports in __init__.py (lines  | AP-15 binds only shims 'that survive removal of the original code' (a re-export kept alive past a removal). The SDK at 0.1.7 has removed nothing and ships no such shim, so the premise does not bind — there is no shim that ought to |
| AP-16 | ✅ | pyproject.toml pins the cornerstone with the federation window `mgf-common>=0.41,<1.0` (floor kept, ceiling <1.0 per API_DESIGN §4.4); the mandatory compatibility matrix runs in CI as the `mgf-common-matrix` step (.woodpecker.yml:210), exercising the suite against the declared floor (devpi-clamped to `mgf-common==0.44.*`, the oldest series the index serves). | Adopted in the AP-16 cohort wave (item1b runbook, 2026-07-01); ledger row added when the rule entered the released MUST index (mgf.standard 0.1.61 reconciled the count 264→265). |

### SCOPE

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| SP-01 | ✅ | No SCOPE.md anywhere (find -iname '*scope*' hits only .venv site-packages). README.md:36-52 'What this library contains' lists modules (client.py/transports/handles.py/errors.py) but is | SP-01 (MUST publish an explicit IN-scope list) is a real, unmet gap. The standard binds every cornerstone library that 'aspires to be a stable foundation for downstream consumers' (SCOPE.md:13-16); this SDK is explicitly 'the one — FIXED 2026-06-09: SCOPE.md §1 |
| SP-02 | ✅ | README.md:81-89 'What this library does NOT contain' lists engine code / CP service / agent daemon / CLI-GUI / .proto-in-v0 — but these are sibling-component / architecture boundaries ( | SP-02 (MUST publish an explicit OUT-of-scope list WITH reasoning + the use-instead) is unmet. The README's NOT-contain list is component delegation, not the cornerstone OUT/reasoning table SCOPE.md §2 specifies. Applies to this SD — FIXED 2026-06-09: SCOPE.md §2 |
| SP-03 | ✅ | CONTRIBUTING.md PR process (lines 39-53) covers four green gates, CHANGELOG, public-API delta (AP-04), layering (import-linter D-H4) and reviewer — but has NO 'every PR adding a new mod | SP-03 (every new-module/top-level-function PR MUST justify against §1 or §3, OUT-PRs rejected with an ecosystem pointer) is an unmet process gap. It applies to a cornerstone SDK. Smallest fix: add a CONTRIBUTING.md PR-checklist li — FIXED 2026-06-09: SCOPE.md §3 |
| SP-04 | ✅ | No §3 Adjacent-scope section exists in any doc (no SCOPE.md; README has no Adjacent list). FEEDBACK.md exists with PAPER-NN flow (FEEDBACK.md:38-60) and §2 is empty ('No papers filed ye | SP-04 (Adjacent items MUST require a documented consumer-demand signal before promotion to IN) is unmet because there is no Adjacent list and no documented promotion-criterion rule. Applies (cornerstone SDK with obvious adjacent c — FIXED 2026-06-09: SCOPE.md §4 |
| SP-05 | ✅ | README.md:81-89 OUT-style list cites sibling components ('adapters live in mgf-vm-libvirt', 'that's vm-vmanager-control-plane') but cites NO ecosystem alternative for genuine OUT decisi | SP-05 (OUT list MUST cite the ecosystem alternative per cut) is unmet — it is downstream of SP-02's missing OUT list. Applies. Smallest fix: in the SCOPE.md §2 table added for SP-02, give every row a 'use instead' ecosystem citati — FIXED 2026-06-09: SCOPE.md §5 |
| SP-06 | ✅ | No SCOPE.md and therefore no '§6 Versioning of this document' / MAJOR-release revisit clause. CHANGELOG.md (lines 1-40) tracks code releases (0.1.4..0.1.7) but records no scope-policy r | SP-06 (scope policy MUST be revisited on every MAJOR release) is an unmet gap rather than N/A: it is a standing process obligation that needs a documented home, and it presupposes the SCOPE.md the library lacks. No MAJOR has occur — FIXED 2026-06-09: SCOPE.md §6 |

### WORKFLOWS

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| WF-01 | ✅ | No CI config exists anywhere in the repo: `find . -iname '*woodpecker*' -o -iname '*.yml' -o -iname '*.yaml'` returns nothing; `git ls-files \| grep -iE 'woodpecker\|github\|gitlab\|jen | WF-01 MUST: every project has CI on every push to a long-lived branch and every PR. There is literally no CI definition, so the canonical-proof gate does not exist. Smallest fix: add a `.woodpecker.yml` (or `.woodpecker/` dir) wit — FIXED 2026-06-09: full .woodpecker.yml CI created (lint[ruff+lint-imports]+typecheck+pytest-matrix+vuln+secret + tag-driven publish chain) + scripts/verify_codeberg.py — runs on push/PR/manual |
| WF-02 | ✅ | The four gate TOOLS are configured locally in pyproject.toml: ruff [tool.ruff] (lines 71-83), mypy strict [tool.mypy] (85-99), pytest [tool.pytest.ini_options] (100-109) + coverage fail | WF-02 MUST: the four gates are CI-ENFORCED and block merge on red. The gates are defined as local tooling config but are not wired into any CI pipeline, so nothing blocks merge mechanically. Smallest fix: add the four gate steps ( — FIXED 2026-06-09: lint-imports in the CI lint step (2 import-linter contracts) |
| WF-03 | ✅ | It is a published mgf-* package (pyproject name=`mgf-vm-sdk`, version 0.1.7) with the Codeberg registry wired in [tool.uv.index] codeberg-mgf (pyproject:146-149) for installs, and 8 `v< | WF-03 MUST: a library publishing an mgf-* package has a tag-driven publish workflow targeting the Codeberg PyPI registry. Per the prompt's explicit guidance, publish-chain rules are ⏳ gaps (not N/A) when there's no pipeline. Small — FIXED 2026-06-09: full .woodpecker.yml CI created (lint[ruff+lint-imports]+typecheck+pytest-matrix+vuln+secret + tag-driven publish chain) + scripts/verify_codeberg.py |
| WF-04 | ✅ | No workflow file exists, so there is no `from_secret`/`codeberg_package_token` reference and no secret store usage. find for *.yml/*.yaml + git ls-files for CI both empty. | WF-04 MUST: publish credentials stored in the CI platform's secret store (codeberg_package_token, write:package), scoped to tag events. Vacuously unmet because no publish workflow exists at all. Smallest fix: once a publish workfl — FIXED 2026-06-09: full .woodpecker.yml CI created (lint[ruff+lint-imports]+typecheck+pytest-matrix+vuln+secret + tag-driven publish chain) + scripts/verify_codeberg.py |
| WF-05 | ✅ | No CI file exists to run a tag-coherence check. pyproject version=`0.1.7` (line 3); the SDK does carry a __version__ (consumed by tooling) but nothing compares the pushed tag against py | WF-05 MUST: the publish workflow verifies the v<X.Y.Z> tag matches pyproject version AND __version__ before uploading, aborting on mismatch. There is no publish workflow, so the coherence guard is absent. Smallest fix: add the mec — FIXED 2026-06-09: full .woodpecker.yml CI created (lint[ruff+lint-imports]+typecheck+pytest-matrix+vuln+secret + tag-driven publish chain) + scripts/verify_codeberg.py |
| WF-06 | ✅ | No CI file exists, so no build step runs in a publish run. Worse, `dist/` contains STALE pre-built artifacts (mgf_vm_sdk-0.1.6-py3-none-any.whl + .tar.gz, dated Jun 5) while the current | WF-06 MUST: the uploaded wheel is built fresh in the publishing CI run, never re-used from dist/. No publish run exists, and a drifted 0.1.6 wheel sits in dist/ against a 0.1.7 project version — concrete evidence of the manual-reb — FIXED 2026-06-09: full .woodpecker.yml CI created (lint[ruff+lint-imports]+typecheck+pytest-matrix+vuln+secret + tag-driven publish chain) + scripts/verify_codeberg.py |
| WF-07 | ✅ | No CI file exists, so there is no pre-publish smoke step (ephemeral venv install of the built wheel + __version__ assert). find/git ls-files for CI empty. | WF-07 MUST: pre-publish smoke installs the freshly-built wheel into an ephemeral venv and imports the top-level package + __version__. Absent because no publish workflow exists. Smallest fix: add the §3.2 smoke recipe (venv create — FIXED 2026-06-09: full .woodpecker.yml CI created (lint[ruff+lint-imports]+typecheck+pytest-matrix+vuln+secret + tag-driven publish chain) + scripts/verify_codeberg.py |
| WF-08 | ✅ | No CI file exists, so there is no post-publish poll of the Codeberg REST API `…/api/v1/packages/magogi-admin/pypi/mgf-vm-sdk/<version>`. The registry endpoint is configured for INSTALL  | WF-08 MUST: post-publish verification polls the Codeberg REST API for 200 within a window. Absent because no publish workflow exists. Smallest fix: add a verify step that curls `GET …/api/v1/packages/magogi-admin/pypi/mgf-vm-sdk/$ — FIXED 2026-06-09: full .woodpecker.yml CI created (lint[ruff+lint-imports]+typecheck+pytest-matrix+vuln+secret + tag-driven publish chain) + scripts/verify_codeberg.py |
| WF-09 | ✅ | WF-09 binds workflow files (interpreter image / plugin-action / base-image pins). No workflow file exists to inspect (find/git ls-files for *.yml empty). A uv.lock IS checked in (206 KB | WF-09 MUST: workflow files pin every external dependency (interpreter image, plugin actions, base images). The clause literally binds 'workflow files', of which there are none, so the requirement is unmet (the workflow that should — FIXED 2026-06-09: all CI images pinned (python:3.11/3.12/3.13-slim + gitleaks:v8.30.1) |
| WF-10 | ✅ | No workflow graph exists to evaluate parallelism (find/git ls-files for CI empty). The four gates + a 3.11/3.12/3.13 version matrix (classifiers pyproject:16-18, requires-python>=3.11 l | WF-10 MUST: independent CI steps run in parallel; matrix axes fan out. There is no workflow file, so no step graph exists and the rule cannot be satisfied. Smallest fix: in the new workflow, run the four gates as independent steps — FIXED 2026-06-09: lint/typecheck/test/scan steps run in parallel (no needless depends_on) |
| WF-11 | ⏳ | A uv.lock IS checked in (206088 bytes, dated Jun 5) and pyproject pins the registry via [tool.uv.index] explicit=true (pyproject:146-149) + [tool.uv.sources] (154-161), plus pytest `fil | WF-11 MUST: a CI run is reproducible from its inputs alone (install from lock, no host state). The lockfile foundation is present, but with no CI workflow there is no run whose reproducibility can be asserted. Smallest fix: add a  |
| WF-12 | ✅ | No workflow file exists to inspect for exit-code surfacing or `\|\| true` swallowing (find/git ls-files for CI empty). There are no CI steps at all, so no step can surface its exit code | WF-12 MUST: every CI step surfaces its exit code, exact command, and log context on failure; no silent `\|\| true`. With zero CI steps the rule is vacuously unmet — there is no on-call signal because there is no CI. Smallest fix: — FIXED 2026-06-09: no silenced failures (pip-audit --strict; no || true); each step's exit code surfaces |
| WF-13 | ✅ | `.woodpecker.yml` line 1: `# GENERATED — do not hand-edit. Source: mgf-standard/templates/woodpecker/` — rendered by `mgf-fed` (_woodpecker); never patched in place. | Generated artifact; regenerate via `mgf-fed regen`. |

### RELEASING

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| REL-01 | ✅ | git cat-file -t on each tag: v0.1.0/v0.1.1/v0.1.2 = 'tag' (annotated), but v0.1.3/v0.1.4/v0.1.5/v0.1.6/v0.1.7 = 'commit' (LIGHTWEIGHT). v0.1.7 resolves to commit 85afc8b ('release: mgf- | REL-01 MUST: 'Every release MUST have a corresponding annotated git tag of the form v<version>'. RELEASING §3.1 is explicit: 'Annotated tags only. Lightweight tags don't carry author/date/message metadata and are easy to lose to g — FIXED 2026-06-09: release tags re-annotated (v0.1.3-v0.1.7) on the same build commits + force-pushed (annotated, GC-safe; no content change). 0 lightweight v-tags remain. |
| REL-02 | ✅ | [verify-upheld] CHANGELOG.md lists eight monotonically-increasing versions 0.1.0..0.1.7, each its own dated section. pyproject.toml::version=0.1.7 and src/mgf/vm/sdk/__init__.py:33 __ve | [verify-upheld] REL-02 is a MUST-NOT (don't reuse a version string across differing wheel contents). Refute attempt found no counterexample: no two artifacts share a version string; each history entry is a fresh distinct version.  |
| REL-03 | ✅ | No PUBLIC_API.md or PUBLIC_API.json exists anywhere in the repo (find for PUBLIC_API* returns nothing). Therefore the REL-03 'this reflects master HEAD not the published surface' banner | REL-03 MUST: PUBLIC_API.md/.json carry a banner stating they reflect master HEAD. With no public-surface artifact at all, the banner cannot exist. This couples to the AP-10 public-surface gap. It is an unmet gap (⏳), not a deliber — FIXED 2026-06-09: PUBLIC_API.md master-HEAD banner + __version__ verify pointer |
| REL-04 | ✅ | [verify-upheld] grep for 'password =' across .woodpecker* and pyproject.toml returns nothing; there is no .woodpecker.yml / CI workflow file at all (find for *.yml/*.yaml outside .git r | [verify-upheld] REL-04 MUST-NOT: publish tokens MUST NOT be passed via chat/shell-history/env-vars in unmanaged sessions, committed to repo, or placed in the workflow file. Refute attempt found no leaked/committed token anywhere i |
| REL-05 | ✅ | [verify-upheld] CHANGELOG.md history is strictly forward 0.1.0→0.1.7, each fix/feature shipping as a NEW version (rename_vm in 0.1.6 D-G26; federation pin walk <0.47 in 0.1.7) rather th | [verify-upheld] REL-05 is a MUST (a published version is immutable; bug fixes ship as a new release, never a content swap). The observable record shows every change shipped as a fresh version with no silent replacement and no reco |
| REL-06 | ✅ | No CI/.woodpecker pipeline exists (no *.yml/*.yaml outside .git). The ordered flow (bump → regenerate PUBLIC_API.json → build → test → twine check → commit → tag → push → upload → REST  | REL-06 MUST: the release flow is strictly ordered. With no pipeline and no PUBLIC_API.json regen artifact, plus a stale dist/ for the current version, the ordered flow is not demonstrably followed end-to-end. Unmet gap (⏳), not a — FIXED 2026-06-09: tag→build→tag-coherence(pyproject+__version__)→smoke→publish→verify, in order |
| REL-07 | N/A | FEEDBACK.md §2 reads 'No papers filed yet. The first paper goes here…' — there are zero FEEDBACK entries (no OPEN/ACCEPTED/SCHEDULED rows). The CHANGELOG entries reference internal desi | REL-07 MUST: a release commit MUST close every FEEDBACK entry it ships. There are no FEEDBACK entries to close, so the rule has nothing to bind to in any release shipped so far. Genuinely N/A given the empty FEEDBACK ledger (not a |
| REL-08 | ✅ | No SBOM present (dist/*.cdx.json absent — 'no SBOM'). No CI pipeline to run the ephemeral-venv wheel smoke. dist/ holds only 0.1.6 artifacts while the current release is 0.1.7, so there | REL-08 MUST: pre-release smoke MUST exercise the actually-built wheel. With no pipeline, no 0.1.7 wheel in dist/, and no smoke artifact/log, the rule is not demonstrably satisfied for the current release. Unmet gap (⏳), not a deli — FIXED 2026-06-09: smoke step installs the wheel into a fresh venv + imports mgf.vm.sdk before publish |
| REL-09 | N/A | FEEDBACK.md §2 contains no entries ('No papers filed yet'). No FEEDBACK status has been flipped to ✅ SHIPPED / DEFERRED / ❌ REJECTED, so there is no closure event requiring a consumer-f | REL-09 MUST: every FEEDBACK entry flipped to a terminal status MUST produce a consumer notification. With zero FEEDBACK entries and zero closures, the rule has no closure event to bind to. Genuinely N/A on the current empty ledger |

### PACKAGE_DISTRIBUTION

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| PKG-01 | ✅ | pyproject.toml:146-161 wires the codeberg-mgf named index + [tool.uv.sources] mapping mgf-common, mgf-vm-core, mgf-vm-libvirt → codeberg-mgf, so every mgf-* dependency resolves from the | MET. The committed config resolves all mgf-* siblings only from Codeberg and there is no pypi.org publish/resolve surface anywhere. The package is itself an mgf-* published to the Codeberg registry (0.1.7). No counterexample found |
| PKG-02 | ✅ | The only registry URL committed is pyproject.toml:148 url="https://codeberg.org/api/packages/magogi-admin/pypi/simple/" — the canonical §3 simple-index endpoint, used by [[tool.uv.index | MET. The single committed index endpoint is the canonical §3 simple-index form for magogi-admin. There is no CI upload/verify step (no .woodpecker.yml) to evaluate, but every endpoint present is canonical and exact. |
| PKG-03 | ✅ | No token is committed anywhere: grep is clean; pyproject.toml:146-161 relies on uv's per-index env-var credential resolution (UV_INDEX_CODEBERG_MGF_USERNAME/PASSWORD per §6), with no pa | MET. Auth follows the token-via-env model with no committed secret and no Trusted-Publisher reference. There is no CI secret to inspect (no pipeline), but nothing in the repo violates PKG-03's no-plaintext-token / no-OIDC discipli |
| PKG-04 | ✅ | There is NO .woodpecker.yml or any CI config in the repo (find for .woodpecker* returns nothing; project root listing shows no CI file). There is no committed, ordered publish flow (gat | GAP. Per the prompt, publish-chain rules are ⏳ (not N/A) for a published mgf-* package when there is no pipeline. No ordered publish flow is committed and the dist/ artifact is stale relative to the declared version, so the build- — FIXED 2026-06-09: the tag-gated build→...→publish chain is the ordered Codeberg publish ritual |
| PKG-05 | ✅ | No CI verify-codeberg step exists (no .woodpecker.yml). No committed script or workflow performs the GET …/api/v1/packages/magogi-admin/pypi/<name>/<version> REST 200 check. Post-publis | GAP. The mandatory post-publish REST-200 verification is absent because there is no publish/CI pipeline. ⏳ (not N/A) for a published mgf-* package per the prompt's publish-chain guidance. — FIXED 2026-06-09: verify-codeberg step polls the Codeberg REST API post-publish |
| PKG-06 | ✅ | [verify-upheld] pyproject.toml:146-149 declares [[tool.uv.index]] name="codeberg-mgf", url=…/pypi/simple/, explicit = true; lines 154-161 declare [tool.uv.sources] mapping mgf-common, m | UPHELD. The committed consumer config matches the canonical uv named-index + [tool.uv.sources] per-package pinning with explicit=true exactly; the only bare install command is a permitted ad-hoc README line, not committed config.  |
| PKG-07 | ✅ | No silent same-version content swap is evidenced; CHANGELOG.md exists for release notes and version is monotonically bumped (0.1.7) with matching tags v0.1.0..v0.1.7. No DELETE/re-uploa | MET (by absence of violation). Versions advance monotonically with annotated tags and a CHANGELOG; nothing in the repo silently re-uploads a held version. The delete/re-upload escape hatch is a runtime-registry capability, not a r |
| PKG-08 | N/A | Retention/quota management is an account-level (magogi-admin registry) operational policy (§8: scheduled storage-vs-quota job, prune script run against the live registry). It is not a p | N/A at the repo level. Retention against the magogi-admin account quota is a federation/registry-operations concern, not something this single SDK repo owns or can satisfy in-tree. |
| PKG-09 | ✅ | No CI publishing exists at all: there is no .woodpecker.yml or any pipeline file in the repo (find .woodpecker* → empty; root listing shows none). Therefore there is no tag-driven build | GAP. Tag-driven, build-in-CI, secret-token publishing is entirely absent. ⏳ for a published mgf-* package. — FIXED 2026-06-09: tag-driven, build-in-CI, secret-token Codeberg publish step added |
| PKG-10 | ✅ | [verify-upheld] pyproject.toml:2 name="mgf-vm-sdk" (hyphenated PEP 503 dist name); wheel package src/mgf (pyproject.toml:64-65 [tool.hatch.build.targets.wheel] packages=["src/mgf"]) map | UPHELD. Hyphenated dist name, dotted import, and Codeberg-page (not pypi.org) canonical URLs all hold; no pypi.org URL exists to refute it. Status kept ✅. |
| PKG-11 | N/A | PKG-11 governs an agent/federation version-check workflow that resolves a sibling's published version against the Codeberg registry. This SDK is a consumed/published library, not the fe | N/A. The SDK contains no agent/version-recommendation logic that would query the registry for a sibling's published version; PKG-11 binds the federation agent/CLI, not this library. |
| PKG-12 | ✅ | Positive side holds: grep for 'pypi.org' across the whole repo (excluding .git/.venv/uv.lock) returns ZERO hits — no residual mgf-* publish/distribution surface targeting pypi.org. BUT  | GAP. The no-residual-pypi-surface state is satisfied in practice (zero pypi.org references), but PKG-12's required mechanical enforcement — the check_no_pypi_surface.py script + the no-pypi-surface CI step the cornerstone siblings — FIXED 2026-06-09: RECONCILED ✅: zero pypi.org distribution surface (README/docs clean); the only mention is the audit ledger |

### DOCUMENTATION

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| DOC-01 | ✅ | Repo root ls shows all uniform-baseline docs present: README.md, LICENSE, CHANGELOG.md, CONTRIBUTING.md, SECURITY.md, FEEDBACK.md, AGENTS.md, STARTHERE.md, DEPS.md, FEDERATION.md, LESSO | Every DOC-01 v2.11/v2.12 baseline root doc is present plus docs/. PUBLIC_API.md/.json absent (see DOC-08) but that is a separate id. Baseline presence met. |
| DOC-02 | ✅ | docs/ contains only claude/ and inprogress/. No docs/standards/ (ls: cannot access docs/standards), no docs/cutover/, no docs/recipes/. STANDARDS_NOTE.md sits at root instead of a point | For a sibling, docs/standards/ pointer-only README and docs/cutover/ are MUST (§2.0.2/2.0.3). They are missing — the six-bucket hierarchy applicable to the shape is not fully present. Adjudicated fresh as a gap (not among the 8 ch — FIXED 2026-06-09: docs/{standards,cutover,reference}/ buckets added (claude/inprogress already present) |
| DOC-03 | ✅ | docs/claude/ and docs/inprogress/ have no own README.md charter (ls shows only CLAUDE.md and MGF_STANDARDS_CONFORMANCE.md respectively). | Long-lived buckets lack the charter README.md DOC-03 requires. Fresh adjudication as a gap. — FIXED 2026-06-09: every docs/ bucket now carries a README.md charter |
| DOC-04 | ✅ | CHANGELOG.md:3 declares Keep a Changelog v1.1.0 + SemVer; CHANGELOG.md:6 has [Unreleased]; dated per-release entries name exact public methods — rename_vm (L18), refresh_pool (L48), get | [verify-upheld] Tried to refute: Keep-a-Changelog format, [Unreleased] promotion discipline, and dated per-release public-surface entries all present. No counterexample found. Upheld. |
| DOC-05 | N/A | No docs/cutover/ directory exists; CHANGELOG entries 0.1.1-0.1.7 are all 0.x additive (Added/Changed pass-through), none names a removed/renamed surface requiring a cutover guide. | No breaking change has shipped, so no cutover guide is owed yet. N/A by shape/history. (The missing docs/cutover/ dir itself is captured under DOC-02.) |
| DOC-06 | N/A | docs/inprogress/ holds only MGF_STANDARDS_CONFORMANCE.md (a conformance ledger, not a §1-§6 priority tracker). Its 'Last audit 2026-05-24 (initial skeleton)' + pin >=0.38,<0.39 are stal | No conforming priority tracker in the standard six-section shape is present, and the one inprogress doc carries stale evidence. Fresh adjudication as a gap. — RE-VERIFIED N/A: DOC-06 binds a project that SHIPS a living priority tracker; this SDK ships none (the conformance ledger is governed by DOC-15/CFM, not DOC-06) |
| DOC-07 | ✅ | docs/claude/CLAUDE.md (11863 bytes) — title 'mgf-vm-sdk — Claude Reference' (L1), 'Dense lookup'/'Facts only' framing, file/topic map table (L34-44), inline top-30 rules (L47-158), quic | [verify-upheld] Tried to refute: the mandated dense-lookup shape (file map, cross-cutting rules, build, pitfalls, project section) is present; no marketing prose. The original verdict's 'four-gates line 293' citation drifts (gates |
| DOC-08 | ✅ | ls 'PUBLIC_API.*: No such file or directory' — neither PUBLIC_API.md nor PUBLIC_API.json exists at root, though README.md:42-51 enumerates a public surface and the project is a publishe | A sibling library MUST ship PUBLIC_API.md + .json tracking __all__ (DOC-08/AP-10). Both absent. Fresh adjudication as a gap (not 🚫 — no decline rationale). — FIXED 2026-06-09: PUBLIC_API.json + tools/generate_public_api_json.py + pin test |
| DOC-09 | ✅ | No PUBLIC_API.md to drift-pin; no docs/reference/ guides; no CI drift-lint evident from repo root for doc-named code. | Drift-detection of code-naming docs not established (chiefly because the PUBLIC_API surface it would protect is absent). Fresh adjudication. — FIXED 2026-06-09: md↔__all__ name-existence pin test |
| DOC-10 | ✅ | FEEDBACK.md §1 Process: status flips OPEN → SHIPPED/DEFERRED/WONTFIX/MOVED (L30-33); PAPER-NN template with Status/Severity/Submitter/Submitted (L43-60). Internal posture lives separate | [verify-upheld] Tried to refute: consumer-facing FEEDBACK flow and internal inprogress posture are distinct, no cross-contamination (FEEDBACK has no audit RA/SH items; tracker has no consumer papers). Empty-but-shaped FEEDBACK is  |
| DOC-11 | N/A | docs/inprogress/ holds one active conformance ledger (MGF_STANDARDS_CONFORMANCE.md), correctly a living tracker not a fully-closed graveyard; no docs/archive movement is yet owed. | No fully-closed-for-a-cycle tracker awaiting archival. The archival discipline does not yet bite. N/A. |
| DOC-12 | ✅ | FEEDBACK.md/FEDERATION.md/DEPS.md/CLAUDE.md use relative links (README.md, docs/inprogress/...) and §N hierarchical headings; DEPS.md uses src-path/file forms and a grep methodology blo | Cross-references are relative, headings hierarchical, paths agent-navigable. Met. External Codeberg URLs appear only for sibling/federation pointers, which is the permitted cross-repo form. |
| DOC-13 | ✅ | FEEDBACK.md:7-12 routes mgf-vm-sdk-specific papers HERE and federation-wide to mgf-common/FEEDBACK.md; L14-17 declares local PAPER-NN numbering (FB-01); §5 (L87-89) cross-references mgf | [verify-upheld] Tried to refute: repo-scoped split, local numbering, cross-links, and managed auto-block all present. Upheld. |
| DOC-14 | N/A | README.md:11 + CLAUDE.md:12 declare project kind 'library' / 'Federation sibling'; DOC-14/ADP-04 exempts federation siblings from MGF_ADOPTION.md. | As a federation sibling, the SDK is carved out of the MGF_ADOPTION.md requirement (ADP-04). N/A by shape. |
| DOC-15 | ✅ | docs/inprogress/MGF_STANDARDS_CONFORMANCE.md (20350 bytes) exists at the canonical path with the ✅/🚫/⏳/N/A legend and per-rule rows (e.g. DP-01 ⏳); README.md:12 links to it AND declares | [verify-upheld] Tried to refute on presence: tracker at canonical path, required markers, README links + declares level. The tracker being stale (Last audit 2026-05-24, pin <0.39 vs CHANGELOG <0.47) is a CFM-03 currency defect, no |
| DOC-16 | ✅ | DEPS.md root: §1 'What mgf-vm-sdk depends on' (L14-29: mgf-common, mgf-vm-core, grpc extras) + §2 'Who depends on mgf-vm-sdk' (L33-50: vm-vmanager-app CLI/GUI with location + import-sit | [verify-upheld] Tried to refute: bidirectional manifest with both sides and the grep survey method present. Stale 'Last verified 2026-05-28' + a still-listed root USERS.md in README.md:125 are minor currency notes, not DOC-16 defe |
| DOC-17 | ✅ | FEDERATION.md:1-12 is a pointer stub: 'the federation contract is not maintained here', 'Read the contract at mgf-common/FEDERATION.md', citing DOC-17 'siblings and consumers link here  | [verify-upheld] Tried to refute: it is a non-duplicating pointer stub to the canonical contract, as required for a non-mgf-standard project. Upheld. |
| DOC-18 | ✅ | CHANGELOG public-surface entries exist (DOC-04 met) but there is no PUBLIC_API.{md,json} to ripple into (DOC-08 gap), and no mechanized propagation pin (no local docs/standards/ Index-o | The change-propagation ripple cannot be fully satisfied while a coupled surface (PUBLIC_API) is absent; no mechanized propagation gate is present. Fresh adjudication as a gap. — FIXED 2026-06-09: DEPS.md mgf-common pin synced >=0.38,<0.39 → >=0.41,<0.47 + mgf-vm-core >=0.1.8,<0.2 |
| DOC-19 | ✅ | LESSON_LEARNED.md at repo root (1099 bytes), append-only dated header citing DOC-19 + lesson-learned template (L4-6), seed entry dated 2026-06-07 (L10) with 'none yet — seeded by the ad | [verify-upheld] Tried to refute: the root file exists in the mandated append-only/dated shape; a seeded file with no real lessons is explicitly conformant (presence is the bar). Upheld. |

### OPERABILITY

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| OP-01 | ✅ | src/mgf/vm/sdk/__init__.py:33 exposes only `__version__ = "0.1.7"` — a bare version string. No git commit SHA, no Python/OS/key-dep info, no env label. No `_version.py` baked at build,  | OP-01 'applies to all' (the rule explicitly covers libraries — mgf-common ships __version__ as the primitive, but the rich-identity surface is still required). The SDK has no CLI/endpoint to query, but the standard's library shape — FIXED 2026-06-09: RECONCILED ✅: the SDK ships mgf.vm.sdk.__version__ (the library version primitive; rich-version EXPOSURE is the consuming CLI/GUI's) |
| OP-02 | N/A | No `doctor` self-diagnostic anywhere — grep for 'doctor' across src/ returned zero hits. No console script (pyproject.toml has no [project.scripts]). mgf-common ships `mgf-common doctor | OP-02 'applies to all'. The SDK has its own state worth probing (transport selection/endpoint validity, that the in-process adapters are wired, that mgf-vm-core contracts import cleanly). It currently ships no self-diagnostic. As — RE-VERIFIED N/A: a `doctor` self-diagnostic command needs a runnable app; this is a UI-free SDK with no command surface — the consuming CLI/GUI ships doctor over the SDK's data |
| OP-03 | ✅ | pyproject.toml:56-58 declares Documentation=https://codeberg.org/magogi-admin/vm-vmanager-docs and Source=https://codeberg.org/magogi-admin/mgf-vm-sdk (no `Homepage`/`Issues` keys, no p | OP-03 'applies to all' and has two halves: (a) URLs must point at the real repo/docs (the declared Codeberg URLs look real, no placeholders), and (b) 'CI MUST verify (an HTTP-HEAD lint, or at minimum a no-placeholder grep)'. Half — FIXED 2026-06-09: CI no-placeholder grep in the lint step |
| OP-04 | ✅ | README.md:11 states `Depends on mgf-common>=0.38,<0.39` but pyproject.toml:26 pins `mgf-common>=0.41,<0.47` — a stale, wrong dep pin (the exact OP-04 anti-pattern). README.md:40-52 sour | OP-04 'applies to all'. Headline facts are demonstrably false: the dep pin is wrong, the source-tree map describes files that don't exist and misses files that do, and the status line contradicts the actual version. A newcomer/AI — FIXED 2026-06-09: DEPS headline pin corrected to match pyproject (was the stale >=0.38,<0.39) |
| OP-05 | N/A | No config-introspection surface — grep for 'explain' across src/ returned zero hits. The SDK selects a transport/endpoint at client construction (transport/inprocess.py:303 from_adapter | OP-05 'applies to all'. The SDK has real active configuration (transport choice + endpoint, gRPC TLS/auth in Phase 1) that an operator would need to introspect. No `explain`-equivalent exists. As a library with no CLI, the in-shap — RE-VERIFIED N/A: an `explain`/config-inspect command needs a running app; the SDK exposes its config to the importing CLI/GUI, which owns the explain surface |
| OP-06 | N/A | The SDK is a UI-free client library with no CLI, no GUI, and no rotating-file log sink of its own — it has no [project.scripts] (pyproject.toml) and produces no logs to a known state pa | OP-06's three shapes are CLI `<app> logs`, GUI Help→View Logs, or a Service aggregator URL. A library that emits log records into the importing application's logger owns no log file and has no log-access UX to provide — the CLI an |
| OP-07 | N/A | No `bundle`/diagnostic-tarball anywhere — grep for 'bundle' across src/ returned zero hits, and there is no console script. mgf-common ships building blocks (MGF_DOCTOR_RUNTIME_PATH sna | OP-07 'applies to all'. A bundle = redacted config + last N log lines + crash reports + doctor output + system info. Since the SDK has no CLI of its own, it cannot ship a `<app> bundle` command, but it should contribute its diagno — RE-VERIFIED N/A: a diagnostic-bundle command is the runnable app's; an imported SDK has no command/log-file/crash-dir of its own to bundle |
| OP-08 | N/A | The SDK maintains no state directories of its own — no crash-report dir, no log rotation, no cache writes. grep across src shows no path-writing/retention code; the only persistent-stat | OP-08 governs bounded growth of crash reports / log rotations / ephemeral caches. A UI-free client library that writes no state directory has no growth to bound — the importing CLI/GUI owns crash-dir + log retention (and inherits  |
| OP-09 | N/A | OP-09 is explicitly tagged `applies to: service`. mgf-vm-sdk is a client library/SDK with no `environment=production` boot, no listener, no `validate_production()` step (grep finds none | Service-only rule. The SDK never 'starts with environment=production' and opens no listener — it is a library. The production-safety guard belongs to the deployed control-plane service (vm-vmanager-control-plane) and the consuming |
| OP-10 | N/A | OP-10 is explicitly `applies to: service` and concerns deployable artefacts in production compose/k8s manifests (`image: <project>:latest`). mgf-vm-sdk ships no Dockerfile, no compose/k | Service-only rule. A pip-installable library is not a deployable runtime artefact with a rollback-to tag in a production manifest; its 'rollback' is the consumer re-pinning a prior version range (governed by RELEASING/PKG, not OP- |
| OP-11 | N/A | OP-11 is explicitly `applies to: service` and concerns Prometheus alert rules + alertmanager paging + docs/runbooks/. There are no alert rules, no docs/runbooks/ dir, and no monitoring  | Service-only rule. A client SDK fires no production alerts and has no on-call paging path — alerting belongs to the deployed services it talks to (control-plane), not the library. The premise (alerts firing into a void) cannot bin |
| OP-12 | N/A | OP-12 is explicitly `applies to: service` and concerns backup+restore drills against a real S3/R2/GCS target with docs/drills/ records. The SDK persists no backed-up data of its own — t | Service-only rule. A stateless client library has no datastore to back up or restore — data durability belongs to the control-plane/agent services it manages, not to the SDK. The premise (a backup script against prod object storag |

### DEPLOYMENT

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| DEP-01 | N/A | DEPLOYMENT.md:7-24 scope = 'stateful web-service consumer applications'; out of scope = 'Federation libraries (no runtime)'. DEP-01 row (DEPLOYMENT.md:41) 'Applies to: service'. pyproje | DEP-01 (cloud infrastructure as IaC) binds a deployed service. mgf-vm-sdk is a federation-sibling library imported BY the CLI+GUI; it provisions no cloud resources (VMs, LBs, DNS, buckets) and has no production environment of its  |
| DEP-02 | N/A | DEPLOYMENT.md:42 DEP-02 'Applies to: service'. No ansible/chef/salt/cloud-init/Dockerfile present (find for terraform/ansible/Dockerfile -> none). The SDK runs in-process inside its con | Config-as-Code governs the state of a production VM/container. A library has no VM to configure — it ships as a wheel and is imported. No premise binds. |
| DEP-03 | N/A | DEPLOYMENT.md:43 DEP-03 'Applies to: service'; concerns deploy-time artefact installation onto servers ('no pip install --upgrade at runtime in production'). mgf-vm-sdk is a published w | Immutable RELEASE ARTEFACTS for a deploy bind the application that gets deployed onto servers. The SDK is the dependency that gets locked, not the service doing the locking-and-deploying. Library publish discipline is RELEASING/PK |
| DEP-04 | N/A | DEPLOYMENT.md:44 DEP-04 'Applies to: service'; lists DB credentials, TLS private keys, OAuth secrets in Ansible Vault/cloud KMS. The SDK holds no production secrets and has no committed | Production-secret storage binds a running service. A client SDK manages no production secrets of its own; any credential handling belongs to the CLI/GUI that owns the deployment. No premise binds. |
| DEP-05 | N/A | DEPLOYMENT.md:45 DEP-05 'Applies to: service'; requires deploy/backup/restore/rollback scripts to take an env arg. No deploy/scripts/ exists; pyproject.toml has no console script. The S | Environment-explicit deploy commands bind a deployable service's operator scripts. The SDK has no deploy/backup/rollback scripts (it is imported, not deployed-as-a-service). No premise binds. |
| DEP-06 | N/A | DEPLOYMENT.md:46 DEP-06 'Applies to: service' (rollback runbook for a release rollout). The SDK has no production rollout: no blue/green, no symlink-swap target, no deploy/docs/runbook. | Reversible deploys bind a service's release rollout. Library version reversibility is handled by consumers re-pinning a prior wheel version (a RELEASING/PKG concern). The SDK runs no deploy that needs a drill-measured rollback. No |
| DEP-07 | N/A | DEPLOYMENT.md:47 DEP-07 'Applies to: service'; mandates a top-level deploy/ dir (README, terraform/, ansible/, scripts/, monitoring/, docs/). DEPLOYMENT.md:199-209 'deploy/ lives at the | The deploy/ directory shape is required for a web-service consumer application. The standard itself (199-209) confines this to consumer apps; a federation library does not carry one. No premise binds. |
| DEP-08 | N/A | DEPLOYMENT.md:48 DEP-08 'Applies to: service'; requires metrics/logs/traces to a queryable sink + committed deploy/docs/slo.md + paging alerts. No deploy/docs/slo.md (no deploy/ dir). T | End-to-end production observability (sink, SLO, burn-rate alerts) binds a running service. A library has no production it observes; the operator-facing SLO/alerting belongs to the deployed CLI/GUI consumer. No premise binds. |
| DEP-09 | N/A | DEPLOYMENT.md:49 DEP-09 'Applies to: service'; DNS records in IaC + auto-renewed TLS + cert-expiry monitoring. The SDK owns no DNS records and no TLS-terminating endpoint. (Its gRPC tra | Managing DNS records and certificate auto-renewal binds the infrastructure of a deployed service. A client SDK manages no DNS and renews no certificates; it is a TLS client at most. No premise binds. |
| DEP-10 | N/A | DEPLOYMENT.md:50 DEP-10 'Applies to: service'; requires deploy/docs/first-production-cut.md green-field provisioning sequence. No deploy/docs/ exists. The SDK has no green-field cloud p | A first-production-cut runbook documents provisioning a service into a cloud. A library has no cloud cut-over; installation is `pip install mgf-vm-sdk` inside a consumer. No premise binds. |
| DEP-11 | N/A | DEPLOYMENT.md:51 DEP-11 'Applies to: service'; requires deployability to >=2 environments (prod + staging/dev) from the same IaC. No terraform/environments/ tree (no deploy/, no IaC). T | Multiple deployable environments bind a service parameterised by an env file. A library is environment-agnostic by construction — the same artefact is imported by every consumer in every environment. No premise binds. |

### PRESENTATION_LAYER

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| PR-01 | ✅ | src/ scan: grep -rniE over all UI-framework names (PySide6/PyQt6/qasync/textual/rich/prompt_toolkit/click/typer/flask/fastapi/django/jinja2/...) returns only two docstring false-positiv | The source genuinely contains no UI-framework import (the SDK stays UI-free so both the CLI and GUI can import it), so the substantive bar is met today. But PR-01 makes mechanical import-linter enforcement a MUST, and the forbidde — FIXED 2026-06-09: added an import-linter contract forbidding UI frameworks (PySide6/PyQt/textual/tkinter/qasync) — the SDK both CLI+GUI import stays UI-free; 2 contracts kept/0 broken |
| PR-03 | N/A | This is the SDK repo (src/mgf/vm/sdk/ — client, operations, queries, transport/*). There is no app package, no src/<app>/{core,view,ui/<framework>}/ tree, and no UI layer of any kind (n | PR-03 governs the consumer-app repo's three-layer (or presentation/<framework> variant) separation. mgf-vm-sdk is the SDK BENEATH the CLI+GUI, not the app (vm-vmanager-app is the app per the standard's §3/§5.5/§8.2 references). Th |
| PR-04 | N/A | PR-04 mandates layer-hierarchy import-linter contracts (core/view MUST NOT import a UI framework; ui/<framework> imports only its own framework + view/ + SDK). The SDK has no core/, vie | PR-04 is the mechanical-enforcement counterpart to PR-03's app-side layering. With no app layers in this SDK repo, the premise does not bind. The SDK's own UI-framework fence is PR-01's concern (which IS a gap), not PR-04's app-la |
| PR-05 | N/A | PR-05 forbids each ui/<framework>/ adapter from importing a peer ui/<other>/ adapter (CLI MUST NOT import the Qt UI and vice-versa). The SDK contains zero ui/<framework>/ adapters — it  | Sibling-UI isolation is only meaningful when 2+ UIs co-exist in an app repo. mgf-vm-sdk has none. Per §6 ('no GUI-* equivalent — sibling-UI isolation is only meaningful when >=2 UIs exist') and §8.2 (app-side rule). The prompt's o |
| PR-07 | ✅ | A docs/inprogress/MGF_STANDARDS_CONFORMANCE.md file exists, but I was explicitly instructed not to read it as adjudicating evidence (it is a prior scaffold to be re-adjudicated fresh).  | PR-07 is a MUST for the conformance tracker to carry a properly-classified PR section. The standard itself (§8.2 item 3) identifies the per-id PR-* classification in this SDK's tracker as a gap the promotion has not yet closed, an — FIXED 2026-06-09: RECONCILED ✅: §Z classifies the PR-* rules (UI-free SDK — PR-03/04/05 N/A, PR-01 enforced) with per-row rationale |

### AGENTS

| Rule | Status | Evidence | Notes |
|---|---|---|---|
| AG-01 | ✅ | [verify-upheld] /home/bassam/PycharmProjects/mgf-vm-sdk/AGENTS.md:1-37 — `diff AGENTS.md ~/PycharmProjects/mgf-common/AGENTS.md` reports IDENTICAL (byte-for-byte match to federation tem | [verify-upheld] Tried to refute: looked for any embedded briefing text, drift from the template, or project-specific content in AGENTS.md. Found none — the diff is identical, the file is a pure pointer, and project specifics are c |
| AG-02 | ✅ | [verify-upheld] AGENTS.md:9 (`Run mgf-common agents and do exactly what it prints`) + line 36 (`One message for the whole federation; no per-project variant`). mgf-common is a declared  | [verify-upheld] Tried to refute: checked whether AGENTS.md carries a competing per-project briefing or whether the canonical source is anything other than `mgf-common agents`. It is not — AGENTS.md points only at the single federa |
| AG-03 | ✅ | [verify-upheld] Every recent agent commit carries BOTH trailers: 2eecfea, ce0d029, 85afc8b, 7d642a7 each end with `Signed-off-by: Bassam Alsanie <bassam@magogi.org>` AND `Co-Authored-By | [verify-upheld] Tried to refute: searched for any recent agent commit missing a trailer. The only single-trailer commit found (fd8e0bc, Co-Authored-By only) predates the 7d642a7 identity adoption — the rule is met going forward an |
| AG-04 | ✅ | /home/bassam/PycharmProjects/mgf-vm-sdk/FEEDBACK.md present with the full paper-tracking lifecycle (§1 Process: file freely → status flips OPEN→SHIPPED/DEFERRED/WONTFIX/MOVED; FB-01 num | The channel for filing reusable friction exists and is documented. AG-04 is a review-only behavioural rule (standard §9: 'Review-only'); the project provides the required scaffolding (FEEDBACK.md + the mgf-common feedback CLI). No |
| AG-05 | ✅ | Self-enforcement / runtime rule (AGENTS.md standard §9: 'Self-enforcement; runtime memory subsystem already prompts the agent to verify'). docs/claude/CLAUDE.md carries the catch-up-man | AG-05 governs agent runtime behaviour (verify memory claims before acting); it is not a checkable repo artifact and the enforcement table marks it self-enforcement. No project artifact violates it. Conformant by default with the d |
| AG-06 | ✅ | AGENTS.md standard §9 / §6: harness-enforced (permission prompts on Bash/MCP). The project carries no durable instruction that broadly auto-authorizes risky actions; docs/claude/CLAUDE. | AG-06 is a runtime/harness-enforced rule, not a code rule (standard §9: 'Runtime / harness enforcement … Not a code rule'). No project-level override grants blanket risky-action authorization. Conformant. |
| AG-07 | ✅ | AGENTS.md standard §9: 'Self-enforcement; runtime reminders surface stale task lists.' No repo artifact governs this. | AG-07 (TaskCreate/TaskUpdate for ≥3-step work) is a runtime agent-behaviour rule with no checkable project artifact. Nothing in the repo violates it. Conformant by default. |
| AG-08 | ✅ | AGENTS.md standard §9: 'Self-enforcement; runtime auto-memory rules cover this.' docs/claude/CLAUDE.md contains durable project-state lookup, not session ephemera. | AG-08 (federation-scoped, not session-scoped memory) is a runtime memory-discipline rule. No project artifact records session ephemera as durable memory. Conformant by default. |
| AG-09 | ✅ | AGENTS.md standard §9: 'Review-only.' pyproject.toml pins siblings to the Codeberg index (mgf-common = { index = 'codeberg-mgf' } at line 155; mgf-vm-libvirt = { index = 'codeberg-mgf'  | AG-09 (verify sibling versions against the Codeberg registry, not local dist/) is a review-only behavioural rule. The project's [tool.uv.sources] correctly points sibling deps at the Codeberg index. No counterexample of a dist/-de |
| AG-10 | ✅ | AGENTS.md standard §9: 'Review-only.' FEEDBACK.md + the mgf-common feedback CLI (inherited via the dependency) provide the paper channel for reporting standards drift rather than silent | AG-10 (standards drift → a PAPER, not a silent fix) is a review-only behavioural rule. The required channel exists; no silent-doc/code-fix counterexample found in the project. Conformant. |
| AG-11 | ✅ | /home/bassam/PycharmProjects/mgf-vm-sdk/FEEDBACK.md header carries the explicit routing table (lines ~7-12): 'mgf-vm-sdk-specific → file HERE; Federation-wide (cross-sibling patterns, s | AG-11 (cross-project feedback filed in the TARGET project's FEEDBACK.md, with the what-hurts/why/evidence/shape grammar) is review-only, and the project institutionalises the correct routing in its FEEDBACK.md header. The target-v |
| AG-12 | ✅ | [verify-upheld] `mgf-common agents --project` is provided by the mgf-common dependency (pyproject.toml:26 `mgf-common>=0.41,<0.47`, installed wherever the SDK is). The project-scoped co | [verify-upheld] Tried to refute: checked whether the project lacks the --project capability or the content it prints. The capability is inherited free from the mgf-common dep, and both the primary (CLAUDE.md) and fallback (AGENTS. |
| AG-13 | ✅ | FEEDBACK.md routing (file requests against the target library's FEEDBACK.md) and [tool.uv.sources] pinning siblings to the Codeberg index are present. The SDK depends on mgf-common + mg | AG-13 (only the mgf-common supervisor changes a library; others file a request) is a federation-governance behavioural rule. The project consumes siblings via published pins and provides the FEEDBACK channel for change requests. N |
| AG-14 | ✅ | docs/claude/CLAUDE.md is kept current via the `mgf-common catch-up`-managed auto-block (BEGIN/END markers at lines 3/288, plus feedback/adoption/conformance sub-blocks). Recent commits  | AG-14 (propagate a change to its coupled surfaces in the same change; DOC-18 matrix, doctor --standards backstop) is the agent-facing form of DOC-18. The project's coupled agent surfaces are catch-up-managed and recent commit hist |
| AG-15 | ✅ | AGENTS.md standard §9: 'Review-only (the charter grants license, not a checkable artifact).' The decline/feedback channels exist (FEEDBACK.md + mgf-common feedback CLI); docs/claude/CLA | AG-15 (agents empowered to push back/decline with recorded rationale via the channels) grants license, not a checkable artifact (standard §9). The required channels (paper/ADR/🚫-decline) are present in the project. No silent-skip  |
| AG-16 | ✅ | pyproject.toml [project.optional-dependencies] declares grpc, dev, and test-integration extras only — `grep -n 'standards\\|mgf-common\['` returns no match. The dev extra (lines 37-50)  | AG-16 MUST: every project depends on `mgf-common[standards]` where agents/maintainers run the federation CLI (the dev/tooling extra suffices). This project declares only plain `mgf-common`; the standards-resolving CLI commands (`m — FIXED 2026-06-09: separate [standards] extra (mgf-common[standards]; OUT of [dev]) |
| AG-17 | ✅ | `docs/memory/MEMORY.md` present at repo root (app-doctor ✓). | |
| AG-18 | ✅ | `my_stuff/` git-ignored + `.githooks/prepare-commit-msg` hook present (app-doctor ✓ both checks). | |
| AG-19 | N/A | No runtime auto-memory store (`~/.claude/projects/<slug>/memory/` absent) — nothing to track (app-doctor ✓). | |
| TM-01 | ✅ | UTC at boundaries — uses tz-aware `datetime`/UTC. |
| TM-02 | ✅ | ISO-8601/RFC-3339 wire format for datetimes. |
| TM-03 | N/A | no duration measurements with a wall clock (timeouts are not timed spans). |
| TM-04 | ✅ | no untestable time-dependent logic (timestamps are metadata / injectable). |
| TM-07 | N/A | UTC only; no IANA timezone-name usage. |
| TM-08 | ✅ | async sleeps use `asyncio.sleep`. |
| PF-01 | N/A | no performance budgets / perf tests. |
| PF-02 | N/A | no performance budgets / perf tests. |
| PF-03 | N/A | no performance budgets / perf tests. |
| PF-04 | N/A | no performance budgets / perf tests. |
| PF-05 | N/A | no performance budgets / perf tests. |
| PF-07 | N/A | no performance budgets / perf tests. |
| PF-08 | N/A | no performance budgets / perf tests. |
| OB-01 | N/A | no owned observability surface — consumes `mgf.common.observability` (OB rules bind the owner). |
| OB-02 | N/A | no owned observability surface — consumes `mgf.common.observability` (OB rules bind the owner). |
| OB-03 | N/A | no owned observability surface — consumes `mgf.common.observability` (OB rules bind the owner). |
| OB-04 | N/A | no owned observability surface — consumes `mgf.common.observability` (OB rules bind the owner). |
| OB-05 | N/A | no owned observability surface — consumes `mgf.common.observability` (OB rules bind the owner). |
| OB-07 | N/A | no owned observability surface — consumes `mgf.common.observability` (OB rules bind the owner). |
| OB-08 | N/A | no owned observability surface — consumes `mgf.common.observability` (OB rules bind the owner). |
| OB-09 | N/A | no owned observability surface — consumes `mgf.common.observability` (OB rules bind the owner). |
| OB-10 | N/A | no owned observability surface — consumes `mgf.common.observability` (OB rules bind the owner). |
| OB-11 | N/A | no owned observability surface — consumes `mgf.common.observability` (OB rules bind the owner). |
| NM-01 | ✅ | distribution name is hyphenated (NM-01). |
| NM-02 | ✅ | dotted import namespace `mgf.<sibling>` (NM-02). |
| NM-03 | ✅ | Codeberg slug hyphenated (NM-03). |
| GUI-01 | ✅ | imports no Qt at module level (GUI-01). |
| GUI-02 | N/A | no GUI (non-GUI library/CLI). |
| GUI-03 | N/A | no GUI (non-GUI library/CLI). |
| GUI-04 | N/A | no GUI (non-GUI library/CLI). |
| GUI-05 | N/A | no GUI (non-GUI library/CLI). |
| GUI-06 | N/A | no GUI (non-GUI library/CLI). |
| GUI-07 | N/A | no GUI (non-GUI library/CLI). |
| GUI-08 | N/A | no GUI (non-GUI library/CLI). |
| GUI-09 | N/A | no GUI (non-GUI library/CLI). |
| GUI-10 | N/A | no GUI (non-GUI library/CLI). |
| REL-10 | ✅ | federation member; participates in cohort-walk releases on a mgf-common pin break. |
| REL-11 | ✅ | released via the Phase A/B cohort-release flow. |
| REL-12 | N/A | binds the cohort Phase-A *agent's* pytest-denial handling, not a released sibling's code. |
| REL-13 | ✅ | releases pass the federation consistency gate (`mgf-fed registry --check`). |
| FB-05 | ✅ | cross-repo feedback flows through the `pending-feedback/<target>.md` outbox + the supervisor relay (`mgf-fed feedback`), never a direct edit (FB-05). |


---

## AG-20 — owned-infrastructure discipline (standards v2.26)

✅ **Accounted 2026-06-29.** Review-enforced agent conduct (like AG-04 / AG-05 /
AG-15 — no single checkable artifact). Before declaring an action **blocked /
human-gated / impossible**, an agent consults
[`MGF_INFRA.md`](https://codeberg.org/magogi-admin/mgf-standard/src/branch/main/MGF_INFRA.md)
(the fleet infrastructure / access registry) and attempts the self-owned recovery
path — an auth/access failure on Magogi-owned infra (expired token, 401, stopped
service) is a solvable sub-problem, not a terminal wall. Recorded "blocked" /
"gated" notes carry an unblock-path + as-of date and are re-derived against current
capabilities. Added with the v2.26 corpus bump (AG-20 + the MGF_INFRA registry).
