Summary
Adding the check-response-examples gate (#657 remediation) surfaced 611 properties across 93
operations that a 2xx response schema declares but the operation's example omits. None of them
fires in CATS today — each was checked against the 107,608-response corpus from run
20260731T200651Z and never observed — so they are recorded in
api-schema/response-example-baseline.json rather than blocking the build.
They are still debt, and they are two different bugs wearing the same shirt:
- The schema is wrong — it declares a shape the server does not return.
- The operation is simply unexercised — the property is real but no fuzzer has produced it yet,
so it is a latent finding waiting for coverage to shift. This is exactly how
PATCH /teams/{team_id} went from invisible to 54 findings in one run.
Only case 2 should be fixed by completing the example. Completing case 1 would document fields the
endpoint never emits, which is worse than the gap.
Confirmed instance of case 1: Project.team
Project.team is declared as a full Team:
"team": { "allOf": [{"$ref": "#/components/schemas/Team"}], "nullable": true, "readOnly": true }
The server returns only a summary:
"team": {"id": "6a6d2eb6-dd45-4f51-9799-53a4fc5ccb7d", "name": "CATS Test Team"}
team.created_at appears in 0 of 107,608 responses. This accounts for the largest baseline
entries (POST /projects 24, PATCH /projects/{project_id} 24, PUT /projects/{project_id} 23,
GET /projects/{project_id} 22).
The 2026-07-30 session already noticed this and deliberately documented team as the {id, name}
summary it actually is — the right call for docs, but it leaves the schema asserting something
untrue. A client generating types from the spec gets a Team and finds most of it undefined.
Fix: introduce a TeamSummary schema ({id, name}) and point Project.team at it. This changes
generated types in api/api.go, so it needs a deliberate decision rather than a drive-by edit.
Baseline top entries
| n |
operation |
| 39 |
GET /threat_models/{threat_model_id} |
| 24 |
POST /projects |
| 24 |
PATCH /projects/{project_id} |
| 23 |
PUT /projects/{project_id} |
| 23 |
GET /threat_models/{threat_model_id}/diagrams/{diagram_id} |
| 23 |
PUT /threat_models/{threat_model_id}/diagrams/{diagram_id} |
| 22 |
GET /projects/{project_id} |
| 16 |
POST /threat_models/{threat_model_id}/restore |
Known-unsatisfiable, do not try to fix
Two response shapes are untyped objects whose keys are runtime-dependent, so no example can
enumerate them. These are the same class as the residual documented in #657 and are not in the
baseline (they are not declared properties at all):
MinimalDiagramModel.metadata — additionalProperties: {type: string}, unbounded keys.
4 findings on GET /threat_models/{id}/diagrams/{id}/model.
RollbackResponse.restored_entity — {"type": "object"} holding whichever entity type was rolled
back. Its example documents a threat model; the observed response was a diagram
(alias, cells, metadata, name, type). 4 findings.
Enumerating every restorable entity's properties into one example would satisfy CATS but produce a
nonsense document. Left alone deliberately.
Acceptance Criteria
References
Summary
Adding the
check-response-examplesgate (#657 remediation) surfaced 611 properties across 93operations that a 2xx response schema declares but the operation's example omits. None of them
fires in CATS today — each was checked against the 107,608-response corpus from run
20260731T200651Zand never observed — so they are recorded inapi-schema/response-example-baseline.jsonrather than blocking the build.They are still debt, and they are two different bugs wearing the same shirt:
so it is a latent finding waiting for coverage to shift. This is exactly how
PATCH /teams/{team_id}went from invisible to 54 findings in one run.Only case 2 should be fixed by completing the example. Completing case 1 would document fields the
endpoint never emits, which is worse than the gap.
Confirmed instance of case 1:
Project.teamProject.teamis declared as a fullTeam:The server returns only a summary:
team.created_atappears in 0 of 107,608 responses. This accounts for the largest baselineentries (
POST /projects24,PATCH /projects/{project_id}24,PUT /projects/{project_id}23,GET /projects/{project_id}22).The 2026-07-30 session already noticed this and deliberately documented
teamas the{id, name}summary it actually is — the right call for docs, but it leaves the schema asserting something
untrue. A client generating types from the spec gets a
Teamand finds most of it undefined.Fix: introduce a
TeamSummaryschema ({id, name}) and pointProject.teamat it. This changesgenerated types in
api/api.go, so it needs a deliberate decision rather than a drive-by edit.Baseline top entries
GET /threat_models/{threat_model_id}POST /projectsPATCH /projects/{project_id}PUT /projects/{project_id}GET /threat_models/{threat_model_id}/diagrams/{diagram_id}PUT /threat_models/{threat_model_id}/diagrams/{diagram_id}GET /projects/{project_id}POST /threat_models/{threat_model_id}/restoreKnown-unsatisfiable, do not try to fix
Two response shapes are untyped objects whose keys are runtime-dependent, so no example can
enumerate them. These are the same class as the residual documented in #657 and are not in the
baseline (they are not declared properties at all):
MinimalDiagramModel.metadata—additionalProperties: {type: string}, unbounded keys.4 findings on
GET /threat_models/{id}/diagrams/{id}/model.RollbackResponse.restored_entity—{"type": "object"}holding whichever entity type was rolledback. Its example documents a threat model; the observed response was a diagram
(
alias,cells,metadata,name,type). 4 findings.Enumerating every restorable entity's properties into one example would satisfy CATS but produce a
nonsense document. Left alone deliberately.
Acceptance Criteria
TeamSummaryforProject.team, or widen the server to return a full teamRollbackResponse.restored_entityshould be a discriminated unionReferences
example, notschema#657 — why examples matter at all (CATS validates againstexample, notschema)scripts/check-response-examples.py,api-schema/response-example-baseline.json20260731T200650Z