Skip to content

v0.6.0#24

Merged
vdavez merged 17 commits intomainfrom
release/v0.6.0
May 8, 2026
Merged

v0.6.0#24
vdavez merged 17 commits intomainfrom
release/v0.6.0

Conversation

@makegov-mark
Copy link
Copy Markdown
Contributor

@makegov-mark makegov-mark Bot commented May 7, 2026

Summary

Single release PR consolidating three open feature PRs into v0.6.0:

  • PR v0.6.0 #21 — vehicles upstream-cutover surface (new top-level fields, metrics(*) expansion, list_vehicle_orders, deprecation warnings on agency_details/competition_details/opportunity, refreshed cassettes).
  • PR Vehicles 4.3.0 filters/ordering + 4.2.x schema catch-up #22 — vehicles 4.3.0 filters/ordering on list_vehicles, search on list_vehicle_awardees, organization(*) expansion across Vehicle/Forecast/Grant/ITDashboardInvestment/Protest, vehicle(*) on Contract.
  • PR Add webhooks signing helpers, local receiver, and tango CLI #23tango.webhooks subpackage (signing, WebhookReceiver, simulate) and the tango[webhooks] extra with the tango console script (full webhook lifecycle: listen / simulate / endpoints / subscriptions / trigger / fetch-sample / list-event-types).

Key merge decision

PR #21 modeled organization as a leaf-only dict because its author found production rejecting organization(...) as Invalid 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, plus vehicle(*) on contracts). PR #22's nested_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 and VEHICLES_COMPREHENSIVE upgrades from organization to organization(*).

PR #21's deprecations (agency_details/competition_details/opportunity) are preserved.

Test plan

  • uv run ruff format . — clean (after merge dedup)
  • uv run ruff check on touched files — clean (24 repo-wide errors are pre-existing in tango/shapes/{parser,generator,factory}.py on main)
  • uv run mypy on touched files — clean (24 pre-existing errors in the same tango/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)
  • Refresh cassettes against live 4.3.0 API as a pre-tag step (prudent given the cassette-merge surgery)
  • Live vehicles smoke against the new filters/ordering
  • Companion docs PR in makegov/docs

Risks

  • New public API surface (tango.webhooks.*, Vehicle/VehicleMetrics exports, 21 new list_vehicles filter params, organization(*) expansion). Per CLAUDE.md these are now contract — renames require deprecation aliases.
  • Console script name tango — flagged in CHANGELOG Notes as revisitable if it conflicts with tango-scripts.
  • Cassette mix — took PR v0.6.0 #21's vehicle cassettes for the three shared tests; new cassettes from PR Vehicles 4.3.0 filters/ordering + 4.2.x schema catch-up #22's filter-probing test additions land here unchanged. A live cassette refresh before tagging will close that gap.

Next steps (after merge)

  1. Refresh cassettes + run live vehicle smoke against current prod.
  2. Tag v0.6.0 → CI publishes to PyPI.
  3. Close PRs v0.6.0 #21, Vehicles 4.3.0 filters/ordering + 4.2.x schema catch-up #22, Add webhooks signing helpers, local receiver, and tango CLI #23 with a pointer here.
  4. Companion docs PR in makegov/docs.

MAKEGOV_BOT_SIGNATURE

vdavez and others added 17 commits May 5, 2026 19:04
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>
@vdavez vdavez merged commit f9f4309 into main May 8, 2026
10 checks passed
@vdavez vdavez deleted the release/v0.6.0 branch May 8, 2026 14:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant