Contracts

operations-and-schemas.md

Contract: the planned file set of contracts/mission-status/ for 1.1

This is the planning contract between the work packages of Mission mission-status-contract-1-1-01M42XJC: the exact set of files the 1.1 slice (a working name, not a version) adds or edits under contracts/mission-status/, the schema sketches, the status and code pairing, the examples and the provisional elements. The product contract is the OpenAPI module itself once built. This note fixes names and shapes so every work package works from one agreed set; an implementer may refine a description but must keep every property, name and rule listed here, or report the change in the hand-off report; the orchestrator records it in tracer-design-decisions.md (a work package never writes under kitty-specs/: plan, "Record-keeping and write scope"). No block below is a YAML example: sketches use plain text fences so the round-trip gate of the corpus (tests/contract/test_example_round_trip.py) has nothing to collect.

Operations (three, all GET, all tagged Missions)

The new operations reuse the existing Missions tag. A new tag would change the root tags list and make the bundle comparison of FR-017 observable 3 (b) unequal (research R-7).

operationIdPath key (root map)Path fileParametersResponses
getWorkPackageDetail/missions/{missionId}/work-packages/{wpId}/detailpaths/missions_missionId_work-packages_wpId_detail.yamlMissionId, WpId (both existing)200 WorkPackageDetail; 404 WorkPackageDetailNotFound; 500 WorkPackageDetailUnreadable; default shared Problem
listArtifacts/missions/{missionId}/artifactspaths/missions_missionId_artifacts.yamlMissionId200 ArtifactListing; 404 ArtifactNotFound; 500 ArtifactListingUnreadable; default shared Problem
getArtifactContent/missions/{missionId}/artifacts/contentpaths/missions_missionId_artifacts_content.yamlMissionId, ArtifactPath (new, query path, required)200 ArtifactContent; 400 ArtifactPathRefused; 404 ArtifactNotFound; 413 ArtifactTooLarge; 415 ArtifactNotText; 422 ArtifactSecretRefused; 500 ArtifactUnreadable; default shared Problem

The three file names were computed with contract_resolver.path_file_name. The detail path is a new path item (a sub-resource), so no existing path file changes. No operation declares a security scheme (as in v1). The getArtifactContent description states the order of decision (400, 404, 413, 500, 415, 422), the 262,144-byte cap, that path is decoded once at the HTTP layer, that a symlink is never followed and that the two root status records are not artifacts. The 422 response description and the readable property description list the credential kinds by name: a GitHub token (ghp, gho, ghu, ghs, ghr prefixes and the fine-grained github_pat prefix), an AWS access key id (AKIA and ASIA prefixes) and the header line of a PEM private key (a test compares these words with SECRET_PATTERNS, FR-019).

New files (all under contracts/mission-status/)

Parameters: parameters/ArtifactPath.yaml (query parameter path, required, schema ArtifactPath).

Schemas, one file each, title equal to the file stem, closed unless stated (additionalProperties: false), every property carrying exactly one x-source or x-derived:

SchemaProperties and rulesProvisional (AD-14)
WorkPackageDetailrequired: missionId (MissionId), wpId (WpId), subtasks (array of Subtask), dependencies (array of DependencyRef), reviewCycles (array of ReviewCycle), workspace (Workspace), ownedFiles (array of OwnedFile), artifactReferences (ArtifactReferences); every array present, possibly empty; embeds no WorkPackagereviewCycles, workspace
Subtaskid (RelativeReference), title (string, minLength 1, or null; human text), statusLane (StatusLane or null)none
DependencyRefwpId (WpId), title (string, minLength 1; never empty; the id as fallback), statusLane (StatusLane or null)none
ReviewCyclecycleNumber (integer, minimum 1), reviewedAt (RFC 3339 date-time with offset, or null), reviewer (ActorHandle, already nullable), verdict (ReviewVerdict or null), feedbackReference (string matching the pointer pattern, or null), artifactPath (ArtifactPath or null)the schema
WorkspacelaneId (string, strict, or null), laneBranch (string matching the branch pattern, or null), planningBranch (string or null), worktreePresent (boolean)the schema
OwnedFilepattern (RelativeReference), isGlob (boolean), changeState (ChangeState)none
ChangeStateenum changed, unchanged, unknown; description states the host dependence and the derivationnone (stable)
ArtifactReferencesprompt and spec, each ArtifactReference or null, both requirednone
ArtifactReferencepath (ArtifactPath), kind (ArtifactKind)via kind
ArtifactPathstring, minLength: 1, maxLength: 512, the pattern belownone
ArtifactKindenum of twelve values: review_cycle, work_package_prompt, spec, plan, tasks, data_model, quickstart, analysis_report, research, contract, checklist, other; the classifier order is the descriptionthe schema
ArtifactEntrypath (ArtifactPath), kind (ArtifactKind), sizeBytes (integer, minimum 0), modifiedAt (RFC 3339 UTC with Z, whole seconds), readable (boolean; the description lists the credential kinds)via kind
ArtifactListingmissionId, entries (array of ArtifactEntry, maxItems: 1000), truncated (boolean)truncated
ArtifactContentall required: path (ArtifactPath), kind, mediaType (string), encoding (const: utf-8), sizeBytes, redacted (boolean), content (string, may be empty)redacted, via kind
ArtifactRefusalCodeenum of seven values (invalid_artifact_path, not_found, artifact_too_large, artifact_not_text, artifact_secret, artifact_unreadable, artifact_listing_unreadable)the schema
ArtifactRefusalthe shared Problem plus required code ($ref ArtifactRefusalCode) and one if/then per code pinning status (400, 404, 413, 415, 422, 500, 500), built like StreamRefusal; no field carries file content, a host path or the Mission directorycode
WorkPackageDetailRefusalCodeenum not_found, source_unreadablethe schema
WorkPackageDetailRefusalthe shared Problem plus required code ($ref WorkPackageDetailRefusalCode) and the status rule (404 for not_found, 500 for source_unreadable)code

ArtifactPath pattern (ECMA-262 compatible, written with the hex escape, not a unicode escape: a unicode escape typed into some editing tools is decoded into a raw NUL character):

^(?!~/)(?![A-Za-z]:)(?!\.\.?(?:/|$))[^/\\\x00]+(?:/(?!\.\.?(?:/|$))[^/\\\x00]+)*$

Responses (responses/, each application/problem+json): ArtifactPathRefused (400), ArtifactNotFound (404, used by listArtifacts and getArtifactContent), ArtifactTooLarge (413), ArtifactNotText (415), ArtifactSecretRefused (422), ArtifactUnreadable (500, content), ArtifactListingUnreadable (500, listing) all with schema ArtifactRefusal; WorkPackageDetailNotFound (404) and WorkPackageDetailUnreadable (500) with schema WorkPackageDetailRefusal. The status of each is the key it is mounted under; the schema pins the code-to-status pairing.

Environment-dependent and derived descriptions (FR-016, FR-019)

Workspace.worktreePresent, OwnedFile.changeState and ArtifactEntry.modifiedAt say in their descriptions that they depend on the serving host. ReviewCycle's description says a cycle held only on a coordination surface is not shown and that [] means none was found on the primary planning surface. OwnedFile.changeState is x-derived with the full rule of the data model and inputs git_merge_base, changed_paths and is_glob_pattern; Subtask.title and DependencyRef.title are x-derived with the extraction rule; ArtifactReferences and the entries are x-derived with the eligibility rule. Citation inputs name symbols (never line numbers); the symbols were all resolved (research R-12).

Examples (21 files under examples/, named <Schema>.<case>.yaml) and the in-test required cases

ExampleCovers
WorkPackageDetail.populatedtitles and lanes on subtasks and dependencies; two review cycles (one approved, one changes_requested); a lane worktree present; owned files changed and unchanged and one glob; both artifact references
WorkPackageDetail.no-cyclesreviewCycles: [], a dependency with a null lane, a subtask with a null title
WorkPackageDetail.unknown-change-stateowned files unknown (no worktree) including a brace pattern; worktreePresent: false
WorkPackageDetail.null-workspacelaneId, laneBranch, planningBranch all null; empty subtasks, dependencies, ownedFiles; both references null
WorkPackageDetail.planning-lanelaneId: lane-planning, laneBranch: null, a planningBranch
WorkPackageDetail.unparseable-cyclea cycle with null reviewedAt, null reviewer, null verdict, null feedbackReference and a null artifactPath
ArtifactListing.populatedentries across several kinds with one readable: false entry
ArtifactListing.truncatedtruncated: true (a short list; a comment states a real truncated listing holds exactly 1000 entries)
ArtifactContent.markdownredacted: false
ArtifactContent.redactedredacted: true, [path] and [email] in content
ArtifactContent.jsonapplication/json
ArtifactContent.emptya zero-byte file: content: "", sizeBytes: 0
ArtifactRefusal.<code> (7: invalid-artifact-path, not-found, artifact-too-large, artifact-not-text, artifact-secret, artifact-unreadable, artifact-listing-unreadable)each code with its pinned status
WorkPackageDetailRefusal.<code> (2: not-found, source-unreadable)each code with its pinned status

Small leaf schemas (Subtask, DependencyRef, ReviewCycle, Workspace, OwnedFile, ArtifactReference, ArtifactEntry, the enums) carry short inline examples so that every new schema has examples (FR-019). The spec's required cases are enforced by extending tests/contract/test_mission_status_examples.py (an in-test manifest: every refusal code has an example; both redacted values occur; both readable values occur; truncated: true occurs; each ChangeState value occurs; a cycle with a null feedbackReference and one with a null reviewedAt occur), not by an entry in contracts/tools/fixtures/example_check/required_examples.json (OD-2). Every example is leak-free; a path in an example satisfies the ArtifactPath pattern; no example holds an absolute path, a host path, an e-mail address, a credential or an unredacted content.

Edits to existing files (the whole list; everything else under contracts/mission-status/ and contracts/_shared/ stays byte-identical)

openapi.yaml: info.version stays 1.0.0-SNAPSHOT (no new version, operator ruling 2026-10-05) and three path keys are added to the root map (no new tag, no change to servers or info otherwise). _index.yaml of schemas/, parameters/, responses/ and examples/: new entries only. CHANGELOG.md: the additions merge into the existing ## 1.0.0-SNAPSHOT section (no new heading), with ### Added (the three operations; the new schemas, parameter and responses; the two behaviours of the reads: invalid UTF-8 is a 415 and the root status records are no artifacts; a one-line reference to #5533 for the route mapping, naming the five route families covered and stating that the dossier overview and the snapshot export are not covered), ### Changed ("Nothing."), ### Removed ("Nothing."), ### Provisional (below) and ### Deferred to the next major version (the four v1 gaps, each with one line of reason: per-Mission staleness on MissionOverview, an actor on WorkPackageSummary, the project branch, the lane weights behind weightedPercentage; each would add a property to an existing response, which the breaking-change check treats as breaking).

The Provisional section of the 1.0.0-SNAPSHOT section names, as whole words: reviewCycles, workspace, ReviewCycle, Workspace, ArtifactKind, truncated, redacted, code, ArtifactRefusalCode, WorkPackageDetailRefusalCode, ArtifactRefusal, WorkPackageDetailRefusal, and kind (the properties that reference a provisional schema are counted by provisional_check under their own name; the final list is the output of provisional_check.py run on the finished tree, never a guess).

Order of work for the shared files

openapi.yaml, the four _index.yaml files and CHANGELOG.md are written by the contract work packages in sequence (artifact reads first, then the detail, then the CHANGELOG once the provisional list is final), never in parallel (plan, Implementation Concern Map).

tool-extension-and-reader.md

Contract: the tool extension (FR-015), the reference reader and the test map

Internal contract between the work packages of Mission mission-status-contract-1-1-01M42XJC. It fixes names, signatures and stable codes so every work package works from one agreed set. An implementer may refine a name but keeps every rule here, or report the change in the hand-off report; the orchestrator records it in tracer-design-decisions.md (a work package never writes under kitty-specs/: plan, "Record-keeping and write scope"). Plain text fences only (the corpus round-trip gate collects yaml fences of this directory).

A. The four tool files of FR-015 (the whole edit set under contracts/tools/)

fixture_builder.py

  • STRICT_FIELDS gains id, laneId, laneBranch, planningBranch, pattern, feedbackReference, reviewer, kind, mediaType, changeState. title stays out (human text). Measured: leak_scan.py --root contracts still exits 0 on every existing file (research R-8).
  • KINDS and _plant gain 25 kinds, every plant built from fragments at run time (no leaking text in source), each next to a clean control on the same root, as today. 22 are red-first (undetected by the unchanged scanner: run on the planning base for the ten strict-name plants, which need the new STRICT_FIELDS entries, and for all twelve artifact-path forms, none of which the human scan reports) and 3 are regression controls (see the table; they pass on the planning base and are not counted as red-first):
Kind familyKindsPlanted intoExpected code
strict name (10)strict-id, strict-laneId, strict-laneBranch, strict-planningBranch, strict-pattern, strict-feedbackReference, strict-reviewer, strict-kind, strict-mediaType, strict-changeStatea value that is a host path only in the strict class (a leading-tilde path) under that key; the control holds a clean value under the same keyHOST_PATH
artifact path (13)artifact-path-empty, -too-long (513 characters), -absolute, -tilde, -drive-letter, -backslash, -nul, -line-break, -empty-segment, -trailing-slash, -dot-segment, -dotdot-segment, and artifact-path-reference (the same malformation under the key artifactPath)entries: [{path: <malformed>}] (the key artifactPath for the last)ARTIFACT_PATH_MALFORMED
content and title (3), regression controls, not red-firstcontent-host-path-and-email, credential-in-content, credential-in-titlea key content holding a host path and an e-mail address; a key content holding a token; a key title holding a tokenHOST_PATH and EMAIL; SECRET; SECRET

Why the third family is labelled this way (research R-15 measures which kinds the unchanged scanner already detects, which gives the split of 22 red-first kinds and 3 regression controls): a key named content or title is already human text, so a host path, an e-mail address or a token under it is found today by both passes of the unchanged scanner (run on the planning base: EMAIL and HOST_PATH, SECRET, SECRET). These kinds would still pass if the FR-015 edit were reverted. They are kept because the artifact-path edit changes how path and artifactPath are judged, and a wrong edit (one that applied the new class to every string, or masked a whole line) would blind the human scan for these keys: the three kinds fail then, so they guard the edit. The per-endpoint planted pair that is red-first lives at reader level (below).

The control of every artifact-path kind holds the legitimate odd names of AD-19: a name with a home/<x>/ directory segment, one with a space, one with an accented letter (all verified clean in both passes on the planning base), and a name with an at sign built at run time (OD-4 is decided: the scoped text pass). No committed file under contracts/, examples and committed controls included, holds an at-sign path (a YAML file cannot be built at run time); under the option not chosen (OD-4 Y) the same would hold for every file, because the raw-text pass reports it as EMAIL (verified: the e-mail pattern matches a retina-style image name, a stem, an at sign, a scale factor and an extension; a scan fixture that needs one builds it at run time from fragments); the at-sign name is proved by the run-time-built scan tests (WP02) and at reader level. A scan that flags a control name fails its own control (CONTROL_FLAGGED).

  • Plant shape (changes fixture_builder.py beyond the kind list). Today _plant returns one (field, value, codes) and build writes a flat mapping, so it cannot put a value under entries[0].path or give each kind its own control. The new contract: _plant(kind) returns a Plant(fragment, control_fragment, field, codes); fragment is a nested document fragment deep-merged into the planted document (a top-level id: <value> and entries: [{path: <malformed>}] are both expressible), control_fragment is the clean fragment deep-merged into the control document of that kind (default empty; each artifact-path kind carries the odd names above), field is the dotted key path reported in BuiltFixture.field; mappings merge recursively and lists are replaced, not concatenated. build writes control = base control merged with control_fragment and planted = base control merged with fragment (the forbidden-property kind keeps its empty base). BuiltFixture keeps its four fields. run_negative_cases.build_leak is unchanged by design (it calls build and unlinks only the planted file, so the control stays a complete clean file); test_run_negative_cases.py moves only through the manifest length. Tests that move: tests/contract/test_fixture_builder.py (the exact KINDS set, 33 kinds: the eight existing plus 25) and a new test that the eight existing kinds still build byte-identically: the two files of each old kind hashed with blake2b (hashlib.sha256 is banned) against digests pinned at the start of IC-02 from the unmodified builder (this test passes before and after the edit by design, so it is not a red-first claim). The unchanged run_self_test() runs every kind on every leak_scan invocation, so a blind scanner fails loudly (PLANTED_NOT_DETECTED).
  • The module docstring changes with the kinds. Reader-level planted pairs (red-first, outside contracts/tools/): payload_leaks over a built ArtifactContent whose body was left unredacted must report HOST_PATH and EMAIL, and over a built detail whose subtask or dependency title holds a credential it must report SECRET; each fails if the reader stops redacting or withholding (artifacts and detail test modules; clean twins pass).

leak_scan.py

  • ARTIFACT_PATH_KEYS = frozenset({"path", "artifactPath"}). In _Scanner.walk, a string under one of those keys whose ancestors hold no x-source or x-derived key is checked by malformed_artifact_path instead of the strict and human host-path and e-mail patterns of the structured pass (AD-19). scan_text is a second, independent pass: it runs the human patterns (host path, e-mail and SECRET) over every non-blank raw line of every file. Under OD-4 X scan_text takes a second argument, the text with the artifact-path value spans blanked: artifact_path_spans(text) composes the YAML (yaml.compose, scalar start and end character indexes) and returns the span of each scalar value of a key in ARTIFACT_PATH_KEYS reached without passing through an x-source or x-derived key; mask_spans blanks those characters (newlines kept, so line numbers hold); host-path and e-mail findings come from the blanked text, SECRET findings from the original line, so a token in a path value is still reported. JSON files and a YAML file that does not parse are scanned unmasked. A test pair proves the mask is bounded: a flow mapping line holding an at-sign path and a title with an address reports the title and not the path; an x-source path holding an address is reported; a token in a path value is reported; a contract example holding an at-sign path passes the whole scan; the module docstring ("nothing is exempt") is rewritten to name the masked span. Under OD-4 Y scan_text is unchanged and no contract file holds an at-sign path. The prototype of X was run on a scratch copy of the tools (research R-14). Under x-source and x-derived the key path is a repository path (226 occurrences) and keeps the human scan.
  • malformed_artifact_path(path) -> str | None is the one definition of the predicate on the tooling side: it returns a stable reason (empty, too_long, absolute, tilde, drive_letter, backslash, nul, line_break (any character str.splitlines treats as a line break: LF, CR, VT, FF, the three information separators FS, GS and RS, NEL, U+2028, U+2029; added by WP02's review, orchestrator 2026-10-04: a line break must make the path malformed so the artifact-path mask can never hide a leak on a second line), empty_segment, trailing_slash, dot_segment, dotdot_segment) or None. The reference reader and payload_leaks import it through the existing loader (tests importing tools is the allowed direction); it lives here, inside the slice file set, rather than in leak_patterns.py, which is outside it. Three-way agreement is tested: this function, a literal restatement of the spec's predicate in the test, and the ArtifactPath schema pattern over the same table of cases.
  • New finding code ARTIFACT_PATH_MALFORMED; the last line gains values_artifact_path=N after values_all=N (the existing last-line assertions of test_leak_scan.py are prefix and key-presence checks and keep passing; a new assertion covers the added key). No floor on the class in check() for every root (fixture roots hold no such key); the real-contract test asserts the count is positive once the examples exist.

negative_cases.json

One case per new kind, {"id": "leak-scan-<kind>", "tool": "leak_scan.py", "build": "leak:<kind>", "plant": {"args": ["--root", "{root}"], "code": <code>}, "control": {"args": ["--root", "{root}"]}}, appended in one block. Cases that need a committed fixture are not allowed (no new file under contracts/tools/fixtures/); every other planted negative of this Mission is an in-test case under tests/contract/.

enum_pins.json

Under mission-status: ChangeState (3 values), ArtifactKind (12), ArtifactRefusalCode (7), WorkPackageDetailRefusalCode (2). enum_pin_check fails ENUM_UNREADABLE for a pinned title that is not in the module, so each pin lands in the same work package as its schema. tests/contract/test_enum_pin_check.py changes its pinned line counts: enums=3 values=19 to enums=7 values=43.

B. Reader modules (test tree, tests/contract/, none is a test module)

ModuleHoldsImports from
_mission_status_payloads.py (edited)Contract wanted titles (adds WorkPackageDetail, ArtifactListing, ArtifactContent, ArtifactRefusal, WorkPackageDetailRefusal); payload_leaks artifact-path class; new FLOORS fields; tree_fingerprint with directory names; write_fixture_mission extended with a files mapping (text or bytes), lanes and tasks_md arguments; git_init and commit_all (the campsite helpers)existing
_mission_status_artifacts.py (new)FileSystem seam and REAL_FS; resolve_mission(repo_root, mission_id); is_eligible; classify; media_type_of; redact; has_credential; read_content(...); list_artifacts(...); artifact_references(...); modified_at(...); ArtifactOutcome(status, body)_mission_status_payloads (MissionSource, load_source), leak_scan (the predicate), leak_patterns
_mission_status_detail.py (new)HostCapability, NO_WORKTREES; work_package_universe; subtask_titles; dependency_refs; review_cycles; workspace_of; matches_owned_file; change_states; build_work_package_detail(...); DetailOutcome(status, body)_mission_status_artifacts (eligibility), _mission_status_payloads (Projector, load_source, STATUS_LANES)

Entry points return an outcome (a status and a body), never raise for a refusal: the three operations are testable as the service answers them. Public readers only (wp_task_files, read_authored_wp_frontmatter, reconstruct_wp_view, read_lanes_json, lane_branch_name, resolve_workspace_for_wp, build_review_cycle_pointer, ReviewCycleArtifact.from_file, is_glob_pattern, git_merge_base, kernel.git.changed_paths, get_current_branch); the only private names in the test tree are the two matcher copies, imported by the parity test alone.

C. Test map (X-pure: three new modules, the plan's recommendation and the layout it is written for; X: the same plus the live module row; Y: fallback with no new module)

The live-baseline row below exists under X only: under X-pure there is no committed live test, the comparison against the contract tree on main is the W-7 recorded command (no tag exists or is needed, operator ruling 2026-10-05), and ACK-1 is void; the three pure rows, the registrations (three modules, six edits; X: four modules, eight edits) and the lane binding hold for the X-family (X or X-pure).

ConcernX home (new test module, run by tests (contract tools))Y home (existing module)
Artifact reader: eligibility, containment, listing, content, boundary, encoding, credentials, redaction, faults, mutationstests/contract/test_mission_status_artifacts.pytest_mission_status_payloads.py (router corpus job)
Detail reader: universe, subtask titles, dependencies, cycles, workspace matrix, change state with real git, matcher parity, mutationstests/contract/test_mission_status_detail.pytest_mission_status_payloads.py
1.1 proof (the additive proof; "1.1" is the working name, not a version): unchanged version, additive diff, bundle comparison, scope function (name-status entries; allowed set as data that carries the admitted statuses), terminology scan, CHANGELOG section (1.0.0-SNAPSHOT), provisional names, credential-kind descriptions, refusal examplestests/contract/test_mission_status_contract_1_1.py (green on every clone)test_mission_status_examples.py
Live baseline test (VOID under the operator ruling 2026-10-05: no tag exists, so no such test; kept as the record) (fails, never skips, while the tag is missing; the only accepted red until the tag, and red in any clone without the tag; ACK-1, X only; retired by its named owner at the 1.1.0 release or by the next contract-changing Mission)tests/contract/test_mission_status_baseline_live.py (its own module, markers contract and corpus only, so the module above is green and the nightly does not select it)none: no committed live test; the comparison against the tag is a recorded command at W-7 (a live id in the examples module would inherit its fast marker)
Corpus reality check: floors, independent counts, readable invariant, refusal codes, verdict and cycle floors, fingerprinttests/contract/test_mission_status_reality.py (edited; router corpus job)same
Tool extension teststest_leak_scan.py, test_fixture_builder.py, test_enum_pin_check.py, test_run_negative_cases.py (edited)same
Examples, required cases, path maptest_mission_status_examples.py (edited: EXPECTED_PATH_KEYS becomes eight paths, the "five paths" test, MIN_EXAMPLES, the required cases)same

Under the X-family the artifacts module and the 1.1 proof module carry the markers of their neighbours (contract, fast, corpus): they read no tag (none exists), no history and start no subprocess, so they are safe on the nightly's shallow checkout, which is verified in a git clone --depth 1 scratch run. The detail module carries contract, corpus and git_repo and not fast: it builds real git repositories and worktrees, and pytest.ini defines fast as pure logic with no subprocess or git; without fast the nightly fast or unit run does not select it and it runs in the router job tests (contract tools) (the reality module's fast marker beside its git init is a precedent not copied under the X-family). The live module carries contract and corpus only, never fast or unit (plan, ACK-1 b: the nightly selects fast or unit over tests/contract on a tag-less shallow checkout, and a fast live test would be red there on every run). Each of the three pure modules (four under X) needs two registrations outside the spec's slice file set (operator decision OD-1; six edits in all, eight under X, written by the work packages that share those two files, in dependency order, one lane by construction; plan, "Lanes"): one --deselect tests/contract/<module> in the built-in-corpus-suite command of .github/workflows/packs.yml and one row in _CORPUS_MARKED_MODULES of tests/architectural/test_ci_corpus_trigger_completeness.py. Both were proven necessary (research R-6) and sufficient (200 passed, 1 skipped on the gate files). Under Y no registration changes and the tests of the new modules join the serial router corpus job; the modelled cost (research R-6) fits NFR-001 either way. Y cost on markers: the detail concern's real-git change-state tests land in test_mission_status_payloads.py, whose module marker includes fast, so they inherit it (the reality module's precedent) unless that module's marker is changed, which removes its pure tests from the nightly selection; a module-level marker cannot be split per test (tracer-tooling-friction F-19). This is a cost of Y stated in plan OD-1, so the rule above is the X-family rule and the test map's "nothing else changes" holds for the X-family only. Either registration edit alone also selects the architectural-heavy battery on the pull request (plan, OD-1).

D. Floors in code and pinned fixtures

FLOORS gains (tasks phase: added by WP08, where the reality check consumes them, not by WP05): detail_work_packages 3000, status_log_only 26, subtask_ids 14000, table_rows 12000, checkbox_rows 8300, owned_file_entries 13000, dependency_entries 3100, review_cycle_files 1200, missions_with_cycles 210, unparseable_cycles 150, verdict_cycles 290, artifact_entries 14000, readable_true 14000, redacted_files 970, missions_without_lanes 72 (each pinned below the research R-5 measurement by the stated margin rule). The existing three floors stay. A new fixture tests/contract/fixtures/mission_status_artifacts_expected.json lists, by name, the two 413 files, the two not-UTF-8 files and the three NUL files with their codes, the work package with no prompt file, and the Mission with duplicate work package ids; each named entry must still be present and behave as stated (a subset check; an unnamed new anomaly is printed, never a failure). Planted 422 coverage is a fixture Mission built at run time, since the corpus holds none. The readable invariant is checked against an independent oracle, not against the reader's own function (plan D-P8): the reality module classifies every eligible file from its bytes alone (size over 262,144 gives 413; a NUL byte, then a strict UTF-8 decode failure, gives 415; a SECRET_PATTERNS match in the decoded text gives 422; otherwise 200; in that order), and asserts the expected status and the expected readable for each file against the listing entry and against the content read. The equality readable == (content read answers 200) stays only as a wiring guard and is labelled so in the test.

E. Stable test-failure messages

Every new corpus-level assertion states its examined and discovered counts in its failure text and fails when either is zero. A fault-injection test fails with the injected fault did not fire when its counter is zero. A mutation test fails with the mutation was not killed: <name> when the planted defect leaves its target test green.