Contracts

operations-and-schemas.md

Contract: the planned file set of contracts/mission-status/ for the health, drift and ops reads

This is the planning contract between the work packages of Mission mission-status-health-drift-ops-01M464D3: the exact set of files the slice 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. No new contract version: info.version stays 1.0.0-SNAPSHOT (spec PR-1).

Operations (two new, one changed), tags and parameters

operationIdPath key (root map)Path fileTagParametersResponses
getProject (changed)/projectpaths/project.yaml (description only)Project (existing)none200 Project (five new required properties); default shared Problem
getDriftReport/driftpaths/drift.yamlDrift (new)optional query missionId (parameters/DriftMissionId.yaml, over the MissionId schema; the existing parameters/MissionId.yaml is a path parameter and is not reused)200 DriftReport with the header Cache-Control: no-store documented; 404 DriftMissionNotFound (DriftRefusal, code mission_not_found); 500 DriftScanUnreadable (DriftRefusal, code drift_scan_unreadable); default shared Problem (a non-ULID missionId is 400 under default)
listOpsInvocations/ops/invocationspaths/ops_invocations.yamlOps (new)shared PageSize and PageCursor; optional query profile (parameters/OpsProfile.yaml, pattern of profileId)200 OpsInvocationPage; 400 the existing PageCursorRefused; 500 OpsUnreadable (OpsRefusal, code ops_unreadable); default shared Problem; carries x-provisional (AD-7)

The two path-file names were computed with contract_resolver.path_file_name (/drift gives drift.yaml, /ops/invocations gives ops_invocations.yaml). No operation declares a security scheme (as in v1). The root tags list of openapi.yaml gains Drift and Ops with one-line descriptions; the previous slice added none, so the additive comparison removes them too (plan PD-4). The listOpsInvocations description states the skipped-record rule (legacy or unreadable records are skipped and counted in skippedCount), that totalCount can lag the newest records, and that the operation never starts an agent or writes. The getDriftReport description states: a scan reads, never repairs; Rescan is a second call; scannedAt is the time of this scan; a Mission whose declared coordination branch no longer exists is scanned from its own directory, which can lag the coordination surface; the stock coordination resolver may query remotes, so the outcome depends on the network; an unreachable remote leaves the Mission's own directory as the read directory (a 200 with no fallback entry), a reachable remote that lacks the declared branch gives the coordination_branch_deleted fallback, and any other resolver error is a 500 drift_scan_unreadable (FRESH3-001, as ruled by the operator ruling at WP07, 2026-10-06); findings: [] means the scan ran and found none.

New files (all under contracts/mission-status/; 49 in all)

Parameters: parameters/DriftMissionId.yaml (query missionId, optional), parameters/OpsProfile.yaml (query profile, optional, pattern ^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$).

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 (the field catalogue of the spec is the authority; citation inputs name symbols, never line numbers):

SchemaProperties and rulesProvisional (FR-023)
ProjectHealthenum healthy, schema_drift; the description says the range is the serving build's and rolls up the three non-compatible schema statusesvia health
DriftReportscannedAt, findings (array of DriftFinding, maxItems: 1000), truncated; the description states the cap, the order, the fallback rule and the network behaviourtruncated
DriftFindingthe ten members of the data model, all required; missionId and artifactPath non-null; the description qualifies the kind-1 summaries (the disagreement is with the reducer replay at the snapshot's own generation, not with the true event log)kind, remedy, laneComparison
LaneComparisonwpId (WpId), persistedStatusLane and derivedStatusLane (StatusLane or null); the description says "status lane", never "code lane"via laneComparison
DriftKindenum of three values; the description states the two checks behind lane_branch_missing, that a manifest predating mission_slug is not evaluated for it, "no local branch", and the decision on derived_view_stale (considered, not shipped, why)the schema
DriftSeverityenum error, warningnone
DriftAuthorityenum event_log, gitnone
DriftSideenum status_json, lanes_jsonnone
DriftRemedyenum materialize_status; the description maps it once to spec-kitty agent status materialize --mission <slug> (not spec-kitty materialize, which writes only derived views), says the client builds the text and takes the slug from MissionHead.slugthe schema
DriftRefusalCodeenum mission_not_found, drift_scan_unreadablethe schema
DriftRefusalthe shared Problem plus required code ($ref DriftRefusalCode) and one if/then per code pinning status (404, 500), built like ArtifactRefusalcode
OpsInvocationPageitems, pageInfo (shared), totalCount, skippedCounttotalCount, skippedCount
OpsInvocationthe twelve members of the data model, all required; the description names the five omitted record fields and uses the glossary definition of an Opevidence
OpsModeOfWorkenum task_execution, advisory, mission_step, querynone
OpsInvocationStatusenum open, closed; the description says closed counts the closure spinenone
OpsOutcomeenum done, failed, abandonednone
OpsClosedByenum agent, doctor_sweepnone
OpsEvidencekind (OpsEvidenceKind), value (maxLength: 512), redacted (boolean)via evidence
OpsEvidenceKindenum repo_path, url, textnone
OpsRefusalCodeenum ops_unreadablethe schema
OpsRefusalthe shared Problem plus required code ($ref OpsRefusalCode) and the status rule (500)code

Responses (responses/, each application/problem+json): DriftMissionNotFound (404) and DriftScanUnreadable (500) with schema DriftRefusal; OpsUnreadable (500) with schema OpsRefusal. The status of each is the key it is mounted under; the schema pins the code-to-status pairing. The 404 no longer reads "shared Problem shape".

Patterns written with the hex escape. Any pattern that needs a NUL or a backslash is written \x00 and \\; a unicode escape typed into some editing tools is decoded into the raw character. Tests build a NUL with chr(0) and a backslash with chr(92).

Descriptions that carry rules (checked by committed tests)

  • Project: names the five properties; no longer says "a name and a count only"; says the cost grows with the Mission count and that each Mission's read directory is resolved through git queries that can include network probes (so "safe to call once per recent project" means side-effect-free, not network-free); says GET /project runs no drift scan and never depends on a finding.
  • schemaVersion: the value is the CLI's coercion (a float or boolean in the file is served as an integer). health: the serving build's range; provisional. currentBranch: null means "no branch name this contract can carry" and does not separate detached, absent, unreadable or unrepresentable; a served name is user-authored text served as stored. lastActivityAt: null when no Mission is listed and when every listed Mission has null activity.
  • evidence: null means "no evidence, or withheld because it holds a credential"; names the credential kinds checked (GitHub classic and fine-grained tokens, AWS access key id, private-key header; a test compares these words with SECRET_PATTERNS) and says other secret shapes are not detected.
  • OpsInvocation: the omitted fields by name (request_text, model_id, governance_context_hash, governance_context_available, router_confidence); a committed test fails when one name is missing, and the closed schema rejects a payload carrying any of them.
  • DriftFinding.summary: the six fixed sentences of the data model, written in the schema description and compared literally by the reference reader.

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

ExampleCovers
Project.example (edited), Project.all-null, Project.schema-drifthealthy; every nullable property null with schema_drift; schema drift with a branch and an activity instant
DriftReport.cleanfindings: [], truncated: false
DriftReport.populatedthe three kinds, a lane comparison with a one-sided work package, a warning variant, a null remedy
DriftReport.corrupt-jsonCORRUPT_JSON with laneComparison: [] and remedy: materialize_status
DriftReport.truncatedtruncated: true (a short list with a comment; a real truncated report holds exactly 1000 findings)
DriftRefusal.mission-not-found, DriftRefusal.scan-unreadableeach code with its pinned status
OpsInvocationPage.first, .last, .emptyhasNextPage true with a cursor; the last page; items: [] with totalCount: 0 and skippedCount: 0
OpsInvocation.open, .closed-by-agent, .closed-by-doctor-sweepthe three closure states, completedAt of the closure for the sweep
OpsInvocation.evidence-repo-path, .evidence-url, .evidence-url-redacted, .evidence-text, .evidence-text-redacted, .evidence-nulleach evidence kind with and without redaction, and evidence: null
OpsRefusal.ops-unreadablethe one code with its pinned status

Small leaf schemas (LaneComparison, OpsEvidence, the enums) carry short inline examples so that every new schema has examples. 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, each evidence kind occurs, each DriftKind and each sourceCode of the pair table occurs, each closure state occurs, an example with a null missionId or artifactPath is refused), not by an entry in contracts/tools/fixtures/example_check/required_examples.json (a path outside the file set). Every example is leak-free: no host path, e-mail address, credential or unredacted evidence value; a path in an example satisfies the artifact-path rule. MIN_EXAMPLES and EXPECTED_PATH_KEYS (ten path keys) move with the files.

The provisional elements and their CHANGELOG tokens (FR-023)

Exactly these elements carry x-provisional; each token is a whole word in the ### Provisional heading of the 1.0.0-SNAPSHOT section (the output of provisional_check.py run on the finished tree is the final list, never a guess):

health                         property of Project
DriftKind                      schema (with the two checks, the legacy-manifest rule, "no local branch", the derived_view_stale decision)
kind                           property of DriftFinding   (qualified: DriftFinding.kind)
remedy, DriftRemedy            property of DriftFinding; schema
laneComparison                 property of DriftFinding
truncated                      property of DriftReport    (qualified: DriftReport.truncated)
DriftRefusalCode               schema;  code of DriftRefusal  (qualified: DriftRefusal.code)
OpsRefusalCode                 schema;  code of OpsRefusal    (qualified: OpsRefusal.code)
evidence                       property of OpsInvocation
totalCount, skippedCount       properties of OpsInvocationPage
/ops/invocations               the operation (carries the legacy-record rule, AD-7)

kind, code and truncated are already named in that section by the previous slice (ArtifactKind, ArtifactRefusalCode, ArtifactListing), so provisional_check is satisfied by the bare word; a committed test asserts the four qualified names literally, each with a plant that removes it.

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

  • schemas/Project.yaml: description rewritten, required extended, five properties added (x-source or x-derived each; health x-provisional); examples/Project.example.yaml updated and two examples added; paths/project.yaml: description only.
  • openapi.yaml: info.version stays; two path keys and two tags added. The four _index.yaml files: new entries only.
  • CHANGELOG.md, merged into the existing ## 1.0.0-SNAPSHOT section (no new heading): ### Added (the two operations; the new schemas, parameters and responses; the two read behaviours that readers will meet: legacy and unreadable Op records are skipped and counted, a manifest predating mission_slug is not evaluated for kind 3), ### Changed (the Project change as a pre-release shape change, with the four non-provisional additions and why the tooling reports them), ### Removed ("Nothing."), ### Provisional (above), ### Deferred to the next major version (three gaps: per-Mission staleness on MissionOverview, an actor on WorkPackageSummary, the lane weights behind weightedPercentage; the line "The project branch" and the word "Four" are gone), plus the statements of FR-023: the cap of 1000, the not-shipped decision of derived_view_stale with its reason, that info of the audit Severity is not carried, that missionId and artifactPath of DriftFinding are non-null, and the design note for the read service (resolve coordination locally; out of scope, no src/ change).
  • contracts/tools/enum_pins.json: thirteen pins (see the data model); tests/contract/test_enum_pin_check.py moves its pinned counts: line.

Order of work for the shared files

openapi.yaml, the four _index.yaml files, enum_pins.json and CHANGELOG.md are written by the contract work packages in sequence (the Project vertical, then Drift, then Ops, then the final CHANGELOG statements), never in parallel (plan, Lanes). The JVM replay (J-1) runs once the last of them is approved.

tool-extension-and-reader.md

Contract: the tool extension (FR-027), the reference reader, the mutation catalogue and the test map

Internal contract between the work packages of Mission mission-status-health-drift-ops-01M464D3. It fixes names, signatures and stable messages so every work package works from one agreed set. An implementer may refine a name but keeps every rule here, or reports the change in the hand-off report; the orchestrator records it in tracer-design-decisions.md (a work package never writes under kitty-specs/). Plain text fences only (the corpus round-trip gate collects yaml fences of this directory). The names of the reader modules and functions are the plan's call (spec FR-025); the rules are the spec's.

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

fixture_builder.py

  • STRICT_FIELDS gains six names through a second tuple beside _NEW_STRICT_NAMES (the names are matched by exact property name in leak_scan.py, str(child) in STRICT_FIELDS): specKittyVersion, currentBranch, profileId, action, invocationId, sourceCode. The existing profile entry does not cover profileId. title and value stay out (human text). Measured at plan time: none of the six names occurs as a key anywhere under contracts/ today, so leak_scan.py --root contracts still exits 0; IC-02 re-runs it.
  • KINDS gains one strict-<name> kind per new name (34 kinds become 40: eight old, sixteen strict, twelve artifact-path forms, the reference kind, three regression kinds). The plant is the existing generic one for strict names (a leading-tilde path under that key, which is a host path only in the strict class) with its clean control on the same root; the plants are built from fragments at run time, no leaking text in the source. All six are red-first: the unchanged scanner does not report them (the key is not strict, and a leading-tilde path is not a human host path).
  • tests/contract/test_fixture_builder.py moves its exact KINDS set; the existing test that the eight old kinds still build byte-identically stays (it passes before and after by design, so it is not a red-first claim).

leak_scan.py (not expected to change)

The scanner derives STRICT_FIELDS from the builder and judges artifactPath as an artifact-path key already, so the new properties need no scanner edit. IC-02 edits it only if a red-first plant shows the unchanged scanner blind to a new case (plan PQ-4, Departs 7), in the same commit, with the file added to the allowed data. A planted profileId example holding a bare absolute path must fail the scan and its clean control pass (tests/contract/test_leak_scan.py); a committed test asserts each of the six names is in STRICT_FIELDS.

negative_cases.json

Six cases appended in one block, in the existing shape: {"id": "leak-scan-strict-<name>", "tool": "leak_scan.py", "build": "leak:strict-<name>", "plant": {"args": ["--root", "{root}"], "code": "HOST_PATH"}, "control": {"args": ["--root", "{root}"]}}. No case needs a new file under contracts/tools/fixtures/. The slice adds no lint rule, so it adds no JVM-, vacuum- or oasdiff-tagged case (the existing lint plants, including enum-case, cover the new enums); the camel-case enum plant is run once at J-1 on a scratch copy.

enum_pins.json

Under mission-status: ProjectHealth (IC-03); DriftKind, DriftSeverity, DriftAuthority, DriftSide, DriftRemedy, DriftRefusalCode (IC-04); OpsModeOfWork, OpsInvocationStatus, OpsOutcome, OpsClosedBy, OpsEvidenceKind, OpsRefusalCode (IC-05). enum_pin_check fails ENUM_UNREADABLE for a pinned title absent from the module, so each pin lands in the same work package as its schema; test_enum_pin_check.py moves its pinned line at each of the three (enums=8 values=45, then enums=14 values=57, then enums=20 values=72). Every value is snake_case; a planted camel-case value fails the pin check and, in J-1, the JVM lint.

B. Reader modules (test tree, tests/contract/; none is a test module; every name holds mission_status)

ModuleHoldsImports from
_mission_status_memo.pyResolverMemo (created per run), MemoEntry (raw outcome: a directory or the exception; the subprocess count of the call), memo_resolve(memo, repo_root, name) (the one place the resolver is called), the counting context (a pytest.MonkeyPatch around subprocess.Popen.__init__), the marker the FR-007 stub tests for ("the resolver call is on the stack")specify_cli.status.aggregate (MissionStatus), specify_cli.coordination.surface_resolver (the exception classes)
_mission_status_project.pybuild_project(repo_root, memo, *, source=...) -> ProjectOutcome; project_version, schema_version_of, health_of, read_head, current_branch_of, last_activity_of; the four v1 fallbacks on the raw outcomethe memo, ProjectMetadata, get_project_schema_version, the schema constants, _mission_status_payloads (load_source only for the equality control in the tests, never in the builder)
_mission_status_drift.pyscan_drift(repo_root, memo, *, mission_id=None, clock, listing=...) -> DriftOutcome(status, body, fallbacks, legacy_manifests); resolve_scan_dir (the strict sibling); completion_of; kind1, kind2, kind3; classify_manifest; fixed_summary; sort_and_cap; the file-system seamthe memo, materialize_snapshot, materialize_to_json, read_lanes_json, LanesManifest, load_meta_fail_closed, derive_mission_lifecycle, code_lane_branch_name, now_utc, leak_scan.malformed_artifact_path (by file path through the loader)
_mission_status_ops.pylist_ops(repo_root, *, profile, page_size, cursor, fs) -> OpsOutcome(status, body, skipped_ids); candidates_of, read_op, closure_of, classify_evidence, redact_evidence, keyset_cursor; the validator of the open/closed invariantsparse_op_event, LegacyRecordError, read_op_closures, closed_invocation_ids, normalise_ref (classification input), redact_emails, SECRET_PATTERNS, the v1 projector (page info)
_mission_status_oracles.pyindependent oracles: oracle_kind12, oracle_kind3, oracle_fallbacks, oracle_ops, oracle_evidence; the UnicodeDecodeError wrapper of the classifierthe memo (raw outcome only), classify_status_json, is_mission_completed, materialize_snapshot, derive_mission_lifecycle, parse_op_event; never the project, drift or ops reader

Entry points return an outcome (a status and a body, or a typed refusal), never raise for a refusal. Public readers only; the one private name in the test tree is _reset_remote_branch_lookup_cache (the offline fixture, FR-026). The reader's own code never calls materialize, InvocationWriter or append_to_index; the tests replace each by a function that raises.

C. Mutation catalogue (the proof kind M of the plan's test strategy)

A mutation is a context manager in the test module that replaces one reader function or branch by its defective twin; the rows named in the right column must go red, else the test fails with the mutation was not killed: <name>. Each reader work package ships the whole catalogue of its reader.

ReaderMutation (name)Rows that must go red
projectwrap-cli-version (call get_project_version instead of the metadata reader)AC-PROJECT 1, 2
projectunguarded-load (call ProjectMetadata.load without the shape pre-check and without catching AttributeError or TypeError; plan D-P4)AC-PROJECT 1
projectequality-health (healthy only when the schema equals the minimum)AC-PROJECT 4
projectinvent-branch (null on unborn, or a subprocess for HEAD)AC-PROJECT 5, 7
projectstring-max-activity (maximum by text)AC-PROJECT 6
projectno-fallback-outside-rootAC-PROJECT 7 (equality with v1)
driftvariant-confusion, no-generation-gate (replay at the current generation)AC-DRIFT 1, 20
driftread-lanes-before-completionAC-DRIFT 10
driftexpect-both-branches, expect-mission-branch-only, remote-ref-countsAC-DRIFT 7, 8
driftper-branch-subprocess, list-without-expected-laneAC-DRIFT 13
driftfallback-on-any-exception (the v1 helper's catch list)AC-DRIFT 14, 17, 18
driftcompletion-from-read-directoryAC-DRIFT 19
driftforward-source-text (a summary or a branch name from the source)AC-DRIFT 24
driftunsorted, cap-off-by-oneAC-DRIFT 23
driftcall-materializeAC-DRIFT 27
opsascending-order, index-profile-filtersAC-OPS 1, 5
opscli-closure-gap (read only the Op's own file)AC-OPS 6
opsdrop-skipped-record-from-count, count-follows-filterAC-OPS 3, 4
opsredact-before-credential-check, mask-credential, skip-url-userinfoAC-OPS 10, 11
opseager-500-on-legacy, empty-on-oserrorAC-OPS 8, 9
opsquadratic-redaction (the pre-fix pattern)AC-OPS 14
opscache-the-result (the entry point memoises its result across calls; plan D-P16)AC-OPS 14, AC-CROSS 4 (the 30 s listing case in the ops module; the real 5 s listing case in the reality module, where the same mutation is applied as a context manager; plan-round ruling 9)

D. Test map (three new modules; the existing modules edited)

ConcernHomeRun byMarkers
Project builder, memo, v1 equality, description checks, metadata and HEAD matricestests/contract/test_mission_status_project.py (new, IC-03)tests (contract tools)contract, corpus, git_repo (not fast); module-level pytestmark
Drift reader: kinds 1 to 3, read directory, fallback, strict sibling, offline row, 500 matrix, order and cap, summaries, no dead valuetests/contract/test_mission_status_drift.py (new, IC-07)tests (contract tools)contract, corpus, git_repo; module-level pytestmark
Ops reader: order, cursor, candidate set, closure, skipped, evidence, credentials, timed cases, no dead valuetests/contract/test_mission_status_ops.py (new, IC-08)tests (contract tools)contract, corpus; module-level pytestmark
Contract-level proofs: frozen-copy pair, path keys and tags, CHANGELOG sections and qualified names, scope data, dependency checktests/contract/test_mission_status_contract_1_1.py (edited, IC-01, IC-03, IC-06)tests (contract tools)unchanged (contract, fast, corpus)
Examples, required cases, path keystests/contract/test_mission_status_examples.py (edited, IC-03 to IC-05)tests (contract tools)unchanged
Tool extensiontest_fixture_builder.py, test_leak_scan.py, test_run_negative_cases.py, test_enum_pin_check.py (edited)tests (contract tools)unchanged
The ratchet edits of v1 teststest_mission_status_payloads.py (the five-property derive_project check), test_mission_status_reality.py (the Project build) (edited, IC-03)tests (corpus-blocking)unchanged
Corpus reality check: oracles, named lists, floors, offline skip (resolver-dependent assertions only), fingerprints; every corpus-sized case: AC-DRIFT 19's completion equality and AC-CROSS 4's real-directory, Project-build and scan bounds, each with a discovered-count guard (plan-round ruling 4)tests/contract/test_mission_status_reality.py (edited, IC-09)tests (corpus-blocking)unchanged

The three new modules above are fixture-built only: they read no corpus directory (the tool job is documented as reading none) and carry their own controls. Their pytestmark is one single-line list holding pytest.mark.corpus (plan D-P5), the form _CORPUS_MARK_APPLICATION_RE of the registry gate matches.

Registrations (outside the spec's file set, plan PD-2): each new module needs 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, written in the commit that creates the module, in sorted position. The modules carry no fast marker: the nightly fast or unit selection runs on a checkout without tags and expects sub-second pure tests (R-10), which these are not. A module without a module-level pytestmark is a defect even when its tests carry a marker each (a corpus reader never marked is silently never run by the corpus lanes).

E. Fixtures: the families of FR-026 and where each is built

Fixture repositories use real git where git matters (the resolver, the branch listing, HEAD states) and write_fixture_mission otherwise (it writes meta.json, status.events.jsonl from rows, lanes.json from lanes, and any other file through files, so status.json and invalid-UTF-8 bytes are written through files).

FamilyBuilt inNotes
Metadata shapes of FR-002; schema versions around the range including a float and a boolean; every HEAD state; a strict host-path branch name; activity at different offsetsproject modulelinked worktree through git worktree add in a scratch repository
One Mission per drift kind plus a clean control; the status.json, meta.json and lanes.json shapes of AD-10, each on a completed and on a non-completed Mission; generation pair; invalid UTF-8 in each of the four filesdrift moduleeach plant has a same-fixture control
The coordination pairs (branch present, branch absent, mission_branch absent with the coordination branch present; primary copy stale beside coordination copy stale; merged_at in the own copy only beside the coordination copy only)drift modulereal git; the memo's two-repository test lives in the project module
The fallback and every other resolver outcome; the offline row (a remote whose URL is a non-existent local path; the probe cache reset between control and plant) ; one fixture per resolver arm the derivation does not mirror (a reopened Mission, a stored topology that does not use the coordination surface, a branch present only as a remote-tracking ref, one present only on a reachable remote)drift modulethe patched-resolver fixtures raise the other three exceptions and the outside-root case
1001 and exactly 1000 findingsdrift moduleshuffled creation order
Every kitty-ops/ shape of FR-017 to FR-020: absent; with and without index; own and spine closures; legacy lines; unreadable files beside a readable one; a vanished file; an index entry naming no file; an index line whose profile differs from its file's; invalid UTF-8 in an Op file and in the spine; an unreadable spine; equal startedAt; mission_id and wp_id present; each modeOfWorkops moduleunreadable files through a fault-injecting seam, never a permission change
Every evidence class and credential shape; three 256 KiB shapes; a 10,000-file directoryops moduleplanted values assembled at run time from fragments

F. Floors in code and pinned fixtures

The frozen pre-slice tree tests/contract/fixtures/mission_status_pre_slice/ mirrors the contract layout (schemas/Project.yaml, examples/Project.example.yaml, paths/project.yaml), so no two names in one directory differ only by letter case. FLOORS gains (IC-09, where the reality check consumes them, and never re-pinned): drift_missions 540, ops_served 440, ops_spine_closed 5, evidence_none 330, evidence_absolute 28, evidence_text 50, evidence_relative 25, kind1_findings 39, kind2_findings 27, completion_non_coord 490 (the guard of AC-DRIFT 19: Missions declaring no coordination branch compared). The existing floors stay. No kind-3 floor and no fallback floor exist. A new fixture tests/contract/fixtures/mission_status_health_drift_ops_expected.json lists the seven Op ids with a legacy completion and no spine record and the one legacy-shaped manifest Mission by name; each named entry must still be present and behave as stated (a subset check; an unnamed new anomaly is printed, never a failure).

G. 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>. The offline skip reads skipped: no configured remote is reachable (git ls-remote --heads failed); resolver-dependent assertions only; offline runs are not evidence for SC-002 or SC-007 (the Ops walk, skippedCount equality, the Ops floors, the AC-DRIFT 19 completion equality, the real kitty-ops/ listing bound and the fingerprints run and do not carry it; the one-Project-build, one-scan and combined timing cases do carry it). A timing failure states the minimum over the repeats, the repeat count and the bound (min of 5 = 0.13 s > 0.1 s). A corpus-condition failure names the Mission (declares a coordination branch present only on a remote: <name>).