Contracts
service-contract-notes.md
Contract notes: how the service consumes the in-repo contract
No file under contracts/ is edited (AC-14d, C-012). This note records the consumption rules and the committed data formats of this mission. It contains no YAML fence on purpose (every fenced yaml block of kitty-specs//contracts/.md is collected by the contract-example round trip test).
Consumed operations
getProject (GET /api/v1/project), getMissions paged (GET /api/v1/missions), GET /api/v1/events (SSE, hand-written). Beside them the service serves the committed contract document itself, bundled at build time and unchanged, at GET /api/v1/openapi.yaml (operator decision E-11: contract-first, no springdoc; plan.md section 13); that route is not an operation of the contract. Generated Spring interfaces/models cover the first two only, from the split root contracts/mission-status/openapi.yaml, at the same commit.
Error statuses
Every non-2xx body is the shared Problem (contracts/_shared/responses/Problem.yaml, status 400-599, code optional). Fixed by the contract: 400 invalid_page_cursor; stream 400 (negative, misaligned, cursor_without_mission), 409 (out_of_range, content_mismatch), 404 unknown Mission; 500 for resolver errors. Not in the contract (decided by the plan): Host/Origin policy refusals made by the filter = 403 request_refused, one constant HostOriginRefusal.STATUS (plan.md section 8); a Host the Tomcat container cannot parse is answered 400 by the container before the filter (the closed 400 list of the spec's AC-9b; operator decision E-1, no custom container valve). A ProjectionError is a 500 Problem of the same shape as the whole-read rows, diagnostic code projection_refused. The 403 constant does not govern the other refusals: the statuses fixed by the contract above stay as listed. The whole-read 500 rows L3a/L3b/L4/L4b/L7/L11 carry no path and no line text.
Conformance data formats (committed under services/mission-status-api/src/test/resources/)
conformance/manifest.json: keysspecKittyEventsVersion,supportedSchemaMin,supportedSchemaMax,resolverCaseIds(array),classes(array of{tag, fixtures[], property, parity}with paritypython-golden|java-only), and per fixturereadDirectoryClass(own|coord). Exact set of classes: the table in FR-008.conformance/resolver-cases.json: array of{id, storedTopology, coordRootPresent, coordMissionDirPresent, lanesJson: "valid"|"corrupt"|"absent", expectedClass}.conformance/<class>/<fixture>/: static trees (kitty-specs/<slug>/{meta.json,status.events.jsonl},.kittify/.yaml; for a project-header fixture a rootHEADtext file, or aGITDIR_POINTERtext file with agitdir-target/HEAD, installed as.git/HEADor as the.gitpointer file by one materialisation step that the generator, the honesty test and the Java fixture factory share: plan.md section 1) plusgolden/.json(canonical serialisation of the whole response document). Symlink fixtures are created at test time, never committed. The committed trees carry no.git: the Python generator and honesty test copy each fixture into a temporary git repository at run time (fixed identity and dates, no network; a registered coordination worktree forcoord-class fixtures, an unregistered plain directory for a husk), because the Python oracle consults the git worktree registry; no git detail enters a golden byte (plan.md section 1). The Java service reads no git. Directory names must not differ only by case. One scoped.gitattributesline,services/mission-status-api/src/test/resources/conformance/** -text(never a global rule), pins the tree so CRLF survives checkout.manifest.jsonalso recordsleakPatternsSha256and, per pattern, probe strings with the Python verdicts, andheadTextper project-header fixture.leak-patterns.jsonis generated by the Python oracle fromcontracts/tools/leak_patterns.pyand committed underservices/mission-status-api/src/main/resources/(the one source of the path, email and strict/human patterns; plan.md section 13, Projection).portlevel-tests.txt(besideconformance/, not inside it): one test id per line; floor recorded in FR-013.- The nightly comparison writes a per-run report
nightly-report.jsonas a workflow artifact; it is never committed and never read as input, and it cannot turn a difference green (plan.md section 7). - Ledger lines are decoded once, by the codec sub-package
adapter.out.filesystem.codecof the one outbound adapter (plan.md section 13; escalation E-13 records that the outbound port therefore carries decoded lines while the conformance harness feeds raw ledger bytes), into typed lines the application consumes; the L3/L4 line classification and the Python-parity rules (duplicate keys, number forms, non-UTF-8) are therefore tested at that codec and through both sources.
Comparison rule
Exact equality of the canonical serialisation of the entire response document; the ignore-list is one empty named constant per language (asserted empty). Canonical form (plan.md section 13): keys sorted, compact separators , and :, UTF-8 without ASCII escaping. The golden writer produces it; the production mapper emits it byte for byte; ONE test-support class, CanonicalJson, does every comparison in both the JVM golden suite and nativeSmoke, and the comparison is on RAW bytes: the response body as received equals the golden file's bytes, and the native digests hash the raw bodies. The class parses and re-serialises only to build a readable diff after a byte mismatch, so no weakening of AC-15b/NFR-005 (raw byte equality) is applied (post-analyze ruling ANL-003).