From 1ff17e32d3610d27a2aa453b71fc5682ec016caf Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 12 Jul 2026 05:19:07 +0000 Subject: [PATCH 1/2] chore(skills): re-sync vendored .agents/skills to skill-lib@6f36340 Second re-sync round. After the initial sweep (skill-lib@d0f6209), skill-lib PR #39 reworked msdmd/ (SKILL.md, collect.py, collection.ts) and test-build/SKILL.md (the CONTRACTS/CHECKS split). This brings the vendored copies back to verbatim compliance with current canonical (skill-lib@6f36340). - Overlay the drifted canonical msdmd/ and test-build/ directories verbatim; repo-local additions are preserved. - Bump the .agents/skills/README source commit to skill-lib@6f36340. Verified clean with tools/check_consumer_drift.py against skill-lib@6f36340. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01J4hD57ejBTZYsuLKSpoE7P --- .agents/skills/README.md | 2 +- .agents/skills/msdmd/SKILL.md | 89 ++++++---- .agents/skills/msdmd/collect.py | 6 +- .agents/skills/msdmd/collection.ts | 9 +- .agents/skills/test-build/SKILL.md | 258 +++++++++++++++++++---------- 5 files changed, 235 insertions(+), 129 deletions(-) diff --git a/.agents/skills/README.md b/.agents/skills/README.md index bcddfcc..ad5cda0 100644 --- a/.agents/skills/README.md +++ b/.agents/skills/README.md @@ -12,7 +12,7 @@ Installed skills: - `meta-module-build/` — metadata-first module scaffolding - `new-retain-old/` — safe replacement workflow that preserves the old implementation while creating a new active path - `manifest/` — living-spec generator for `CLAUDE.md` (vendored from - `The-Interdependency/skill-lib@d0f6209`). Generates the mechanical facts + `The-Interdependency/skill-lib@6f36340`). Generates the mechanical facts block in `CLAUDE.md` from `backend/pyproject.toml` + the tree; the `manifest drift check` workflow runs `generate.py --check` in CI. Refresh with `python .agents/skills/manifest/generate.py --pyproject backend/pyproject.toml --write`. diff --git a/.agents/skills/msdmd/SKILL.md b/.agents/skills/msdmd/SKILL.md index 0af6b20..15f49d6 100644 --- a/.agents/skills/msdmd/SKILL.md +++ b/.agents/skills/msdmd/SKILL.md @@ -1,17 +1,18 @@ --- name: msdmd -description: Module Self-Declared Metadata in Markdown — the foundational convention where each source module declares its own structured metadata in a fenced comment block. Other skills in this lib (doc-build, cap-build, deps-build, owner-build, test-build, meta-module-build, risk-boundary-build, ratios, etc.) are thin applications on top of this convention. Load this when authoring a new metadata-driven skill, when extending the block schema, or when building a parser/executor for a new application. +description: Module Self-Declared Metadata in Markdown — the foundational convention where each module declares its own structured metadata in a fenced comment block. Other skills in this lib (doc-build, cap-build, deps-build, owner-build, test-build, meta-module-build, risk-boundary-build, ratios, etc.) are thin applications on top of this convention. Load this when authoring a new metadata-driven skill, when extending the block schema, or when building a parser/executor for a new application. --- # msdmd — Module Self-Declared Metadata in Markdown ## The doctrine -Every cross-cutting fact a module owns — its test contracts, its public -documentation, its declared capabilities, its dependency edges, its -owner — should live **in the same file as the code that implements it**, -in a structured comment block. A meta-runner walks the tree, parses -every block, and acts on it. +Every cross-cutting fact a module owns — its behavior obligations, +public documentation, declared capabilities, dependency edges, owner, +runtime boundaries, or executable evidence — should live **in the same +file as the module that owns that fact**, in a structured comment +block. A meta-runner walks the tree, parses every block, and acts on +it. Modules without the relevant block surface as visible coverage gaps in the runner output. Coverage is observable, not implicit. @@ -20,8 +21,14 @@ This is the inverse of the conventional "keep your docs/tests/configs in sync with code" approach, which fails because the contract and the implementation live in different files. Anyone can delete the code and forget the doc; the lie persists. msdmd makes the lie structurally -impossible: when you delete the code, you delete the block in the same -diff. +visible: when the implementation-owning file disappears, its owned +block disappears in the same diff. + +For tests, ownership is split rather than flattened: source modules own +`CONTRACTS` obligations; test modules own `CHECKS` evidence that +claims to prove those obligations. See +[`test-build/SKILL.md`](../test-build/SKILL.md) and +[`doctrine/msdmd-checks.md`](../doctrine/msdmd-checks.md). ## Block syntax @@ -39,8 +46,8 @@ diff. ### Universal rules - **Fence**: `=== ===` opens, `=== END ===` - closes. Block name is uppercase snake_case (e.g. `CONTRACTS`, `DOCS`, - `CAPABILITIES`, `REQUIRES`, `OWNERS`). + closes. Block name is uppercase snake_case (e.g. `CONTRACTS`, + `CHECKS`, `DOCS`, `CAPABILITIES`, `OWNERS`). - **Comment marker**: whatever is idiomatic for the file's language. `#` for Python / Ruby / Elixir / shell. `//` for TS / JS / Rust / Go / Java / C / C++ / Swift. `--` for SQL / Lua / Haskell. The marker @@ -58,7 +65,7 @@ diff. `CONTRACTS` and `DOCS` (and any others). Each is parsed independently by its respective application. -### Example (Python) +### Example (Python source module) ```python # === CONTRACTS === @@ -66,11 +73,24 @@ diff. # given: GET /api/v1/conversations/{id} with x-user-id != row.user_id # then: 404 (existence non-disclosure) # class: security -# call: tests.contracts.chat.test_get_other_owner_404 # === END CONTRACTS === ``` -### Example (TypeScript) +### Example (Python test module) + +```python +# === CHECKS === +# id: check_chat_get_other_owner_404_http +# proves: chat_get_other_owner_404 +# call: self::test_chat_get_other_owner_404_http +# requires: python3, posix_shell +# timeout: 20 +# mutates: db +# cleanup: transaction_rollback +# === END CHECKS === +``` + +### Example (TypeScript source module) ```typescript // === CONTRACTS === @@ -78,7 +98,6 @@ diff. // given: a message is in flight // then: send button is disabled and shows pending state // class: ux_correctness -// call: src/__contracts__/chat_input.ts#test_send_disabled_while_pending // === END CONTRACTS === ``` @@ -137,12 +156,14 @@ export default defineMsdmdCollection({ repo: "", declarations: [ { file: "path/to/module.py", block: "CONTRACTS", id: "...", fields: { summary: "..." } }, + { file: "tests/test_module.py", block: "CHECKS", id: "...", fields: { proves: "..." } }, ], gaps: [ { file: "path/to/module.py", missing: ["CONTRACTS", "DOCS"] }, ], edges: [ { from: "module_a", to: "module_b", kind: "requires", source_block: "DEPENDENCIES", source_id: "..." }, + { from: "check_module_a", to: "module_a_contract", kind: "claims_proves", source_block: "CHECKS", source_id: "..." }, ], }); @@ -151,11 +172,12 @@ export const gaps = []; ``` A repo-level msdmd visualizer SHOULD read `_msdmd.ts` and render -relationships between modules using the `MsdmdEdge` shape: `DEPENDENCIES.requires`, -`CAPABILITIES.exposes`, `OWNERS.owner`, `BOUNDARIES` risk fields, `DOCS.covers`, -`CONTRACTS.call`, and any `requires` edges shared across application skills. -The visualizer is a consumer of the collection point, not a second metadata -source. +relationships between modules using the `MsdmdEdge` shape: +`DEPENDENCIES.requires`, `CAPABILITIES.exposes`, `OWNERS.owner`, +`BOUNDARIES` risk fields, `DOCS.covers`, `CHECKS.call`, +`CHECKS.proves` as `claims_proves`, and any `requires` edges shared +across application skills. The visualizer is a consumer of the +collection point, not a second metadata source. If a repo has no collection point or visualizer yet, record that as `hmmm` in repo-local planning rather than pretending the graph exists. @@ -220,16 +242,17 @@ consistency): |---|---| | `id` | Unique stable identifier within the block. Required on every entry. | | `class` | Free-text tag for grouping (`security`, `correctness`, `idempotency`, etc.). The runner counts entries per class in summaries. | -| `call` | Fully-qualified path to an executable target (Python module path, JS module + export, etc.) the executor will invoke. | +| `call` | Executable target owned by an evidence/check declaration. Source `CONTRACTS` do not use this field for test topology. | +| `proves` | Comma-separated ids this evidence/check entry claims to prove. The collection edge kind is `claims_proves`; mutation sensitivity is a higher verification rung. | | `summary` | One-sentence human description. | -| `requires` | Comma-separated list of other entry ids this one depends on. | +| `requires` | Comma-separated dependency ids or host capabilities. Exact semantics are application-specific and must be documented by the skill that consumes it. | | `owner` | Who is responsible (person, agent role, team). | | `since` | Version or date this declaration was added. | | `deprecated` | If present, marks the entry as scheduled for removal. | Application-specific fields (`given`, `then`, `expects`, `inputs`, -`outputs`, etc.) are introduced by individual SKILLs and documented in -their own SKILL.md. +`outputs`, `mutates`, `cleanup`, `timeout`, etc.) are introduced by +individual SKILLs and documented in their own SKILL.md. ## Authoring a new msdmd application @@ -247,18 +270,20 @@ their own SKILL.md. 5. **Author a SKILL.md** in this lib with the convention spec, the executor's behavior, and at least one worked example. -`test-build/` is the canonical reference application. Read its -SKILL.md alongside this one to see the pattern fully realized; read -`doc-build/`, `cap-build/`, `deps-build/`, `owner-build/`, -`risk-boundary-build/`, and `ratios/` for additional applications over -the same parser contract. +`test-build/` is the canonical reference application for paired source +`CONTRACTS` and test `CHECKS`. Read its SKILL.md alongside this one to +see the pattern fully realized; read `doc-build/`, `cap-build/`, +`deps-build/`, `owner-build/`, `risk-boundary-build/`, and `ratios/` +for additional applications over the same parser contract. ## Anti-patterns -- **Don't define the contract in a separate file.** The whole point is - that the declaration lives next to the implementation. If you find - yourself writing `tests.yaml` or `docs.json`, you're outside the - doctrine. +- **Don't define an owned declaration in a detached side file.** The + whole point is that the declaration lives next to the module that + owns that fact. Source obligations belong in source; test evidence + belongs in the test module that owns the evidence. +- **Don't put `call:` in source `CONTRACTS`.** Source modules own + obligations, not test topology. Put executable targets in `CHECKS`. - **Don't make ids reflect implementation details.** `chat_returns_200` tells future-you nothing; `chat_get_other_owner_404` tells you what's protected. Ids are part of the documentation. diff --git a/.agents/skills/msdmd/collect.py b/.agents/skills/msdmd/collect.py index 5e34235..74b52f6 100644 --- a/.agents/skills/msdmd/collect.py +++ b/.agents/skills/msdmd/collect.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=141:7 imports_exports=6:3 calls_definitions=35:6 +# ratios: loc_comments=143:7 imports_exports=6:3 calls_definitions=35:6 """Generate repo-level msdmd collection-point TypeScript. This is a small stdlib helper for consuming repos that want to generate a @@ -20,6 +20,7 @@ "DEPENDENCIES", "OWNERS", "CONTRACTS", + "CHECKS", "MODULE_BUILD", "BOUNDARIES", "RATIOS", @@ -33,6 +34,7 @@ "owner": "owns", "covers": "covers", "call": "calls", + "proves": "claims_proves", "boundaries": "risk", } @@ -172,4 +174,4 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=141:7 imports_exports=6:3 calls_definitions=35:6 +# ratios: loc_comments=143:7 imports_exports=6:3 calls_definitions=35:6 diff --git a/.agents/skills/msdmd/collection.ts b/.agents/skills/msdmd/collection.ts index e86a6cf..697dca2 100644 --- a/.agents/skills/msdmd/collection.ts +++ b/.agents/skills/msdmd/collection.ts @@ -1,4 +1,4 @@ -// ratios: loc_comments=66:0 imports_exports=0:0 calls_definitions=1:0 +// ratios: loc_comments=67:0 imports_exports=0:0 calls_definitions=1:0 /** * Shared TypeScript shapes for repo-level msdmd collection points. * @@ -13,6 +13,7 @@ export type MsdmdBlockName = | "DEPENDENCIES" | "OWNERS" | "CONTRACTS" + | "CHECKS" | "MODULE_BUILD" | "BOUNDARIES" | "RATIOS" @@ -24,7 +25,7 @@ export type MsdmdFieldMap = Record; export interface MsdmdDeclaration { /** Repository-relative source file that owns the declaration. */ file: string; - /** msdmd application block name, such as CONTRACTS or DOCS. */ + /** msdmd application block name, such as CONTRACTS, CHECKS, or DOCS. */ block: MsdmdBlockName; /** Stable entry id declared inside the block. */ id: string; @@ -46,7 +47,7 @@ export interface MsdmdEdge { from: string; /** Target declaration id, capability id, owner, route, file, or external system. */ to: string; - /** Relationship kind: requires, exposes, owns, covers, calls, risk, etc. */ + /** Relationship kind: requires, exposes, owns, covers, calls, claims_proves, risk, etc. */ kind: string; /** Block that produced this edge. */ source_block: MsdmdBlockName; @@ -71,4 +72,4 @@ export interface MsdmdCollection { export function defineMsdmdCollection(collection: MsdmdCollection): MsdmdCollection { return collection; } -// ratios: loc_comments=66:0 imports_exports=0:0 calls_definitions=1:0 +// ratios: loc_comments=67:0 imports_exports=0:0 calls_definitions=1:0 diff --git a/.agents/skills/test-build/SKILL.md b/.agents/skills/test-build/SKILL.md index f3d74e2..1bf111e 100644 --- a/.agents/skills/test-build/SKILL.md +++ b/.agents/skills/test-build/SKILL.md @@ -1,23 +1,38 @@ --- name: test-build -description: Self-declaring contract tests built on msdmd. Each module owns the tests that protect its contracts via a `# === CONTRACTS ===` block; a runner discovers and executes them and reports per-contract status plus visible coverage gaps. Load this when adding tests that ride the msdmd convention, when refactoring a module that has CONTRACTS declarations, or when authoring a new contract test executor. +description: Self-declaring contract tests built on msdmd. Source modules own behavior obligations in `# === CONTRACTS ===` blocks; test modules own executable evidence in `# === CHECKS ===` blocks. Load this when adding tests that ride the msdmd convention, when refactoring a module with CONTRACTS/CHECKS declarations, or when authoring a contract/check audit or executor. --- # test-build — Contract tests on msdmd `test-build` is an application of [msdmd](../msdmd/SKILL.md). The foundational skill defines the comment-block convention, the universal -parser, and the gap-reporting requirement; this skill applies the -convention to test contracts and ships an executor. +parser, and the visible-gap requirement; this skill applies the +convention to behavior contracts and their executable witnesses. Read `msdmd/SKILL.md` first if you haven't — the block syntax, parser contract, and visibility rules below are inherited from there and not redefined. -## The block +For the ratified doctrine behind this split, see +[`doctrine/msdmd-checks.md`](../doctrine/msdmd-checks.md). -Every module that promises a contract declares it in a `CONTRACTS` -block: +## The split + +```text +CONTRACTS are obligations. +CHECKS are accountable witnesses. +audit reconciles the witness list against the obligation list. +``` + +Source modules own promises. Test modules own evidence. Neither owns +the other's declarations. + +## Source block: CONTRACTS + +Every module that promises behavior declares those obligations in a +`CONTRACTS` block. A contract says what must remain true; it does not +name the test topology. ```python # === CONTRACTS === @@ -25,131 +40,194 @@ block: # given: POST /api/v1/conversations with x-user-id=A and body.user_id=B # then: stored row has user_id=A; smuggled value is dropped # class: security -# call: tests.contracts.chat.test_create_owner_isolation # # id: chat_get_other_owner_404 # given: GET /api/v1/conversations/{id} where conv.user_id != caller # then: returns 404 (existence non-disclosure, not 403) # class: security -# call: tests.contracts.chat.test_get_other_owner_404 # === END CONTRACTS === ``` -## Field schema +### CONTRACTS field schema Required: | Field | Meaning | |---|---| -| `id` | Unique snake_case identifier, stable across refactors. Becomes the test handle in reports. | +| `id` | Unique snake_case identifier, stable across refactors. Becomes the contract handle in reports. | | `given` | Plain-English precondition / request shape. State the input, not the implementation. | | `then` | The asserted post-condition — the actual contract, not the steps to verify it. | -| `call` | Fully-qualified path to the test function. The executor imports and invokes this. Sync or async; `None` return on pass; raise (typically `AssertionError`) on fail. | Optional: | Field | Meaning | |---|---| -| `class` | Free-text tag (`security`, `correctness`, `idempotency`, `auth`, `regression`). The runner counts entries per class in the summary. | -| `requires` | Comma-separated list of other contract ids this one depends on (informational; the runner does not currently enforce ordering). | +| `class` | Free-text tag (`security`, `correctness`, `idempotency`, `auth`, `regression`, `doctrine`, `evidence`, `safety`). The runner counts entries per class in summaries. | +| `requires` | Comma-separated list of other contract ids this contract depends on. | | `since` | Version or date the contract was added. | | `deprecated` | If present, the runner skips and reports the entry as deprecated. | -## The contract for test functions +`call:` is not a CONTRACTS field in skill-lib. The call belongs to the +CHECKS entry that owns the executable evidence. + +## Test block: CHECKS + +A test module declares the checks it contributes in a `CHECKS` block. +A check is an evidentiary procedure: an executable claim to prove one +or more named contracts. + +```python +# === CHECKS === +# id: check_chat_create_owner_isolation_http +# proves: chat_create_owner_isolation +# call: self::test_chat_create_owner_isolation_http +# requires: python3, posix_shell +# timeout: 20 +# mutates: db +# cleanup: transaction_rollback +# +# id: check_chat_get_other_owner_404_http +# proves: chat_get_other_owner_404 +# call: self::test_chat_get_other_owner_404_http +# requires: python3, posix_shell +# timeout: 20 +# mutates: db +# cleanup: transaction_rollback +# === END CHECKS === +``` + +### CHECKS field schema + +Required: + +| Field | Meaning | +|---|---| +| `id` | Unique snake_case identifier for this evidentiary procedure. | +| `proves` | Comma-separated contract ids this check claims to prove. "Proves" means claims-to-prove; audit verifies linkage, not mutation sensitivity. | +| `call` | Executable target resolved by the runner. In Python skill-lib checks, the sanctioned no-exec audit form is `self::fn`. | +| `mutates` | Declared side-effect surface (`none`, `filesystem`, `db`, `network`, `external_service`, etc.). | +| `cleanup` | Cleanup/isolation obligation (`none`, `tempdir_teardown`, `transaction_rollback`, `finally_delete_created_rows`, etc.). | + +Conditionally required when consumed by the runner: -A test function: +| Field | Meaning | +|---|---| +| `requires` | Comma-separated host capabilities. A runner that reads this field must refuse execution when requirements are missing. | +| `timeout` | Per-check execution bound. A runner that reads this field must apply it to the spawned work, not merely print it. | + +Fields enter the schema in the same change that makes a runner consume +them. Declared-but-unread metadata is decorative and should be treated +as a defect, not diligence. + +## The contract for check functions + +A check function: -- Is importable at the path declared in `call:`. -- Is a plain function, sync or async. The executor awaits it if it's a - coroutine. +- Is resolvable at the path declared in `call:`. - Takes no required arguments. The executor does not inject fixtures - or context; the test is self-contained or pulls from the language's + or context; the check is self-contained or pulls from the language's standard environment (env vars, a known service URL, etc.). - Returns `None` on pass. -- Raises `AssertionError` on fail with a message that names the - violated invariant. Other exceptions are treated as `ERROR` - (test infra failure) rather than `FAIL` (contract violation). -- Cleans up any persistent state it creates. Tests run against the - same database / service as the executor; isolation is the test's - responsibility (uuid-prefixed identities, deletion in `finally`, - etc.). +- Raises `AssertionError` on behavior violation. The runner reports + this as `FAIL`. +- Lets unexpected exceptions escape. The runner reports these as + `ERROR` (infrastructure/harness failure) rather than `FAIL` + (contract violation). +- Cleans up any persistent state it creates. Isolation is the check's + responsibility unless the runner explicitly provides a fixture. -## Authoring a runner +## Authoring an audit -The reference Python runner uses `msdmd/parsers/universal.py`: +Audit is the cheapest runner mode: reconcile declarations without +executing checks. A Python audit for `self::fn` checks can avoid import +side effects entirely: ```python -from pathlib import Path -import asyncio, importlib, sys -from collections import Counter -from skill_lib.msdmd.parsers.universal import walk_tree - -async def run_one(entry: dict) -> dict: - call = entry.get("call") - if not call: - return {**entry, "status": "ERROR", "error": "missing 'call' field"} - mod_path, _, fn_name = call.rpartition(".") - try: - fn = getattr(importlib.import_module(mod_path), fn_name) - except Exception as e: - return {**entry, "status": "ERROR", "error": f"import: {e}"} - try: - if asyncio.iscoroutinefunction(fn): - await fn() - else: - fn() - except AssertionError as e: - return {**entry, "status": "FAIL", "error": str(e)} - except Exception as e: - return {**entry, "status": "ERROR", "error": f"{type(e).__name__}: {e}"} - return {**entry, "status": "PASS", "error": None} - -async def main(root: Path) -> int: - annotated, untested = walk_tree(root, "CONTRACTS") - results = [await run_one(e) for _, entries in annotated for e in entries] - counts = Counter(r["status"] for r in results) - for r in results: - sym = {"PASS": "✓", "FAIL": "✗", "ERROR": "!"}[r["status"]] - tail = "" if r["status"] == "PASS" else f" — {r['error']}" - print(f" {sym} {r['id']}{tail}") - print(f"\n{counts['PASS']} pass / {counts['FAIL']} fail / " - f"{counts['ERROR']} error " - f"{len(untested)} modules without CONTRACTS") - for p in untested[:20]: - print(f" · {p.relative_to(root.parent)}") - return 0 if counts["FAIL"] + counts["ERROR"] == 0 else 1 - -if __name__ == "__main__": - sys.exit(asyncio.run(main(Path(sys.argv[1]).resolve()))) +def resolve_self_call(spec: str, namespace: dict) -> object: + if not spec.startswith("self::"): + raise LookupError(f"only self::fn resolves without execution: {spec}") + fn = namespace.get(spec[len("self::"):]) + if not callable(fn): + raise LookupError(f"not callable: {spec}") + return fn +``` + +An audit MUST report, at minimum: + +```text +GAP has no CHECKS entry claiming to prove it +GAP claims unknown contract: +GAP call does not resolve: +GAP executable check has no resolving CHECKS declaration +``` + +Exit nonzero on any gap. A reconciler that has only ever said +"closed" is itself unverified; negative-test it by planting an orphan +contract, a phantom `proves` target, and an unresolvable call, then +observing the GAP. + +## Authoring an executor + +A full executor runs after audit or as part of the same command. It +should: + +1. Parse source `CONTRACTS` and test `CHECKS` using the msdmd parser. +2. Reconcile the graph before execution. +3. Refuse execution when consumed `requires` fields are unmet. +4. Apply consumed `timeout` fields to the actual spawned work. +5. Report per-check `PASS`, `FAIL`, and `ERROR` without aborting the + remaining checks on a single harness error. +6. Surface source contracts with no proving checks, checks proving + unknown contracts, and executable checks with no declaration. + +The visibility-of-gaps requirement is mandatory per msdmd. Drop it and +the runner stops being a msdmd application. + +## Semantics of "proves" + +`proves:` means claims-to-prove. Audit verifies linkage and call +resolution. A passing check demonstrates the declared witness ran +successfully. It does not prove the check is sensitive to every +possible breakage of the contract. + +Status vocabulary: + +```text +[implemented-prototype] runs; verified by session contact only +[test-backed] suite passes and audit closes the graph +[mutation-verified] checks demonstrated to notice planted breakage ``` -The visibility-of-gaps requirement (`untested` list) is mandatory per -msdmd. Drop it and the runner stops being a msdmd application. +Do not claim one rung above the evidence. ## Anti-patterns - **Contracts in test files instead of source files.** The contract - belongs to the module that promises the behavior; the test file just - implements the check. Putting the CONTRACTS block in the test file - inverts the doctrine and lets the source module be deleted without - the contract noticing. -- **Tests with no CONTRACTS entry.** Orphan tests don't run via the - runner; they're dead weight. If you write a test, declare it. + belongs to the module that promises the behavior; the test file owns + the check. +- **`call:` in CONTRACTS.** Source modules should not know test + topology. Put executable targets in CHECKS. +- **Executable tests with no CHECKS entry.** They may still run through + ad hoc tooling, but they are invisible to the msdmd evidence graph. +- **CHECKS proving unknown CONTRACTS.** This is an orphan witness; fix + the target id or declare the source contract. - **Implementation-shaped ids.** `chat_create_returns_200` tells you - nothing; `chat_create_owner_isolation` tells you what's protected. - Ids are part of the documentation. -- **Catching unexpected exceptions in the test to "make it pass".** - Let the exception escape — the runner will mark it `ERROR` (infra - problem) instead of `PASS` (contract holds), which is the correct - signal. + little; `chat_create_owner_isolation` tells you what's protected. +- **Importing during audit.** Python imports execute module top level. + Use no-exec resolution such as `self::fn`, or make import execution + an explicit non-audit mode. +- **Catching unexpected exceptions in the check to "make it pass".** + Let the exception escape so the runner can mark `ERROR` honestly. ## Versioning -Field schema additions are non-breaking and don't bump the version. -Field renames or removals are breaking; bump the major version and -note the migration in the lib README. The `CONTRACTS` block name -itself is stable — never reuse it for a different purpose. +The `CONTRACTS` block name remains stable for source-owned +obligations. `CHECKS` is the paired test-owned evidence block. +Field additions are non-breaking only when they are additive and +consumed by a runner. Field renames or removals are breaking; bump the +major version and note the migration in the lib README. hmmm -- Whether a future test-build runner should standardize quarantine/flaky/slow contract states or leave them to consuming repos. -- The exact boundary between an `ERROR` caused by infrastructure and a `FAIL` caused by violated behavior can get swampy; bring boots. +- The block type for harness/infrastructure tests that prove no product contract remains unnamed is still unsettled. +- Mutation-level verification is defined but not yet generalized across skills. +- Slow/flaky/quarantined states should enter only when a runner consumes them rather than as decorative labels. From 673679e9ea728da5a0febf5cf0ca5553bb74d0f5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 12 Jul 2026 10:02:52 +0000 Subject: [PATCH 2/2] fix(skills): vendor doctrine/msdmd-checks.md referenced by msdmd/test-build The re-synced msdmd/SKILL.md and test-build/SKILL.md (skill-lib@6f36340) link to `../doctrine/msdmd-checks.md`, but only the skill directories were vendored, so that relative link resolved to a missing `.agents/skills/doctrine/msdmd-checks.md` -- a dead reference to the ratified CONTRACTS/CHECKS doctrine. Vendor the shared doctrine file alongside the skills so the links resolve. (propagate_skills.py is skills-only and does not carry doctrine/; tracking that gap upstream in skill-lib separately.) Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01J4hD57ejBTZYsuLKSpoE7P --- .agents/skills/doctrine/msdmd-checks.md | 139 ++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 .agents/skills/doctrine/msdmd-checks.md diff --git a/.agents/skills/doctrine/msdmd-checks.md b/.agents/skills/doctrine/msdmd-checks.md new file mode 100644 index 0000000..336ac47 --- /dev/null +++ b/.agents/skills/doctrine/msdmd-checks.md @@ -0,0 +1,139 @@ +# msdmd doctrine: CONTRACTS / CHECKS / audit + +Status: ratified for skill-lib. Reference implementation: +`skill_lib/safety/repo_loto.py` + `tests/test_repo_loto.py`. + +## The triad + +``` +CONTRACTS are obligations. +CHECKS are accountable witnesses. +audit reconciles the witness list against the obligation list. +``` + +Source modules own promises. Test modules own evidence. Neither owns +the other's declarations. + +## Ownership + +**CONTRACTS live in the source module.** A contract is a normative +claim: what must remain true of this module's behavior. It belongs to +the module doing the promising, never to the file checking it. + +**CHECKS live in the test module.** A check is an evidentiary +procedure: an executable claim to prove a named contract. Test +topology is the test module's business; source modules do not carry +`call:` fields. There is no legacy bridge in skill-lib — new code +adopts this split directly. + +## Grammar + +Blocks are comment-fenced, one entry per `id:`, fields indented +beneath it: + +```python +# === CONTRACTS === +# id: loto_scope_enforced +# given: files touched outside the declared --files globs +# then: close refuses with the violating paths named +# class: safety +# === END CONTRACTS === +``` + +```python +# === CHECKS === +# id: check_scope_enforced +# proves: loto_scope_enforced +# call: self::test_scope_enforced +# requires: git, python3, posix_shell +# timeout: 20 +# mutates: filesystem +# cleanup: tempdir_teardown +# === END CHECKS === +``` + +CONTRACTS fields: `id`, `given`, `then`, `class` +(doctrine | evidence | safety | security). + +CHECKS fields, all consumed: `id`, `proves`, `call`, `requires` +(runner refuses to execute on hosts missing them), `timeout` (runner +sets the active subprocess bound per check), `mutates` and `cleanup` +(danger documentation read by humans deciding when a check may run). + +## The field-entry rule + +A field enters the schema in the same change that makes a runner +consume it, not before. Declared-but-unread metadata is F6 — +decorative preservation — and is treated as a defect, not diligence. +(`determinism` and `level` are currently out for exactly this reason; +they enter when a runner mode reads them. Note that `determinism` is +self-reported until a runner measures it by repeated execution.) + +## call: resolution + +The only sanctioned form is `self::fn` — a callable defined in the +file that declares the check. Dotted import paths are refused by the +audit: Python imports execute module top level, and **an audit that +executes is not an audit**. The `self::` form is also rename-immune; +copies and uploads reconcile identically. + +## audit + +The cheapest runner mode and the first one built: no execution, pure +reconciliation of the declared graph. It must report, at minimum: + +``` +GAP has no CHECKS entry claiming to prove it +GAP claims unknown contract: +GAP call does not resolve: +GAP executable check has no resolving CHECKS declaration +``` + +Exit nonzero on any gap. A reconciler that has only ever said +"closed" is itself unverified: every audit implementation must be +negative-tested by planting an orphan contract, a phantom `proves` +target, and an unresolvable call, and observing the GAP. + +## Semantics of "proves" + +`proves:` means *claims to prove*. The audit verifies linkage and +resolution, not that the check exercises the contract. The rung above +— break the module, confirm the witness notices (mutation-level +verification) — is named here so its absence stays visible. A module +is `[test-backed]` when its suite passes and its graph closes; it is +not thereby mutation-verified. + +## Evidence discipline + +- **Latest run wins, per identical command.** A rerun supersedes its + predecessor as standing evidence; history persists in working + memory until distillation. Superseding is by exact command string — + a passing narrow rerun does not launder a failing broad one. +- **Skipped or flaky checks are non-proof** unless explicitly waived, + and waivers ride the record; they do not erase it. +- **Execution semantics are evidence.** Records carry `shell:`, + exit codes, and timestamps, not just command strings. +- **Harness errors are results.** The runner reports TimeoutExpired, + CalledProcessError, and resolver failures as `ERROR` per check and + continues; an aborted run is not evidence about the unrun checks. +- **Subprocesses are bounded and owned.** Every spawned process runs + in its own session with a metadata-driven timeout; on expiry the + whole process group is killed. A hung witness is dismissed, on the + record, not waited on. + +## Status vocabulary + +``` +[implemented-prototype] runs; verified by session contact only +[test-backed] suite passes and audit closes the graph +[mutation-verified] checks demonstrated to notice planted breakage +``` + +Each term is earned, never assumed, and never claimed one rung above +its evidence. + +hmmm: the block-type for harness/infrastructure tests that prove no +contract remains unnamed; mutation-level verification is defined but +unbuilt; the audit grammar is currently specified by its reference +parser rather than by this document, and if a second parser disagrees, +one of them yields here, in writing.