Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions python/client-contract/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ All notable changes to the `dfo-spec-kit-client-contract` Python package will be
### Added

- Package scaffolding (uv, ruff, ty, pytest).
- `served/openapi.json`, the composed dfo-apis app's OpenAPI document, and `served/SOURCE.json`, naming dfo-apis commit `d80df9f5fd598b7345255470932d202ee5d49942`. The document admits `timed_out` as a no_data reason and the optional `entity_refs` on each answer part, a list of `EntityRefs` with one entry per restatement entity row, the graph nodes that row binds, and a stored manifest warning states its counts and the refs it names (`CountedRefsModel`, `CountedEntityYearsModel`, `EntityYearsModel`). `NoDataOption` and `SentenceToken` carry `entity_choice`, the index into `NoData.ambiguous_entities` of the group an entity_ambiguous option settles or a slot token asks, so a client groups a place menu by it when two wordings share one text.
- `served/openapi.json`, the composed dfo-apis app's OpenAPI document, and `served/SOURCE.json`, naming dfo-apis commit `ccb6617922c839ce3cd5615a3bbb65c70bfe1bde`. The document admits `timed_out` as a no_data reason and the optional `entity_refs` on each answer part, a list of `EntityRefs` with one entry per restatement entity row, the graph nodes that row binds, and a stored manifest warning states its counts and the refs it names (`CountedRefsModel`, `CountedEntityYearsModel`, `EntityYearsModel`). `NoDataOption` and `SentenceToken` carry `entity_choice`, the index into `NoData.ambiguous_entities` of the group an entity_ambiguous option settles or a slot token asks, so a client groups a place menu by it when two wordings share one text.
- `strict-fields.json` and its schema: 29 members that stay required and non-nullable, each one a client reads, and the answer kinds, part statuses and operation statuses the clients branch on.
- `actions.json` and its schema: the caller's next step for each of the 148 active refusal codes on `problem_details` or `no_data_reason`, with the gap kind of each no_data reason.
- The status fallback for a refusal whose code has no row: 400 and 422 to `InvalidRequestError`, 401 to `AuthenticationError`, 403 to `PermissionDeniedError`, 404 and 410 to `NotFoundError`, 408 to `DfoTimeoutError`, 429 to `RateLimitError`, and any other 4xx or 5xx to `InvalidRequestError` or `ServerError`.
- `scenario.schema.json`, the scenario record, and 126 scenarios: 34 recorded against the deployed dfo-apis dev app at `d80df9f5f`, and 92 synthetic, each derived from a recorded one by a one-line edit, with 21 binary bodies totalling 1,134,603 bytes and records and bodies together 13,695,441 bytes. `search-place-ambiguous-twice` pins two entity_ambiguous groups of one text, each option and slot token naming its group by `entity_choice`, and `choose-refused-label-in-two-groups` pins that a label both groups offer matches two options, so a choice there names its option by index. The scenarios cover the several-part choice entries and the assumptions each identity entry echoes, a picked reading whose assumption Search states in its answer, the place choice through a no_data part's options, the choice refusals, a part without variable_text, an unknown status without a restatement, a shared label, a one-part option whose data_query holds two parts, the separate and operations echo, an unsupported part's placeholder, the timed_out, unsupported and unknown gaps, an unknown answer kind, the `SearchWarning` rule, the stacked dataset's `part` column, the frame layout, a strict fetch's `DataWarning`, a spec fetch that rebinds one of two entity roles and keeps the other, one that drops a role bound to null, a spec whose destination is a named group fetched with a window or with its origin rebound, whose sentence lookup posts the groups of the roles the build left bound, a twice cut-off build, a cut-off build retried after its Retry-After, a retryable refusal whose Retry-After passes the 60-second cap, a build refused 504 then 503 and one refused 503 then 504, a refused build, a build refused 501 `capability_unavailable`, a digest mismatch, a signed URL refused on both rounds, a relative files-route Location, an artifact url on a foreign origin, on the storage host and absolute on the API's origin, a manifest definition without identity or schema_version, a Search spec without identity or resolved, nested specs no rule reads, an empty parts list, a Search body that is not an object, a codeless 503 problem document that is not retryable, a 401, an identity resubmit answering another spec, a part whose status is not a string, a Search spec whose definition names another spec_id, a Search response with no JSON body, an option with no data_query, empty entities and facets, a Sunset header issued once, `x-dfo-warning` messages and a Deprecation and Sunset pair, an unreadable versions page, a mint and a spec fetch whose build answers 202 and is polled to done, a resumed operation, a rate-limited poll, 503 polls retried within the budget an answered poll restores, a 202 that reads running, polls whose `retry_after_ms` changes from one to the next, a 404 poll that raises, a failed operation posted again, a failed operation with no error, and a ref whose stored file expired, read through the file selector, rebuilt at the same version, at a newer one, or refused with the newest version read from the versions route: the first version it lists with no `base_content_version`, the newest plain build, so a derived version listed before it is skipped.
- `scenario.schema.json`, the scenario record, and 126 scenarios: 34 recorded against the deployed dfo-apis dev app at `ccb661792`, and 92 synthetic, each derived from a recorded one by a one-line edit, with 21 binary bodies totalling 1,142,020 bytes and records and bodies together 14,160,817 bytes. `search-place-ambiguous-twice` pins two entity_ambiguous groups of one text, each option and slot token naming its group by `entity_choice`, and `choose-refused-label-in-two-groups` pins that a label both groups offer matches two options, so a choice there names its option by index. The scenarios cover the several-part choice entries and the assumptions each identity entry echoes, a picked reading whose assumption Search states in its answer, the place choice through a no_data part's options, the choice refusals, a part without variable_text, an unknown status without a restatement, a shared label, a one-part option whose data_query holds two parts, the separate and operations echo, an unsupported part's placeholder, the timed_out, unsupported and unknown gaps, an unknown answer kind, the `SearchWarning` rule, the stacked dataset's `part` column, the frame layout, a strict fetch's `DataWarning`, a spec fetch that rebinds one of two entity roles and keeps the other, one that drops a role bound to null, a spec whose destination is a named group fetched with a window or with its origin rebound, whose sentence lookup posts the groups of the roles the build left bound, a twice cut-off build, a cut-off build retried after its Retry-After, a retryable refusal whose Retry-After passes the 60-second cap, a build refused 504 then 503 and one refused 503 then 504, a refused build, a build refused 501 `capability_unavailable`, a digest mismatch, a signed URL refused on both rounds, a relative files-route Location, an artifact url on a foreign origin, on the storage host and absolute on the API's origin, a manifest definition without identity or schema_version, a Search spec without identity or resolved, nested specs no rule reads, an empty parts list, a Search body that is not an object, a codeless 503 problem document that is not retryable, a 401, an identity resubmit answering another spec, a part whose status is not a string, a Search spec whose definition names another spec_id, a Search response with no JSON body, an option with no data_query, empty entities and facets, a Sunset header issued once, `x-dfo-warning` messages and a Deprecation and Sunset pair, an unreadable versions page, a mint and a spec fetch whose build answers 202 and is polled to done, a resumed operation, a rate-limited poll, 503 polls retried within the budget an answered poll restores, a 202 that reads running, polls whose `retry_after_ms` changes from one to the next, a 404 poll that raises, a failed operation posted again, a failed operation with no error, and a ref whose stored file expired, read through the file selector, rebuilt at the same version, at a newer one, or refused with the newest version read from the versions route: the first version it lists with no `base_content_version`, the newest plain build, so a derived version listed before it is skipped.
- `client-codes.json` and its schema: the 23 codes a client sets on the errors it raises itself, each with its error class, retryable flag and condition, disjoint from `actions.json` and `registries/codes.json`, and the `client_codes()` and `client_codes_schema()` accessors. Every scenario error the client raises itself carries its code, and `refusal_without_code` names a refusal whose body carries no code.
- `proxy-codes.json` and its schema, the website proxy's `rate_limited` and `transient_unavailable` codes with their statuses, which the proxy sends in problem documents of the shape every refusal has, both retryable, with `Retry-After` on the 429, so a client reads one refusal shape, and `warning-codes.json` and its schema, the 7 codes a client sets on the warnings it issues itself, with the `proxy_codes()` and `warning_codes()` accessors.
- `ref_digest_changed` in `warning-codes.json`, a `DfoWarning` for a ref member whose `content_version` serves a file with another digest than the ref's, because the server rendered the same data again. The client returns the data, with no error under strict.
Expand Down
2 changes: 1 addition & 1 deletion python/client-contract/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ uv sync --directory ../dfo-apis --extra eval --extra api --extra engine --extra
uv run tools/capture_client_contract.py openapi --dfo-apis ../dfo-apis
```

Its `record` command runs every scenario against a base URL, dev's Cloud Run URL by default, checks each live answer still has the shape its scenario names, derives the synthetic scenarios from the fresh records with no request, and then runs the validator. A recorded scenario holds only exchanges the server made, and the run refuses one that a canned exchange answered, and an API response without the `x-dfo-trace-id` header the server sends on every response but a shared-cacheable one, marked `Cache-Control: public`, whose `cache-control` header the record then keeps. The validator refuses a recorded API response with neither. The record names the commit the server's responses name. It fails when the validator reports a problem or the binary bodies pass 5 MB. `--only` records the scenarios it names. `--offline` makes no request. Each recorded scenario's steps run again over its own exchanges and rewrite its `expect`, and each synthetic scenario is derived again from its parent, so a change to the outcome rules or to `client-codes.json` reaches the records without a recording:
Its `record` command runs every scenario against a base URL, dev's Cloud Run URL by default, which refuses anonymous callers, so a record against dev passes `--cloud-run-token` to send the gcloud account's Google ID token in `X-Serverless-Authorization` to the base URL's origin and to no other host. It checks each live answer still has the shape its scenario names, derives the synthetic scenarios from the fresh records with no request, and then runs the validator. A recorded scenario holds only exchanges the server made, and the run refuses one that a canned exchange answered, and an API response without the `x-dfo-trace-id` header the server sends on every response but a shared-cacheable one, marked `Cache-Control: public`, whose `cache-control` header the record then keeps. The validator refuses a recorded API response with neither. The record names the commit the server's responses name. It fails when the validator reports a problem or the binary bodies pass 5 MB. `--only` records the scenarios it names. `--offline` makes no request. Each recorded scenario's steps run again over its own exchanges and rewrite its `expect`, and each synthetic scenario is derived again from its parent, so a change to the outcome rules or to `client-codes.json` reaches the records without a recording:

```bash
uv run tools/capture_client_contract.py record --offline --only answer-declined choose-refused-no-choice
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
"members": [
{
"parts": [],
"sentence": "Gross disbursements to Kenya from 96 providers for official development assistance in 2022. Current US dollars · OECD DAC2a.",
"sentence": "Net disbursements to Kenya from 96 providers for official development assistance in 2022. Current US dollars · OECD DAC2a.",
"spec": {
"spec_id": "spec_73897fdfb0206fcf27c5898b353348fb",
"spec_id": "spec_e14699f9dcd167f0211081a726ae1031",
"definition": {
"schema_version": 1,
"spec_id": "spec_73897fdfb0206fcf27c5898b353348fb",
"spec_id": "spec_e14699f9dcd167f0211081a726ae1031",
"identity": {
"shape": {
"kind": "statvar_schema",
Expand Down Expand Up @@ -45,7 +45,7 @@
},
{
"binding": {
"ref": "Gross",
"ref": "Net",
"state": "value"
},
"property": "nettingBasis"
Expand All @@ -65,9 +65,9 @@
}
}
},
"content_version": "cv_2aa4cc2fd852cd9b84347c61d07d609a",
"content_version": "cv_574e68a18917407b0f5534cfc9ff001b",
"artifact": {
"digest": "sha256:05d0cbf59e8dddd7162104bf8e2edc406fc402a0b8d0d971452d01b07b0ee426",
"digest": "sha256:7ef1038eb25d978077bb4f5b9c54e940052ccd2aedd3284e6fb9739f1deca1f4",
"rendering": {
"format": "parquet",
"bundle": false
Expand Down
Loading
Loading