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: keys specKittyEventsVersion, supportedSchemaMin, supportedSchemaMax, resolverCaseIds (array), classes (array of {tag, fixtures[], property, parity} with parity python-golden | java-only), and per fixture readDirectoryClass (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 root HEAD text file, or a GITDIR_POINTER text file with a gitdir-target/HEAD, installed as .git/HEAD or as the .git pointer file by one materialisation step that the generator, the honesty test and the Java fixture factory share: plan.md section 1) plus golden/.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 for coord-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 .gitattributes line, services/mission-status-api/src/test/resources/conformance/** -text (never a global rule), pins the tree so CRLF survives checkout.
  • manifest.json also records leakPatternsSha256 and, per pattern, probe strings with the Python verdicts, and headText per project-header fixture. leak-patterns.json is generated by the Python oracle from contracts/tools/leak_patterns.py and committed under services/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 (beside conformance/, not inside it): one test id per line; floor recorded in FR-013.
  • The nightly comparison writes a per-run report nightly-report.json as 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.codec of 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).