Conversation
Bring the vehicles client up to date with the upstream tango "lakehouse
cutover" (May 2026). Adds new top-level fields, a `metrics(*)` shape
expansion, the `/api/vehicles/{uuid}/orders/` endpoint, an `ordering`
query param, and DeprecationWarning emission for fields the API now
sends a `Deprecation: true` header for.
Cassettes re-recorded against current production. Defaults verified to
match production's shape contract (organization is a leaf dict, /orders/
rejects `uuid`, organization payload includes `organization_id`).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace references to the upstream "lakehouse" with neutral language
("computed metrics", "upstream sync") in CHANGELOG, API_REFERENCE,
SHAPES, source comments, and test docstrings. Internal storage details
(e.g. "denormalized lakehouse table") replaced with capability framing
("optimized for fast pagination").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
list_vehicles now exposes 21 explicit filter parameters (vehicle_type, type_of_idc, contract_type, set_aside, who_can_use, naics_code, psc_code, program_acronym, agency, organization_id, total_obligated_min/max, idv_count_min/max, order_count_min/max, fiscal_year, award_date_after/before, last_date_to_order_after/before) plus an ordering parameter, matching API v4.3.0. list_vehicle_awardees gains a search parameter for entity-aware full-text search across IDV and recipient fields. Schema additions catch the SDK up through API 4.2.x: new top-level Vehicle fields (is_synthetic_solicitation, program_acronym, idv_count, total_obligated, latest_award_date), a bundled metrics expansion (12 lakehouse rollups), an organization(*) expand on Vehicle/Forecast/Grant/ITDashboard/Protest returning the canonical 7-key office payload, and a vehicle(*) expand on Contract. VEHICLES_MINIMAL/COMPREHENSIVE defaults updated to surface the new fields. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tier 1+2 of the Stripe-CLI-style webhook DX migration from tango/tools/ webhook_lab into the SDK. - tango.webhooks: HMAC-SHA256 signing helpers (verify_signature, generate_signature, parse_signature_header). Pure stdlib; importable from a default install. - WebhookReceiver: stdlib-based local listener with optional forwarding, delivery history, and on_delivery callback. Usable as a context manager inside integration tests. - simulate.deliver: offline sign+POST helper for driving a receiver without provisioning a real subscription. - New tango[webhooks] extra installs click and a tango console script with `webhooks listen|trigger|simulate` subcommands. - Top-level re-exports: WebhookReceiver, Delivery, verify_signature, generate_signature, parse_signature_header. The script name `tango` is flagged in CHANGELOG as revisitable before release if it conflicts with sibling tooling (e.g. tango-scripts). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The default BaseHTTPRequestHandler error page is HTML, which makes the
receiver's rejection responses inconsistent with tango's own JSON-shaped
API and harder to inspect programmatically (e.g. via simulate's
response_body field).
Now responds with `{"ok": false, "error": "<code>"}` and Content-Type
application/json on 401 (invalid_signature) and 404 (not_found), and
`{"ok": true}` on 200 — explicit Content-Length on all paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The simulate output's `sent_bytes` (length only) and `response_body` (the receiver's pong) were both confusing for devs who really want to see the canonical Tango shape they're driving their handler with: - `sent_bytes` (int) → `sent_payload` (the parsed JSON dict) - `response_body` → `receiver_response` (clarifies whose body it is) Two new subcommands close the discovery loop for devs building against the Tango API: - `tango webhooks fetch-sample [--event-type X]` — print the canonical sample payload Tango emits (read-only, no POST). Wraps the SDK's `get_webhook_sample_payload`. - `tango webhooks list-event-types` — list every event type Tango supports with descriptions. Wraps `list_webhook_event_types`. Together: `list-event-types` → pick one → `fetch-sample` to see the shape → `simulate --event-type X` to drive the handler with that shape, all without leaving the shell. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A dev exploring "what would Tango send my handler" shouldn't have to stand up a listener first. Now: - `simulate --event-type X` (no --to) signs the canonical payload and prints the headers + body — no POST. Output includes the literal X-Tango-Signature value the handler would see. - `simulate --event-type X --to URL` keeps the previous behavior: signs, POSTs, prints the receiver's response. Output gained `delivered: bool` so callers can disambiguate the two modes from the JSON itself. Refactored simulate to expose a public `simulate.sign(payload, secret) -> SignedRequest`. Useful in pytest fixtures that want the wire bytes without spinning a process. `simulate.deliver` now goes through it. Top-level re-exports: `from tango.webhooks import sign, SignedRequest`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the dev-against-Tango workflow loop — a dev with TANGO_API_KEY can now go from zero to receiving real deliveries entirely from the shell. Previously they had to drop into Python to call create_webhook_endpoint / create_webhook_subscription. Added: - `tango webhooks endpoints list|get|create|delete` - `tango webhooks subscriptions list|get|create|delete` Notes: - `subscriptions create` accepts simple flags (--name, --event-type, --subject-type, --subject-id) and assembles them into the payload.records[] shape Tango expects. For multi-record subscriptions, use the SDK's create_webhook_subscription directly. - `delete` requires confirmation; pass --yes to skip. - update commands deferred — endpoints/subscriptions are usually delete-and-recreate during dev, and partial-update semantics are awkward to express via flags. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`test_cli_simulate_rejects_both_modes` was passing /dev/null as a placeholder file. Click validates --payload-file with exists=True before the command body runs, so on Windows CI the test bailed with "File '/dev/null' does not exist" before ever reaching the mutual- exclusion error it was checking for. Use tmp_path to get a real existing file. macOS/Linux already passed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- New `docs/WEBHOOKS.md` (~370 lines) — single comprehensive guide for developers building against the Tango API: install, concepts, a zero-to-receiving quickstart, full CLI reference (every subcommand with copy-pasteable examples), and programmatic patterns for `WebhookReceiver`, `simulate.sign`, and `simulate.deliver` in pytest fixtures and offline development. Includes a troubleshooting appendix for the common gotchas (signature drift, missing extra, one-endpoint-per-user, etc.). - `README.md` — new "Webhook Tooling" section under Advanced Features with a one-paragraph overview, the install one-liner, the seven CLI subcommands at a glance, and the bare-minimum receiver pattern. WEBHOOKS.md added to the Documentation index. - `docs/API_REFERENCE.md` — filled in `get_webhook_subscription` (previously missing); replaced the hand-rolled signature snippet with a pointer to `tango.webhooks.verify_signature`; added a new "Webhook tooling (`tango.webhooks`)" section that catalogs every importable from the new subpackage (signing helpers, WebhookReceiver, Delivery, sign, SignedRequest, simulate.deliver, CLI entry point) with constructor tables and short examples. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Public-audience material shouldn't compare the SDK to a third-party tool — describe what the CLI does without the analogy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refresh the three vehicle cassettes against the live API so they match the new default shape (adds is_synthetic_solicitation, program_acronym, idv_count, total_obligated). Make test_get_vehicle_supports_joiner_and_flat_lists robust to the new default ordering: list 20 vehicles with shape=uuid,opportunity(title) and pick one whose opportunity is populated, so the joiner/flat_lists behavior is meaningfully exercised. Skip if none have one. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Combined with PR #21 (already merged): - Took PR #22's organization(*) expansion design (nested_model='OrganizationOffice') on Vehicle, replacing PR #21's leaf-only definition. Production probe confirmed both leaf and expansion forms work; the expansion form is a strict superset. - Took PR #22's full filter/ordering parameter list on list_vehicles (superset of PR #21's ordering-only signature). - Kept PR #21's deprecation of agency_details/competition_details/opportunity. - VEHICLES_COMPREHENSIVE swaps bare 'organization' for 'organization(*)' to match the new expansion-aware design. - Took PR #21's refreshed cassettes (post-cutover, May 2026) over PR #22's earlier recordings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CHANGELOG conflict resolved by combining PR #23's webhook bullets into the existing v0.6.0 Added block, plus adding Notes and Documentation sections. No code conflicts — disjoint surfaces.
- Promote CHANGELOG [Unreleased] -> [0.6.0] - 2026-05-07. - Drop duplicate VEHICLE_METRICS_SCHEMA introduced by auto-merge between PR #21 and PR #22 (both branches added the same 12-field schema). - Drop duplicate VEHICLE_SCHEMA keys (idv_count, organization, metrics) caused by both branches adding the same fields in different positions. Version was already bumped to 0.6.0 by PR #21. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The auto-merge between PR #21 and PR #22 created a Frankenstein `test_get_vehicle_supports_joiner_and_flat_lists` that combined PR #22's opportunity-finding setup with PR #21's organization-based assertion — the resulting list_vehicles call (`shape=opportunity(title)`) didn't match either PR's cassette recording. Reverting to PR #21's version: `list_vehicles(limit=1)` plus an organization-flatten check, which matches the merged-in cassette. Co-Authored-By: Claude Opus 4.7 (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.
Summary
Single release PR consolidating three open feature PRs into v0.6.0:
metrics(*)expansion,list_vehicle_orders, deprecation warnings onagency_details/competition_details/opportunity, refreshed cassettes).list_vehicles,searchonlist_vehicle_awardees,organization(*)expansion acrossVehicle/Forecast/Grant/ITDashboardInvestment/Protest,vehicle(*)onContract.tango.webhookssubpackage (signing,WebhookReceiver,simulate) and thetango[webhooks]extra with thetangoconsole script (full webhook lifecycle: listen / simulate / endpoints / subscriptions / trigger / fetch-sample / list-event-types).Key merge decision
PR #21 modeled
organizationas a leaf-only dict because its author found production rejectingorganization(...)asInvalid shape(May 5). I re-probed production today (May 7) and confirmed both forms now work on every relevant endpoint (vehicles,forecasts,grants,itdashboard,protests, plusvehicle(*)oncontracts). PR #22'snested_model="OrganizationOffice"design is a strict superset (supports both leaf and sub-selectable forms), so the merge takes PR #22's design across the board andVEHICLES_COMPREHENSIVEupgrades fromorganizationtoorganization(*).PR #21's deprecations (
agency_details/competition_details/opportunity) are preserved.Test plan
uv run ruff format .— clean (after merge dedup)uv run ruff checkon touched files — clean (24 repo-wide errors are pre-existing intango/shapes/{parser,generator,factory}.pyon main)uv run mypyon touched files — clean (24 pre-existing errors in the sametango/shapes/files unchanged)uv run pytest -m "not production"— 372 passed, 2 skipped, 29 deselected (the 29 are production smoke tests)uv run pytest tests/integration/— all 122 integration tests pass against the merged cassettes (took PR v0.6.0 #21's post-cutover recordings; one test was repaired post-merge where auto-merge stitched together incompatible PR v0.6.0 #21+Vehicles 4.3.0 filters/ordering + 4.2.x schema catch-up #22 logic)orderingmakegov/docsRisks
tango.webhooks.*,Vehicle/VehicleMetricsexports, 21 newlist_vehiclesfilter params,organization(*)expansion). PerCLAUDE.mdthese are now contract — renames require deprecation aliases.tango— flagged in CHANGELOG Notes as revisitable if it conflicts withtango-scripts.Next steps (after merge)
v0.6.0→ CI publishes to PyPI.makegov/docs.MAKEGOV_BOT_SIGNATURE