v1.3.0: DIBBS/SBIR/exclusions support + reverse shape-coverage gate#51
Merged
Conversation
…ge gate
The conformance check only validated one direction — that the SDK's shape
constants reference *allowed* fields. Nothing checked the reverse, so the
hand-maintained explicit_schemas.py had silently fallen 200+ fields behind the
API: 209 leaf fields, 41 nested expand branches, and 3 resources (naics, psc,
mas_sins) the typed shape API couldn't request at all.
Close all of them with a generated overlay (tango/shapes/generated_overlay.py,
merged over the base by SchemaRegistry), with types resolved from live-API
sampling rather than guessed. Code-object expands (set_aside, award_type,
idv_type, …) — previously modeled inconsistently as str / bare dict — now
uniformly resolve to {code, description} via the shared CodeDescription schema.
Add a reverse-coverage gate (scripts/check_shape_coverage.py) that fails when
Tango's shape trees expose anything the SDK misses and it isn't baselined.
Offline against the vendored contract — no token, runs on forks — and wired
into the lint.yml conformance job. Regenerate the overlay with
scripts/generate_shape_overlay.py (from the vendored contract +
contracts/observed_shape_types.json, no API key); refresh the observations with
scripts/probe_shape_types.py (maintainer-run, needs a key).
Verified: 428 tests pass, mypy clean, ruff clean, conformance passes, coverage
gate at 0, and previously-uncapturable shapes now validate end-to-end while bad
fields are still rejected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The vendored API contract had gone stale by seven resources — it tracked 25 against Tango's current 32 — so both the conformance and shape-coverage checks were validating against out-of-date truth. They could not see DIBBS, exclusions, SBIR, or budget/accounts at all. Refreshing it surfaced those six unimplemented endpoint families plus 72 additional fields on existing resources. Add full SDK support for the six families Tango shipped in v4.16-v4.18: list_/get_ pairs for dibbs rfqs, rfps and awards, exclusions, and SBIR topics and solicitations, with all 85 filter params, shape schemas, ShapeConfig defaults, and new DibbsRfq/DibbsRfp/DibbsAward/Exclusion/SbirTopic/SbirSolicitation models. Documents the two API behaviors that bite: is_open / is_currently_excluded are derived at query time (filter with open/active instead), and DIBBS total_contract_price is the order total repeated per line item — never sum it. Fix the conformance checker's false stale-param reports. It resolved SDK args to API params only through an explicit api_param_mapping dict, so methods expressing the translation as tuple tables looked like they exposed ~55 params the API rejects. They do not — list_budget_accounts correctly sends apportioned__gte and account_title__icontains. The checker now understands both tuple forms across Assign, AnnAssign and inline for-iterables, with api_param_mapping still winning. That let budget/accounts be conformance-checked for the first time; its real missing lookup variants are baselined. Also allowlists budget/accounts `search`, which the endpoint honors but the contract omits (verified live). Verified: 450 tests pass, mypy clean, ruff clean, conformance passes with 0 errors, shape-coverage gate at 0, and all 12 new methods exercised against the live API (list + detail round-trip for each family). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs/WEBHOOKS.md listed only five of the eight webhook alert types, missing alerts.exclusion.match, alerts.dibbs_rfq.match and alerts.dibbs_rfp.match. No SDK change is needed for these — event types are served by the API and never hardcoded, so they already worked — but the hand-written list had gone stale the same way the vendored contract had. Also document two things that surprise people: DIBBS awards are not alertable (there is no alerts.dibbs_award.match), and nothing fires when a record merely lapses — an exclusion reaching its termination date, or an RFQ/RFP passing its close date, emits no event, because alerts fire on matching a saved search rather than on the passage of time. Add a production smoke test asserting every event type the live API reports appears in the doc, so the list cannot silently fall behind again. Verified it fails with a precise message when a type is removed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Minor bump: this release only adds surface (six new endpoint families, wider shape coverage, new dev tooling) and fixes drift. Nothing is removed or narrowed, and the shape overlay only widens what the typed shape API accepts, so existing callers are unaffected. Cut the accumulated [Unreleased] entries into [1.3.0], merging the duplicate ### Added blocks and ordering sections per Keep a Changelog. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Releases v1.3.0. Four related problems, all found by pulling one thread: "what about DIBBS/SBIR/exclusions?"
What
1. The vendored contract had gone stale by seven resources. It tracked 25 against Tango's current 32, so both the conformance check and the new coverage gate were validating against out-of-date truth — they could not see DIBBS, exclusions, SBIR, or
budget/accountsat all. Refreshed. That alone surfaced 72 additional fields on existing resources. Nested routes are also keyed with a slash now (budget/accounts), which had silently broken the oldbudget_accountsmapping; both keys are accepted.2. The SDK under-captured what the API returns. The conformance check only validated one direction — that shape constants reference allowed fields. Nothing checked that the SDK captures everything Tango exposes. It had fallen 200+ fields behind: 209 leaf fields, 41 nested expand branches, and 3 resources unreachable through the typed shape API.
3. Six endpoint families had no SDK support at all — no models, no methods.
4. The webhook docs listed 5 of 8 alert types. No SDK change was needed — event types are served by the API and never hardcoded, so the new types already worked — but the hand-written list had drifted the same way the contract had.
How
Shape coverage.
tango/shapes/generated_overlay.py(generated, merged over the curated base bySchemaRegistry) closes every gap, with types resolved from live-API sampling rather than guessed: observation → structural equivalence (vehicles.awardeesmirror IDVs,.ordersmirror contracts) → name heuristic. Code-object expands (set_aside,award_type,idv_type, …), previously modeled inconsistently asstr/ baredict, now uniformly resolve to{code, description}.Reverse-coverage gate.
scripts/check_shape_coverage.pyfails when Tango's shape trees expose anything the SDK misses that isn't baselined. Offline against the vendored contract — no token, runs on forks — and wired into thelint.ymlconformance job. Baseline is empty: coverage is real, not deferred. This gate is what caught the stale contract once it had current input.New endpoint families (Tango v4.16–v4.18), full
list_*+get_*pairs:dibbs/rfqs,dibbs/rfps,dibbs/awardsuuidexclusionsexclusion_keysbir/topics,sbir/solicitationstopic_id/solicitation_idPlus 6 models, shape schemas, and
ShapeConfigdefaults. Two API behaviors are documented in-code because they bite:is_open/is_currently_excludedare derived at query time (filter withopen/active, don't shape on them), and DIBBStotal_contract_priceis the order total repeated on every line item — never sum it across rows.Webhooks. Documented
alerts.exclusion.match,alerts.dibbs_rfq.match,alerts.dibbs_rfp.match, plus two things that surprise people: DIBBS awards are not alertable, and nothing fires when a record merely lapses (an exclusion hitting its termination date, or an RFQ/RFP passing its close date, emits no event — alerts fire on matching a saved search, not the passage of time). A production smoke test now asserts every live event type appears in the doc; verified it fails with a precise message when one is removed. Notedocs/here is the coloc source for the SDK pages on docs.makegov.com, so this propagates to the public site.Conformance checker fix. It resolved SDK args to API params only through an explicit
api_param_mappingdict, so methods expressing the translation as tuple tables —("account_title__icontains", account_title)andrange_filters = (("apportioned", apportioned, apportioned_gte, ...))— looked like they exposed ~55 params the API rejects. They do not:list_budget_accountscorrectly sendsapportioned__gte,fiscal_year__lte,account_title__icontains(verified empirically). The checker now understands both tuple forms acrossAssign,AnnAssign, and inlineforiterables, with explicitapi_param_mappingstill taking precedence. This letbudget/accountsbe conformance-checked for the first time; its genuinely missing lookup variants (agency_code__in,bureau_name__icontains, …) are baselined.Versioning
Minor (1.2.0 → 1.3.0): this only adds surface and fixes drift. Nothing is removed or narrowed, and the overlay only widens what the typed shape API accepts, so existing callers are unaffected.
Testing
mypyclean ·ruff check/formatcleanuv buildproduces1.3.0artifacts;pyproject.tomland__version__agreeNotes for review
generated_overlay.pyis a generated artifact — reviewscripts/generate_shape_overlay.pyfor the logic. Regenerable with no API key from the vendored contract +contracts/observed_shape_types.json; refresh the observations withscripts/probe_shape_types.py(maintainer-run, needs a key).budget/accountsacceptssearch(verified live: 21,170 results vs 0 for a nonsense term) but the contract omits it, because Tango's generator only surfaces view-handled search for filtersets declaringmin_length_params. Allowlisted locally with the verification noted; the real fix belongs in Tango's contract generator.🤖 Generated with Claude Code