Skip to content

feat(sdk): trade intelligence in the OpenAPI spec and TypeScript SDK - #415

Merged
dodeja merged 30 commits into
mainfrom
feat/trade-intel-sdk
Oct 18, 2026
Merged

dodeja merged 30 commits into
mainfrom
feat/trade-intel-sdk

Conversation

@dodeja

@dodeja dodeja commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds Terminal49 trade intelligence to the public API reference, the TypeScript SDK, and the MCP server. The data is US import bill-of-lading records (US Customs manifests), January 2022 onward, refreshed daily, served at /v2/trade_intel/* with normal v2 Token auth and gated per account.

OpenAPI and docs (docs: commits)

  • Nine operations under a public Trade Intelligence tag, with TradeIntel* request and response schemas, reusable error responses (400, 401, 403 not enabled, 422, 429, 502/504), and synthetic examples.
  • The response schemas follow what the service actually returns (checked against apps/trade-intel on t49 staging): meta and search-window dates are first-of-month YYYY-MM-DD; every trend period is labelled YYYY-MM by its first month, so quarters read 2026-01, 2026-04; ranking rows can have states: null and always carry estimated_value, which is null without hs4; ranking filters echo port_of_discharge as pod; a profile miss has no until and totals.states can be null; a 422 is either a schema-validation detail list or the query cost-cap { error }.
  • Endpoint and tag descriptions carry the data caveats: volume is physical containers, values are modelled estimates, the current month is partial, names are split by state or withheld so missing volume is a floor, the company filter is a substring match, and carriers have spelling variants. Default windows are stated exactly: trends cover the 24 months ending with the current, partial month, breakdowns 12.
  • API Reference navigation group with nine pages. The Postman collection is regenerated by the Postman bot on push.

SDK

  • client.tradeIntel with meta, searchCompanies, companyProfile, searchCommodities, topImporters, trends, breakdown, lookupContainer, lookupBillOfLading. Each returns the plain JSON body, typed from the generated schemas, so the nulls above are visible to TypeScript callers.
  • The 403 not-enabled body maps to FeatureNotEnabledError. 400 and 422 map to ValidationError with the server's message, including FastAPI detail arrays (Input should be less than or equal to 50 (body.limit)) and the cost-cap error.
  • The trade intelligence POSTs are read-only queries, so the retry policy treats POSTs under /trade_intel/ like GETs. Other writes still need an Idempotency-Key. Network failures and 429/5xx responses now share one per-request retry budget, so a request never makes more than maxRetries retries in total (before, a network failure followed by 5xx responses could retry up to twice as often).
  • generate:types passes --default-non-nullable=false so request fields with server defaults (limit, months, top) stay optional.
  • Fixture tests use the service's real formats. spec-types.test.ts pins the nullable and optional fields at type level (enforced by tsc in vp run check). SDK reference docs regenerated.

MCP tools (folded in from #417)

  • Seven trade intelligence tools, bringing the server to 21 tools, plus evals, docs/mcp/home.mdx, TOOLS_OVERVIEW.md, the ChatGPT and Claude directory submission JSON, and an Unreleased MCP changelog entry.
  • Results are compacted for agents. All months are shown as YYYY-MM: get_trade_data_coverage reports last_full_month and search_window.to, and search_importers reports period.to, from the service's YYYY-MM-DD index dates. rank_importers reports estimated_value_usd only when the service sends a value.

Versioning

The SDK version is not hand-bumped. release-please produces the 0.6.0 release PR with its changelog entry after merge.

Test evidence (head ccf71a5d)

  • vp run check passes. vp run test: SDK 416 passed (1 file skipped: live smoke), MCP 502 passed, CLI 153 passed.
  • Each fix in the latest batch has a regression test that fails without it: 5 MCP tests on the date handling and null value, 6 type assertions in spec-types.test.ts, and the mixed network/5xx retry test (5 requests and a success before, 3 requests and UpstreamError after).
  • CI on ccf71a5d: every check passes except MCP preview 2026-07-28 (see Known CI noise).
  • Independent exact-head reviews of the latest batch: Opus and Codex (GPT-6 Astra) both found no blocking issues and confirmed the contract claims against the t49 staging source.

Merge prerequisites (owner go-ahead required)

Nothing here is auto-merged, published, or deployed. Merging this PR does all of the following at once:

  1. Publishes the Trade Intelligence API reference on the public docs site through Mintlify.
  2. Deploys the seven new MCP tools to mcp.terminal49.com, since Vercel rebuilds the gateway from main. Afterwards, update the tool names in the Claude directory listing, let the ChatGPT directory rescan, and cut the usual release(mcp) version PR.
  3. Starts the SDK 0.6.0 release. release-please opens the release PR. Merging that PR publishes @terminal49/sdk to npm, which needs separate approval.

Before merging:

  • Reviews: @maurycy for the code (SDK and MCP), @namitabhasin for the public API reference docs. One thread is left open on purpose for the reviewer: whether to type latest_import in the container lookup.
  • Live check: run at least one successful live trade intelligence call with a token from an account that has trade intelligence enabled (the SDK smoke test now expects facts_since_month as a first-of-month date). So far only the 403 not-enabled path has been verified live.
  • Squash message: delete any [skip ci] text that the Postman bot's commits add to the squash message. If it stays, CI, Postman deploy, and release-please are skipped on main.

Known open item: a trade intelligence call can still outlast the 30-second gateway limit. The SDK default is a 30-second timeout per attempt and 2 retries, the backend proxy allows 10 seconds per upstream call, and there is no deadline across the whole tool call. This is not new to trade intelligence: every MCP tool shares that client. Fixing it needs a call-wide deadline for the gateway, so it is left out of this PR.

Known CI noise: CI / MCP preview 2026-07-28 fails at fixture discovery (list_shipments ... check eval credentials) because the shared MCP_EVAL_TOKEN passthrough credential returns 401. The same failure shows up on every PR and in main's own mcp job. That secret needs to be rotated; this PR's code does not cause the failure.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

RetriggerConfidence Score: 4/5

The PR appears safe to merge, with non-blocking improvements needed in the Postman examples, lookup types, and retry documentation.

Summary

Adds nine trade intelligence operations to the API reference and exposes them through client.tradeIntel.

  • The API reference now groups nine Trade Intelligence queries together.
  • TypeScript callers can query importers, commodities, trends, and import records with client.tradeIntel.
  • Trade Intelligence errors read clearly, and its query POSTs can retry safely.

Diagram

sequenceDiagram
    participant Caller
    participant Manager as TradeIntelManager
    participant Transport
    participant API
    Caller->>Manager: Query or lookup
    Manager->>Transport: Plain JSON request
    Transport->>API: Token-authenticated GET or POST
    alt Retryable failure
      API-->>Transport: 429, 5xx, or network failure
      Transport->>API: Replay read-only request after backoff
    end
    API-->>Transport: JSON response
    Transport-->>Caller: Plain body or typed error
Loading

Reviews (1) · Last reviewed commit: "chore(sdk): regenerate types for the loo..." · Reviewed by Greptile

Current readiness verification keeps the original gates. Local checks and tests pass, but authenticated MCP preview evaluation is blocked by a backend HTTP 401 for the shared CI passthrough credential. The transport handshake alone does not prove tool access. Review fixes and rebases preserve this PR's base topology. No merge or deploy has been performed.

@vercel

vercel Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
api Ready Ready Preview Oct 17, 2026 6:51pm UTC

Request Review

@mintlify

mintlify Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
terminal49 🟢 Ready View Preview Oct 17, 2026, 6:22 PM

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-17T18:53:21.717720Z ccf71a5 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 31826ba961

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/openapi.json
@greptile-apps

greptile-apps Bot commented Oct 9, 2026

Copy link
Copy Markdown

Comments Outside Diff

These findings could not be posted inline.

  • P2 Postman queries contain invalid defaults Terminal49-API.postman_collection.json:20639 ▶

    The new trends and breakdown requests contain impossible months such as 0416-86 and 4003-21, plus "reefer": "<boolean>" where a boolean is required. Users must repair unrelated optional fields before they can try either query. Generate these bodies from the supplied OpenAPI examples, and add a check that the generated bodies match the request schemas.

  • P2 Lookup summary fields lack types docs/openapi.json:20609 ▶

    latest_import has an empty properties object, so lookupContainer() exposes its documented date, carrier, vessel, and port fields as unknown. SDK users must write casts to use this main part of the lookup result. Add a schema for the known fields, with their actual optional and nullable types, and regenerate the SDK types.

  • P2 Retry reference loses its explanation sdks/typescript-sdk/src/client/interceptors.ts:55 ▶

    The new helper declarations separate RetryInterceptor from its existing documentation comment. The regenerated reference loses the explanation of network retries, Retry-After, and the protection against replaying writes. Move the class comment directly above RetryInterceptor, update it to mention read-only query POSTs, and regenerate the reference.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c5d97b2232

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/openapi.json Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 42a2d47ce5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/openapi.json

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 584dbb66e4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Terminal49-API.postman_collection.json Outdated
"method": "POST",
"body": {
"mode": "raw",
"raw": "{\n \"measure\": \"containers\",\n \"group_by\": [\n \"hs2\",\n \"origin_country\"\n ],\n \"filters\": {\n \"company\": \"<string>\",\n \"company_state\": \"<string>\",\n \"hs4\": \"9179\",\n \"hs2\": \"49\",\n \"carrier\": \"<string>\",\n \"scac\": \"<string>\",\n \"origin_country\": \"<string>\",\n \"origin_region\": \"<string>\",\n \"pol\": \"<string>\",\n \"pol_country\": \"<string>\",\n \"pod\": \"<string>\",\n \"pod_coast\": \"WEST\",\n \"dest_state\": \"<string>\",\n \"container_type\": \"<string>\",\n \"reefer\": \"<boolean>\"\n },\n \"interval\": \"month\",\n \"since\": \"7668-10\",\n \"until\": \"1392-09\",\n \"top\": 10\n}",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Emit an ordered date range in generated requests

When someone sends the regenerated Get import trends request as provided, it uses since: "7668-10" and until: "1392-09", so the start is later than the end and the request is rejected rather than demonstrating the endpoint; the breakdown request likewise uses dates thousands of years outside the documented January 2022-to-present coverage. Fresh evidence after the calendar-month pattern fix is that regeneration now produces syntactically valid but still unusable ranges. Adjust the OpenAPI/generation inputs so these requests use coherent example dates, then regenerate rather than editing this artifact directly.

AGENTS.md reference: AGENTS.md:L51-L53

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving the spec as is. The Postman collection is generated in Schema mode, which fakes every request-body value from the schema and ignores examples. I checked: adding a body example doesn't change the output. Other bodies are placeholders too: hs4 comes out as a random valid code, and string filters as <string>. JSON Schema can't express that since must not be after until, so no schema change orders them. The documented request examples in the API reference use ordered ranges. Generating the whole collection from examples would be a separate tooling change to the Postman workflow.

dodeja and others added 5 commits October 17, 2026 00:40
Brings in #434, #437 and #444. The only conflict was the generated
Postman collection: took main's copy and regenerated it from the merged
docs/openapi.json with openapi-to-postmanv2@6.3.0. SDK types, MCP filter
schemas and SDK reference docs regenerate unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The API rejects estimated_value with pol, pol_country or container_type
as a dimension or filter (docs/openapi.json TradeIntelDimension and
TradeIntelFilters). The MCP guide presented estimated_value as generally
available, so it now states the restriction for get_trade_trends and
get_trade_breakdown and lists the lane combination rules with the other
trade intelligence caveats.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The measure and dimension descriptions for get_trade_trends and
get_trade_breakdown now say that estimated_value cannot be used with
pol, pol_country or container_type, which the API answers with a 400.
A protocol test checks the advertised measure description and the MCP
docs section for both tools.

Addresses the Codex review on packages/mcp/src/tools/trade-intel.ts:82.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PR registers seven trade intelligence tools, but the MCP README
header and table, the TOOLS_OVERVIEW summary and the setup guide's
"list the tools" check still said 14. The README table now lists the
trade intelligence tools too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dodeja and others added 2 commits October 17, 2026 00:43
…list

Fails when the README, TOOLS_OVERVIEW or the setup guide state a tool
count other than the tools/list length, or when the README tool table
misses or adds a tool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The API rejects estimated_value when pol, pol_country or container_type
is used at all, as a dimension or as a filter. The test now fails if the
tool description or the docs drop the filter case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dodeja

dodeja commented Oct 17, 2026

Copy link
Copy Markdown
Member Author

Merged main into this branch with an ordinary merge, 7f4fb93, bringing in #434, #437 and #444. The only conflict was the generated Postman collection. I took main's copy and regenerated it from the merged spec. The Postman bot then regenerated it again in 55603b0. SDK types, MCP filter schemas and the SDK reference regenerate unchanged.

Follow-ups on top, all signed:

  • b9e6ca7 / 803466b: estimated_value is not available by lane (pol, pol_country, container_type, as a dimension or a filter). This is now in the trends and breakdown tool schemas and in docs/mcp/home.mdx, with a protocol test (Codex P2 on trade-intel.ts:82).
  • 881e9c1 / 8cb6d1c: the MCP README, TOOLS_OVERVIEW and the setup guide said 14 tools. They now say 21, and the README table lists the trade intelligence tools. A new protocol test checks those counts and the README table against tools/list.

Local checks: vp run check passes. vp run test passes: SDK 153, MCP 499, CLI 412. The Postman generation test passes.

Note for merging: 55603b0 is the Postman bot's commit, and its message contains [skip ci]. Remove that line from the squash message.

dodeja and others added 4 commits October 17, 2026 11:16
Without since, the trade-intel service returns the N months ending with
until (24 for trends, 12 for breakdown), so since is N - 1 months before
until. The test fails while tools/list, docs/mcp/home.mdx or the OpenAPI
spec describe the default as "N months ago" or "about a year back".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ctly

Without since, the API returns the N months ending with until (24 for
trends, 12 for breakdown), so since defaults to N - 1 months before
until. The since fields said "Defaults to 24/12 months ago", which reads
as N + 1 calendar months. The breakdown and trends descriptions and the
MCP guide now give the same window. Source: analytics._range in the
trade-intel service.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…them

get_trade_trends and get_trade_breakdown said "about two years back" and
"about a year back, not a clean 12 months". They now say the window is
the 24 or 12 months ending with the current, incomplete month, with
since 23 or 11 months before until. The SDK JSDoc, regenerated types
and SDK reference match the OpenAPI source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dodeja

dodeja commented Oct 17, 2026

Copy link
Copy Markdown
Member Author

Follow-up on the default-window finding from the independent review of 88cb9e8. That review flagged docs/openapi.json (breakdown description) and sdks/typescript-sdk/src/client/managers/trade-intel.ts (trends JSDoc), saying the default is "an inclusive start 12 months ago through the current month = 13 calendar months".

What the API actually does. The backend source has the final say. In apps/trade-intel/src/trade_intel/analytics.py on Terminal49/t49 staging (last changed in abbb633a6e), _range sets end_exclusive = until + 1 month and then since = end_exclusive - N months. N is 24 for trends and 12 for breakdown. until defaults to the current, partial month. So with no since, the window is the N months that end with until. I ran that function's code as written:

default=12 until=None:    since=2025-11 until=2026-10 months=12
default=24 until=None:    since=2024-11 until=2026-10 months=24
default=12 until=2026-06: since=2025-07 until=2026-06 months=12
default=24 until=2026-06: since=2024-07 until=2026-06 months=24

That means the breakdown sentence ("the last 12 months including the current, partial month") and the SDK trends JSDoc were already right. The "13 months" claim does not hold. The real off-by-one was in the since field descriptions, "Defaults to 12 months ago" and "Defaults to 24 months ago". Those read as N + 1 calendar months, and they were measured from today rather than from until. The MCP descriptions ("about a year back, so the default window is not a clean 12 months") were vague in the same way. No behavior changed and no new policy was added. Only the wording now matches what the API does.

Commits (all signed):

  • 67c21f0 test(mcp): a regression test in packages/mcp/src/protocol-compat.test.ts. For get_trade_trends and get_trade_breakdown, it requires the tools/list description, the tools/list since description, docs/mcp/home.mdx and the OpenAPI operation and request since descriptions to state "N months ending with" and "N - 1 months before". It also rejects "N months ago" and "about a year back".
  • be31631 docs(trade-intel): corrects the docs/openapi.json trends and breakdown descriptions, the since fields and docs/mcp/home.mdx.
  • 99d98b0 is the Postman bot's regeneration of that spec change ([skip ci]).
  • f8e9abf fix(mcp,sdk): updates the MCP tool and since descriptions and the SDK trends/breakdown JSDoc. It also regenerates src/generated/terminal49.ts and docs/sdk/reference from the source. f8e9abf was pushed on top of the bot commit, so CI ran on the PR head.

Failing before the fix, passing after. On 67c21f0 alone:

× states the default window the API computes, in tools/list, the MCP docs and the OpenAPI spec
AssertionError: get_trade_trends tools/list since: expected 'First month, YYYY-MM, inclusive. Defa…' to contain '23 months before'
Received: "First month, YYYY-MM, inclusive. Default: about two years back. Data goes back to 2022-01. Set since and until explicitly when the number of periods matters."
Tests  1 failed | 11 skipped (12)

On f8e9abf, vp run test passes with sdk 153, cli 412 (1 file skipped) and mcp 500. vp run check exits 0 and tools/postman-generation.test.mjs passes 3 of 3.

CI on f8e9abf. Every check passes except MCP preview 2026-07-28. That job fails in fixture discovery with "check eval credentials". This is the known broken MCP_EVAL_TOKEN eval credential, which fails the same way on main, so it is not caused by this change.

Independent review. gpt-6-sol re-derived the windows from the backend excerpt and agreed the 13-month claim was wrong. It found no wrong or off-by-one sentence in the changed wording. It raised one P3: the per-field example values in the TradeIntelTrends/TradeIntelBreakdown response schemas (since 2024-10/2025-10 with until 2026-10) span 25 and 13 months. I left those alone. Each operation's own response example (2026-01 to 2026-06) is the one that gets rendered, and it is labelled as an explicit range.

This PR still needs human review. I have not marked it merge-ready, and no threads were resolved.

dodeja and others added 4 commits October 17, 2026 11:49
…ice returns

Checked against Terminal49/t49 staging (apps/trade-intel build.py,
analytics.py, api.py, index.py):

- meta dates (since_month, until_month_exclusive, facts_since_month,
  partial_month, refreshed_since) and company first_month/last_month are
  first-of-month dates, YYYY-MM-DD; facts_until_exclusive is the day after
  the build; mode is full or incremental.
- importers/top: states can be null, estimated_value is always sent and is
  null without hs4, and filters echo port_of_discharge as pod.
- companies/profile: until is absent when found is false, and totals.states
  can be null.
- trends periods are always YYYY-MM (the first month of the quarter or
  year), not YYYY-Qn or YYYY.
- a 422 can also be the query cost-cap error, { error }.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tries

Regenerated types from the corrected spec, so TradeIntelTopImporter
states and estimated_value, profile totals.states and profile until are
typed as the service sends them, ranking filters expose pod, and the 422
body includes the cost-cap { error } variant. Type tests in
spec-types.test.ts fail against the old types.

Fixtures now use the service's real formats (YYYY-MM-DD meta dates,
YYYY-MM quarterly periods), and the live smoke test expects
facts_since_month as a first-of-month date.

RetryInterceptor kept separate retry counters in onError and onResponse,
so network failure, network failure, 503, 503, success made five requests
with maxRetries 2. Both paths now draw on one per-request budget.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The service sends meta and search-window dates as YYYY-MM-DD, but
previousMonth() only accepted YYYY-MM, so get_trade_data_coverage lost
last_full_month and search_window.to, and search_importers lost
period.to. Index dates are now accepted in either form and shown as
YYYY-MM, including first_month and last_month. rank_importers also leaves
out estimated_value_usd when the service sends null instead of emitting
an undefined key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dodeja

dodeja commented Oct 17, 2026

Copy link
Copy Markdown
Member Author

Receipts for the fixes that follow up on the independent review of f8e9abfa. The head moved f8e9abfa → ccf71a5d through ordinary signed fast-forward commits. c88f4b69 is the Postman bot's [skip ci] commit, so ccf71a5d sits on top of it and full CI ran on the head. Nothing was merged, resolved, force-pushed, or released.

Contract source: Terminal49/t49 staging (apps/trade-intel/src/trade_intel/build.py, analytics.py, api.py, index.py; apps/tnt-api/app/apis/trade_intel/client.rb passes bodies through unchanged).

Fixed (each with a test that fails without the fix)

  1. P1, MCP index dates. The service sends meta dates with date.isoformat() (build.py ~L277), so they look like 2026-10-01. previousMonth() only accepted 2026-10, so get_trade_data_coverage lost last_full_month and search_window.to, and search_importers lost period.to. MCP now accepts both forms and shows every month as YYYY-MM, including first_month and last_month (stored as DATE in index.py). The SDK smoke test now expects facts_since_month to be a first-of-month date. Commit ccf71a5d. With the fix reverted, 5 MCP tests fail.
  2. P2, ranking nulls. top_importers selects null as estimated_value without hs4 (analytics.py ~L386), and states comes from states.get(), so it can be None. The spec now types both as nullable, with estimated_value required. The same applies to profile totals.states: DuckDB list(...) filter (where ...) returns NULL when no row has a state, which I checked with duckdb 1.5.6. MCP rank_importers no longer emits estimated_value_usd: undefined. Commits f88bae9b, ea7e9a90, ccf71a5d.
  3. P2, profile miss. A miss returns only company_name, company_state, since, and found (analytics.py ~L325), so until is now optional. The MCP miss fixture no longer invents until.
  4. P2, trend periods. The service emits r["period"][:7] (analytics.py ~L207), so quarters read 2026-01 and 2026-04, and years read 2026-01. The spec, examples, and fixtures used 2026-Q1; they now match the service.
  5. P2, ranking filter key. port_of_discharge comes back as filters.pod (analytics.py ~L368). The spec and the MCP fixture now say pod.
  6. P2, 422 shape. The cost-cap 422 is { "error": "Query too broad for the cost cap; ..." } (api.py ~L180). The shared 422 response is now oneOf validation detail or TradeIntelError. An SDK test covers the message mapping.
  7. P2, retry budget (pre-existing, small fix). onError and onResponse each counted retries from zero, so network fail, network fail, 503, 503, 200 made 5 requests with maxRetries: 2. One per-request budget now covers both paths. Without the fix, the regression test resolves after 5 calls; with it, the test sees 3 calls and an UpstreamError.

Type-level checks for 2, 3, 5 and 6 are in sdks/typescript-sdk/src/spec-types.test.ts. Against the old generated types, all 6 assertions fail tsc.

Not fixed, recorded

  • Retries vs the 30 s gateway limit. Three ~10 s attempts plus backoff can exceed maxDuration: 30. This applies to every MCP tool, not only trade intelligence, because they all share the default client (30 s per attempt, 2 retries). The fix is a deadline across the whole tool call, which changes how the gateway budgets time. It belongs with the call-wide deadline work, not this PR. It is recorded in the PR body.

Verification at ccf71a5d

  • vp run check passes. vp run test: SDK 416 passed (1 live-smoke file skipped), MCP 502 passed, CLI 153 passed. Generated MCP filter schemas show no drift.
  • CI: everything passes except MCP preview 2026-07-28, which is the known MCP_EVAL_TOKEN failure (Fixture discovery list_shipments failed ... check eval credentials). That is a provider block, not this code.
  • Postman: openapi-to-postmanv2@6.3.0 is not deterministic (random ids and enum picks), so the bot regenerated the collection in c88f4b69.
  • Independent exact-head reviews: Opus found no blockers (3 non-blocking P3s) and Codex gpt-6-astra found no blocking findings. Both checked the contract claims against t49 staging.

Remaining gates: human review (@maurycy code, @namitabhasin docs), a live call from an account with trade intelligence enabled, rotating the eval credential, and removing [skip ci] from the squash message.

@dodeja
dodeja merged commit 68e8458 into main Oct 18, 2026
23 of 24 checks passed
dodeja added a commit that referenced this pull request Oct 18, 2026
Brings in trade intelligence (#415) and the CI trust gates (#384).

The seven trade intelligence tools move into the tool registry under a new
`trade` toolset. It is on by default and stays on for the shared env token,
because every tool in it is read-only, so the default tools/list keeps
everything main serves today. Names, titles, descriptions, schemas and
annotations are unchanged. The per-account entitlement is still checked when
a tool runs, not when it is registered.

The server instructions keep main's trade guidance as their own paragraph,
dropped when the session does not serve the trade toolset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot mentioned this pull request Oct 17, 2026

This branch was successfully deployed

2 active (1 outdated) deployments
Preview — ccf71a5d Deployed Oct 17, 2026 by vercel[bot]
staging - docs — f8e9abfa Deployed Oct 17, 2026 by mintlify[bot]
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