Skip to content

Commit 3184966

Browse files
vdavezclaude
andcommitted
feat(conformance): vendor API contract; add staleness, type, and baseline checks
- vendor canonical contract at contracts/filter_shape_contract.json (schema_version 2, generated from tango staging; refresh with new scripts/refresh_contract.py — sibling checkout or gh fallback) - checker: staleness direction (SDK arg the API dropped = error), type conformance vs contract type metadata, known-gaps baseline (contracts/conformance_baseline.json), --suggest scaffold mode - CI: conformance runs unconditionally against the vendored contract (closes the silent skip when TANGO_API_REPO_ACCESS_TOKEN is absent); token now only powers a staleness notice vs tango HEAD - baseline 7 real coverage gaps found on first run: key (contracts/ idvs/otas/otidvs), cage (entities), id (forecasts), opportunity_id (opportunities) - docs: DEVELOPERS.md + scripts/README.md conformance sections; pr_review.py defaults to the vendored contract Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 4aa6c1a commit 3184966

10 files changed

Lines changed: 6561 additions & 81 deletions

File tree

.github/workflows/lint.yml

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ name: Linting
55
# - ruff format + ruff check are HARD gates (block the PR).
66
# - mypy is a HARD gate: the package type-checks cleanly under strict mypy.
77
# (The earlier ~28-error burn-down is complete.)
8-
# - The SDK filter/shape conformance check needs the canonical manifest from the
9-
# private makegov/tango repo, which requires a TANGO_API_REPO_ACCESS_TOKEN
10-
# secret the public CI does not have. The conformance job SKIPS cleanly when
11-
# the token is absent (rather than failing red) and becomes a real gate the
12-
# moment the secret is configured.
8+
# - The SDK filter/shape conformance check is a HARD gate: it runs against the
9+
# vendored contract at contracts/filter_shape_contract.json on every run (no
10+
# secrets needed, works on forks). A second, token-gated step compares the
11+
# vendored contract against the tango repo's HEAD and emits a staleness
12+
# notice (never a failure — tango HEAD may carry unreleased changes).
13+
# Refresh the vendored contract with scripts/refresh_contract.py.
1314
on:
1415
workflow_dispatch:
1516
push:
@@ -45,13 +46,29 @@ jobs:
4546
run: uv run mypy tango/
4647

4748
conformance:
48-
# Requires the canonical filter_shape manifest from the private makegov/tango
49-
# repo. When TANGO_API_REPO_ACCESS_TOKEN is not configured, every real step
50-
# is skipped and the job passes (rather than failing on an empty token).
51-
# Configure the secret to turn this into a hard gate automatically.
49+
# Hard gate against the vendored contract (contracts/filter_shape_contract.json).
50+
# Runs unconditionally — no secrets required, so forks and tokenless runs
51+
# get the full check instead of a silent skip.
5252
runs-on: ubuntu-latest
5353

5454
steps:
55+
- uses: actions/checkout@v6
56+
57+
- name: Install uv
58+
uses: astral-sh/setup-uv@v8.1.0
59+
with:
60+
version: "latest"
61+
62+
- name: Set up Python
63+
run: uv python install 3.12
64+
65+
- name: Install dependencies
66+
run: uv sync --all-extras
67+
68+
- name: Check SDK filter/shape conformance (vendored contract)
69+
run: uv run python scripts/check_filter_shape_conformance.py
70+
71+
# --- Staleness notice (best-effort, never fails the job) ---------------
5572
- name: Determine token availability
5673
id: gate
5774
env:
@@ -61,34 +78,22 @@ jobs:
6178
echo "ready=true" >> "$GITHUB_OUTPUT"
6279
else
6380
echo "ready=false" >> "$GITHUB_OUTPUT"
64-
echo "::notice::Skipping SDK conformance check — TANGO_API_REPO_ACCESS_TOKEN not configured."
81+
echo "::notice::Contract staleness check skipped — TANGO_API_REPO_ACCESS_TOKEN not configured."
6582
fi
6683
67-
- uses: actions/checkout@v6
68-
if: steps.gate.outputs.ready == 'true'
69-
70-
- name: Checkout tango API repo (manifest source)
84+
- name: Checkout tango API repo (contract source)
7185
if: steps.gate.outputs.ready == 'true'
7286
uses: actions/checkout@v6
7387
with:
7488
repository: makegov/tango
7589
path: tango-api
7690
token: ${{ secrets.TANGO_API_REPO_ACCESS_TOKEN }}
7791

78-
- name: Install uv
92+
- name: Compare vendored contract against tango HEAD
7993
if: steps.gate.outputs.ready == 'true'
80-
uses: astral-sh/setup-uv@v8.1.0
81-
with:
82-
version: "latest"
83-
84-
- name: Set up Python
85-
if: steps.gate.outputs.ready == 'true'
86-
run: uv python install 3.12
87-
88-
- name: Install dependencies
89-
if: steps.gate.outputs.ready == 'true'
90-
run: uv sync --all-extras
91-
92-
- name: Check SDK filter/shape conformance
93-
if: steps.gate.outputs.ready == 'true'
94-
run: uv run python scripts/check_filter_shape_conformance.py --manifest tango-api/contracts/filter_shape_contract.json
94+
run: |
95+
if ! diff -q contracts/filter_shape_contract.json tango-api/contracts/filter_shape_contract.json >/dev/null; then
96+
echo "::warning::Vendored contract differs from makegov/tango HEAD. Refresh with: uv run python scripts/refresh_contract.py"
97+
else
98+
echo "Vendored contract matches makegov/tango HEAD."
99+
fi

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
### Added
11+
- **Contract-first conformance system.** The canonical API filter/shape
12+
contract is now vendored at `contracts/filter_shape_contract.json` (refresh
13+
with the new `scripts/refresh_contract.py`), so the conformance check runs
14+
out of the box — locally, in CI, and on forks — with no tango checkout or
15+
access token. `scripts/check_filter_shape_conformance.py` gained three new
16+
checks on top of filter coverage: **staleness** (an SDK filter argument the
17+
API no longer accepts is an error — it would silently no-op), **types**
18+
(each argument's annotation is validated against the contract's
19+
`schema_version: 2` per-filter type metadata), and a **known-gaps baseline**
20+
(`contracts/conformance_baseline.json`) that downgrades accepted missing
21+
params from errors to warnings so backlog is tracked instead of silent. A
22+
new `--suggest` flag prints ready-to-paste typed parameter scaffolds for
23+
any missing filters. The first run against the current API surface found 7
24+
real coverage gaps, now baselined: `key` on contracts/IDVs/OTAs/OTIDVs,
25+
`cage` on entities, `id` on forecasts, and `opportunity_id` on
26+
opportunities.
1127
- `TangoValidationError` now exposes the API's structured validation details
1228
directly: `.issues` (the list of `{"path": ..., "reason": ...}` entries the
1329
server returns for shape errors) and `.available_fields` (the endpoint's
1430
valid field set, when included). Both were previously reachable only by
1531
digging through `.response_data`. ([#45](https://github.com/makegov/tango-python/issues/45))
1632

1733
### Changed
34+
- The CI conformance job (`lint.yml`) now runs unconditionally against the
35+
vendored contract instead of silently skipping when
36+
`TANGO_API_REPO_ACCESS_TOKEN` is absent; the token is only used for a
37+
best-effort staleness notice comparing the vendored contract to tango HEAD.
38+
`scripts/pr_review.py` likewise defaults its conformance step to the
39+
vendored contract (override with `TANGO_CONTRACT_MANIFEST`).
1840
- 400 error messages now name the rejected field(s) and reason when the API
1941
returns structured `issues` — e.g.
2042
`Invalid request parameters: Invalid shape: tradeoff_process (unknown_field)`
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"_comment": "Accepted SDK coverage gaps vs the API contract. Params listed here downgrade from error to warning in scripts/check_filter_shape_conformance.py. Each entry is tracked backlog: remove it in the same PR that adds the param to the SDK. Run the checker with --suggest for ready-to-paste typed parameter scaffolds.",
3+
"missing_filters": {
4+
"contracts": ["key"],
5+
"entities": ["cage"],
6+
"forecasts": ["id"],
7+
"idvs": ["key"],
8+
"opportunities": ["opportunity_id"],
9+
"otas": ["key"],
10+
"otidvs": ["key"]
11+
}
12+
}

0 commit comments

Comments
 (0)