Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
b474de0
Seed dev from ONEcampaign/dfo-spec-kit@5d381d8322288dc77411baa70c0dcf…
dfo-release[bot] Oct 13, 2026
2116cf2
docs: point install docs and changelog links at the DFO repo
jm-rivera Oct 15, 2026
45348fa
ci: run spec-kit CI on dev and main
jm-rivera Oct 15, 2026
bfd2ccf
Merge pull request #6: ci: run spec-kit CI on dev and main
jm-rivera Oct 15, 2026
1622945
chore: point schema ids at the public repository
jm-rivera Oct 15, 2026
cc46513
Merge pull request #8: point schema ids at the public repository
jm-rivera Oct 15, 2026
20cdb35
feat(codes): register the whole_total_available search warning
jm-rivera Oct 19, 2026
0a0c068
Merge remote-tracking branch 'origin/dev' into feat/whole-total-avail…
jm-rivera Oct 19, 2026
ba3a9ec
docs(codes): name whole_total_available's whole_of values as member c…
jm-rivera Oct 20, 2026
d22db46
docs(codes): call whole_total_available's whole_of values party classes
jm-rivera Oct 20, 2026
2f57847
Merge pull request #9 from DevelopmentFinanceObservatory/feat/whole-t…
jm-rivera Oct 22, 2026
cbe2857
feat(registry): map the datasets file selector and versions refusals …
jm-rivera Oct 23, 2026
f85695a
feat(client-contract): add the DFO client contract distribution
jm-rivera Oct 20, 2026
94c83a8
feat(client-contract): record client scenarios against dev
jm-rivera Oct 20, 2026
c36e393
feat(client-contract): derive each scenario's expected outcome
jm-rivera Oct 20, 2026
f8b37b9
feat(client-contract): add scenarios for every answer and choice rule
jm-rivera Oct 20, 2026
9818197
feat(client-contract): name the codes a client raises itself
jm-rivera Oct 20, 2026
0344f8f
feat(client-contract): add fetch, long-build and stored-version scena…
jm-rivera Oct 20, 2026
a2d1a8c
feat(client-contract): pin how entities become a build's scope
jm-rivera Oct 21, 2026
4dd8a12
feat(client-contract): pin which groups a built identity keeps
jm-rivera Oct 21, 2026
6148139
feat(client-contract): record the contract against the composed server
jm-rivera Oct 21, 2026
8b17e17
fix(client-contract): check what each replayed exchange carries
jm-rivera Oct 21, 2026
3b69691
feat(client-contract): pin retries, downloads, warnings and refusal s…
jm-rivera Oct 21, 2026
d2ca886
fix(client-contract): check only what a client reads, and pin the ref…
jm-rivera Oct 21, 2026
142192a
feat(client-contract): pin each error's trace id
jm-rivera Oct 21, 2026
9d19483
fix(client-contract): record trace ids, replay under a second host, a…
jm-rivera Oct 21, 2026
daea49c
fix(client-contract): read malformed members through typed accessors,…
jm-rivera Oct 21, 2026
677e523
fix(client-contract): read entity bindings through the typed accessor…
jm-rivera Oct 21, 2026
f2c102b
docs(client-contract): tighten the contract README, rules and tools p…
jm-rivera Oct 21, 2026
8ed610a
docs(contract): clarify conformance, capabilities and timeout scope
jm-rivera Oct 22, 2026
c1a9966
docs(contract): scope concurrent cache write guarantees
jm-rivera Oct 22, 2026
94ed5a2
fix(contract): close the review's body-file, ref, echo, choice and re…
jm-rivera Oct 23, 2026
091bebe
fix(contract): describe choose-candidate-by-label without a fetch
jm-rivera Oct 23, 2026
2f51e04
fix(contract): echo only the assumptions and operations a client reads
jm-rivera Oct 23, 2026
4524699
fix(contract): read a whole-valued target_year as an integer
jm-rivera Oct 23, 2026
f479c21
fix(contract): check a ref's schema_version where it is written
jm-rivera Oct 23, 2026
7ab92c3
fix(contract): read a whole-valued number as an integer and refuse a …
jm-rivera Oct 23, 2026
95a15cf
Merge pull request #11 from DevelopmentFinanceObservatory/feat/client…
jm-rivera Oct 23, 2026
4ef22b7
feat(client-contract): read the newest plain build and record against…
jm-rivera Oct 23, 2026
d8a8271
feat(client-contract): add client codes for unsafe keys, headers, bas…
jm-rivera Oct 23, 2026
ca98b11
feat(client-contract): admit without_party_totals in a ref's artifact…
jm-rivera Oct 23, 2026
ec9fb5d
feat(client-contract): add the ref_digest_changed client warning (#18)
jm-rivera Oct 23, 2026
ba976a2
feat(client-contract): add the timed_out no_data reason (#19)
jm-rivera Oct 24, 2026
0d19172
feat(client-contract): read proxy refusals as problem documents and r…
jm-rivera Oct 24, 2026
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
12 changes: 9 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ name: CI
# A draft pull request runs no jobs. `ready_for_review` is in the types so that
# marking it ready starts the run: without it the skipped jobs would stand as
# passing required checks on the commit and nothing would have tested it. A push
# to main still runs, as the only test of the merged tree.
# to dev or main still runs, as the only test of the merged tree.
on:
push:
branches: [main]
branches: [dev, main]
pull_request:
branches: [main]
branches: [dev, main]
types: [opened, synchronize, reopened, ready_for_review]

jobs:
Expand All @@ -29,6 +29,8 @@ jobs:
module: dfo_provenance_docs
- package: python/descriptions
module: dfo_descriptions
- package: python/client-contract
module: dfo_client_contract
defaults:
run:
working-directory: ${{ matrix.package }}
Expand Down Expand Up @@ -74,6 +76,7 @@ jobs:
python/dfo-spec-kit-components,
python/provenance-docs,
python/descriptions,
python/client-contract,
]
python-version: ["3.12", "3.13"]
defaults:
Expand Down Expand Up @@ -141,3 +144,6 @@ jobs:

- name: Validate step and description vectors
run: uv run tools/validate_descriptions_vectors.py

- name: Validate the client contract
run: uv run tools/validate_client_contract.py
45 changes: 0 additions & 45 deletions .github/workflows/sync-guard.yml

This file was deleted.

31 changes: 17 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# dfo-spec-kit

The executable form of the platform's shared contracts. It exists so the consumers of these contracts, the Search API in `dfo-apis`, the harness and Analysis MCP in `dfo-ai`, the website, and the docs, share one source of truth instead of hand-maintained mirrors. This repo is not the spec registry nor the op-spec schema.
The executable form of the platform's shared contracts. It exists so the consumers of these contracts, the Search API in `apis`, the harness and Analysis MCP in `ai`, the website, and the docs, share one source of truth instead of hand-maintained mirrors. This repo is not the spec registry nor the op-spec schema.

The target design (repository shape, artifact families, versioning, governance, and delivery sequence) lives in the kit design, maintained outside this repo. This README only orients. That document is authoritative.

Expand All @@ -14,36 +14,37 @@ The **component-spec family** (the presentation catalogue, composition schema, o

## Status

Five Python distributions exist so far. `python/spec-kit/` (`dfo-spec-kit`, import `dfo_spec_kit`): `identity/` implements the spec1 identity scheme (role-slot projection, JCS canonicalisation, SHA-256 `spec_id` minting), conformance-tested against the vector suite. `units/` reads a unit ref into a typed value (`Money`, `UnpricedMoney`, `Percent`, `Count` or `Other`) from `registries/units.json`, conformance-tested against `vectors/units/`. Money whose ref leaves prices unstated cannot be compared until `complete_prices` applies the StatVar's measurement qualifier, and constant-price money with no base year cannot be compared at all. `models/` has no code yet; model extraction comes next. `python/frame-grammar/` (`dfo-spec-kit-frame-grammar`, import `dfo_frame_grammar`): the frame grammar's executable form -- canonical frame bytes and `content_version` minting for a materialised observation table -- conformance-tested against its own vector suite, and depending on `dfo-spec-kit` for the identity canonicalisation primitives it reuses. `python/dfo-spec-kit-components/` (`dfo-spec-kit-components`, import `dfo_spec_kit_components`): the presentation contract bundle as package data, holding the envelope schema, the per-member component schemas, the write-side intent grammar, and their conformance fixtures. Generated Pydantic models for the catalogue land in this same package once the catalogue freezes. `python/provenance-docs/` (`dfo-spec-kit-provenance-docs`, import `dfo_provenance_docs`): the provenance documentation grammar, which decodes the escaped Markdown documents a Provenance node carries and parses the transformations, changelog, methodology and notes templates, conformance-tested against its own vector suite. `python/descriptions/` (`dfo-spec-kit-descriptions`, import `dfo_descriptions`): the step records and descriptions contract, holding the step schema, the operation list, the description schema and a check of the structure rules a step list must meet beyond its schema, conformance-tested against its own vector suite.
Six Python distributions exist so far. `python/spec-kit/` (`dfo-spec-kit`, import `dfo_spec_kit`): `identity/` implements the spec1 identity scheme (role-slot projection, JCS canonicalisation, SHA-256 `spec_id` minting), conformance-tested against the vector suite. `units/` reads a unit ref into a typed value (`Money`, `UnpricedMoney`, `Percent`, `Count` or `Other`) from `registries/units.json`, conformance-tested against `vectors/units/`. Money whose ref leaves prices unstated cannot be compared until `complete_prices` applies the StatVar's measurement qualifier, and constant-price money with no base year cannot be compared at all. `models/` has no code yet; model extraction comes next. `python/frame-grammar/` (`dfo-spec-kit-frame-grammar`, import `dfo_frame_grammar`): the frame grammar's executable form -- canonical frame bytes and `content_version` minting for a materialised observation table -- conformance-tested against its own vector suite, and depending on `dfo-spec-kit` for the identity canonicalisation primitives it reuses. `python/dfo-spec-kit-components/` (`dfo-spec-kit-components`, import `dfo_spec_kit_components`): the presentation contract bundle as package data, holding the envelope schema, the per-member component schemas, the write-side intent grammar, and their conformance fixtures. Generated Pydantic models for the catalogue land in this same package once the catalogue freezes. `python/provenance-docs/` (`dfo-spec-kit-provenance-docs`, import `dfo_provenance_docs`): the provenance documentation grammar, which decodes the escaped Markdown documents a Provenance node carries and parses the transformations, changelog, methodology and notes templates, conformance-tested against its own vector suite. `python/descriptions/` (`dfo-spec-kit-descriptions`, import `dfo_descriptions`): the step records and descriptions contract, holding the step schema, the operation list, the description schema and a check of the structure rules a step list must meet beyond its schema, conformance-tested against its own vector suite. `python/client-contract/` (`dfo-spec-kit-client-contract`, import `dfo_client_contract`): the contract the DFO Python and TypeScript clients are built to, as package data: the served OpenAPI document of the composed dfo-apis app, the response members the clients act on, the caller's next step for each refusal code, the code of each error a client raises itself, the scenario and `DatasetRef` formats with their records, and the client rules with their vectors. dfo-apis CI checks its app against the same files.

The Python distributions live at `python/spec-kit/`, `python/frame-grammar/`, `python/dfo-spec-kit-components/`, `python/provenance-docs/`, and `python/descriptions/`, each its own installable unit with its own version and dependency surface. A TypeScript package (`ts/`) opens only when the component catalogue freeze lands.
The Python distributions live at `python/spec-kit/`, `python/frame-grammar/`, `python/dfo-spec-kit-components/`, `python/provenance-docs/`, `python/descriptions/`, and `python/client-contract/`, each its own installable unit with its own version and dependency surface. A TypeScript package (`ts/`) opens only when the component catalogue freeze lands.

`dfo-spec-kit-frame-grammar` depends on `dfo-spec-kit` as an intra-repo path source rather than a git dependency, so the two packages develop together in this repo: a change to identity canonicalisation and a change here that depends on it land in the same commit. `dfo-spec-kit-components` depends on `dfo-spec-kit-descriptions` as an intra-repo path source, because the envelope's `description` references `description.schema.json` by its `$id`. A consumer pins both distributions at one revision. Its catalogue bundle is static data plus one accessor function, `bundle_dir()`. `dfo-spec-kit-provenance-docs` also stands alone, depending on `markdown-it-py` only. `dfo-spec-kit-descriptions` depends on `rfc8785` only.
`dfo-spec-kit-frame-grammar` depends on `dfo-spec-kit` as an intra-repo path source rather than a git dependency, so the two packages develop together in this repo: a change to identity canonicalisation and a change here that depends on it land in the same commit. `dfo-spec-kit-components` depends on `dfo-spec-kit-descriptions` as an intra-repo path source, because the envelope's `description` references `description.schema.json` by its `$id`. A consumer pins both distributions at one revision. Its catalogue bundle is static data plus one accessor function, `bundle_dir()`. `dfo-spec-kit-provenance-docs` also stands alone, depending on `markdown-it-py` only. `dfo-spec-kit-descriptions` depends on `rfc8785` only. `dfo-spec-kit-client-contract` depends on the standard library only.

Publishing is undecided: no registry workflow exists for any package. Until that's resolved, install a Python package as a git dependency pinned to one commit, e.g.

```bash
uv add "git+https://github.com/ONEcampaign/dfo-spec-kit@<rev>#subdirectory=python/spec-kit"
uv add "git+https://github.com/ONEcampaign/dfo-spec-kit@<rev>#subdirectory=python/frame-grammar"
uv add "git+https://github.com/ONEcampaign/dfo-spec-kit@<rev>#subdirectory=python/dfo-spec-kit-components"
uv add "git+https://github.com/ONEcampaign/dfo-spec-kit@<rev>#subdirectory=python/provenance-docs"
uv add "git+https://github.com/ONEcampaign/dfo-spec-kit@<rev>#subdirectory=python/descriptions"
uv add "git+https://github.com/DevelopmentFinanceObservatory/spec-kit@<rev>#subdirectory=python/spec-kit"
uv add "git+https://github.com/DevelopmentFinanceObservatory/spec-kit@<rev>#subdirectory=python/frame-grammar"
uv add "git+https://github.com/DevelopmentFinanceObservatory/spec-kit@<rev>#subdirectory=python/dfo-spec-kit-components"
uv add "git+https://github.com/DevelopmentFinanceObservatory/spec-kit@<rev>#subdirectory=python/provenance-docs"
uv add "git+https://github.com/DevelopmentFinanceObservatory/spec-kit@<rev>#subdirectory=python/descriptions"
uv add "git+https://github.com/DevelopmentFinanceObservatory/spec-kit@<rev>#subdirectory=python/client-contract"
```

A package's metadata names its intra-repo dependency by distribution name alone, and no index serves these distributions. uv reads the path source in `[tool.uv.sources]` and resolves the dependency from the same checkout, so `uv add` of `frame-grammar` or `dfo-spec-kit-components` alone installs `dfo-spec-kit` or `dfo-spec-kit-descriptions` at that commit. Every other installer reads the bare name and fails, so name both distributions at the same commit:

```bash
pip install \
"dfo-spec-kit-components @ git+https://github.com/ONEcampaign/dfo-spec-kit@<rev>#subdirectory=python/dfo-spec-kit-components" \
"dfo-spec-kit-descriptions @ git+https://github.com/ONEcampaign/dfo-spec-kit@<rev>#subdirectory=python/descriptions"
"dfo-spec-kit-components @ git+https://github.com/DevelopmentFinanceObservatory/spec-kit@<rev>#subdirectory=python/dfo-spec-kit-components" \
"dfo-spec-kit-descriptions @ git+https://github.com/DevelopmentFinanceObservatory/spec-kit@<rev>#subdirectory=python/descriptions"
```

A uv project pins each kit distribution it imports in its own `[tool.uv.sources]`, every entry at the same `rev`, as `dfo-apis` pins `dfo-spec-kit-components` and `dfo-spec-kit-frame-grammar`:
A uv project pins each kit distribution it imports in its own `[tool.uv.sources]`, every entry at the same `rev`, as `apis` pins `dfo-spec-kit-components` and `dfo-spec-kit-frame-grammar`:

```toml
[tool.uv.sources]
dfo-spec-kit-components = { git = "https://github.com/ONEcampaign/dfo-spec-kit.git", rev = "<rev>", subdirectory = "python/dfo-spec-kit-components" }
dfo-spec-kit-descriptions = { git = "https://github.com/ONEcampaign/dfo-spec-kit.git", rev = "<rev>", subdirectory = "python/descriptions" }
dfo-spec-kit-components = { git = "https://github.com/DevelopmentFinanceObservatory/spec-kit.git", rev = "<rev>", subdirectory = "python/dfo-spec-kit-components" }
dfo-spec-kit-descriptions = { git = "https://github.com/DevelopmentFinanceObservatory/spec-kit.git", rev = "<rev>", subdirectory = "python/descriptions" }
```

## Layout
Expand All @@ -68,6 +69,8 @@ dfo-spec-kit/
│ # the provenance documentation grammar and its vector suite as package data
├── python/descriptions/ # the dfo-spec-kit-descriptions package (import dfo_descriptions),
│ # the step and description schemas, the operation list and their vector suite as package data
├── python/client-contract/ # the dfo-spec-kit-client-contract package (import dfo_client_contract),
│ # the served OpenAPI document, strict fields, caller actions, scenarios, ref format and client rules as package data
├── policies/ # compatibility policy per artifact family
└── tools/ # codegen + drift-check scripts, including the registry and vector validators
```
121 changes: 121 additions & 0 deletions python/client-contract/.pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Pre-commit hooks for Python + data repos
# Install dev deps: uv sync --group dev
# Enable hooks: pre-commit install
# Run all files: pre-commit run --all-files
# Update hooks: pre-commit autoupdate
# Skip once: git commit --no-verify

repos:
# ---------------------------------------------------------------------------
# Ruff: fast linter + formatter
# ---------------------------------------------------------------------------
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.21
hooks:
- id: ruff-check
args: [--fix]
files: ^python/client-contract/
- id: ruff-format
files: ^python/client-contract/

# ---------------------------------------------------------------------------
# Core hygiene, JSON/YAML/TOML, security
# ---------------------------------------------------------------------------
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
# Whitespace & endings
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
- id: end-of-file-fixer
- id: mixed-line-ending
args: [--fix=lf]

# Conflicts & platform quirks
- id: check-case-conflict
- id: check-merge-conflict

# Python sanity
- id: debug-statements
- id: check-ast
- id: check-shebang-scripts-are-executable
- id: check-builtin-literals
- id: check-docstring-first

# Config/data formats
- id: check-json
- id: pretty-format-json
args: [--autofix, --indent=2, --no-sort-keys, --no-ensure-ascii]
exclude: |
(?x)^(
\.idea/.*|
\.vscode/.*
)$
- id: check-yaml
args: [--allow-multiple-documents]
- id: check-toml

# Basic secret hygiene
- id: detect-private-key

# Large files (1MB) with sensible excludes
- id: check-added-large-files
args: [--maxkb=5000]
exclude: |
(?x)^(
.*\.parquet|
.*\.feather|
uv\.lock
)$

# ---------------------------------------------------------------------------
# Type checking (uses settings from pyproject.toml)
# ---------------------------------------------------------------------------
- repo: local
hooks:
- id: ty
name: ty type check
language: system
entry: bash -c 'cd python/client-contract && uv run ty check'
types: [python]
files: ^python/client-contract/
pass_filenames: false

# ---------------------------------------------------------------------------
# Spelling (docs + code comments)
# ---------------------------------------------------------------------------
- repo: https://github.com/codespell-project/codespell
rev: v2.4.3
hooks:
- id: codespell
args: [--write-changes]
# The contract files are data: served bodies, keys and codes read as
# misspellings, and --write-changes would rewrite them.
exclude: |
(?x)^(
.*\.lock|
.*\.parquet|
.*\.feather|
.*/dfo_client_contract/.*\.json
)$

# ---------------------------------------------------------------------------
# GitHub Actions linting
# ---------------------------------------------------------------------------
- repo: https://github.com/rhysd/actionlint
rev: v1.7.12
hooks:
- id: actionlint

# ---------------------------------------------------------------------------
# Markdown formatting (GFM + front matter)
# ---------------------------------------------------------------------------
- repo: https://github.com/hukkin/mdformat
rev: 1.0.0
hooks:
- id: mdformat
additional_dependencies:
- mdformat-gfm>=1.0.0
- mdformat-frontmatter>=2.1.0
# Example: limit to docs if desired
# files: ^(README\.md|docs/|.*/README\.md)$
Loading
Loading