Uh oh!
There was an error while loading. Please reload this page.
fix(mcp): authoritative status, demurrage staleness guard, terminal-local times; drop _mapped bloat - #277
Conversation
…ocal times; drop _mapped bloat Extracts three pure, unit-tested modules under packages/mcp/src/lib/ and wires them into get_container, the container resource, and get_shipment_details: - ContainerStatusResolver (container-status.ts): surfaces the API `current_status` as the headline status in get_container AND resources/container.ts, ending the three-way status-vocabulary divergence. The heuristic lifecycle is kept only as clearly-labeled, non-authoritative steering metadata and never reports "delivered" unless `delivered_at` is set. - DemurrageUrgencyEvaluator (demurrage.ts): removes the fabricated "~$75-150/day demurrage accruing / URGENT" message. Surfaces the real fees_at_pod_terminal (amount + currency) and suppresses urgency when terminal_checked_at is stale, was never set, or tracking is stopped/closed. - TemporalFormatter (temporal.ts): renders terminal timestamps in pod_timezone and computes day-deltas in terminal-local time, fixing the get_shipment_details "ETA in N days" UTC off-by-one. Also: - get_container `include` now AUGMENTS the default [shipment, pod_terminal] instead of replacing it. - Surfaces pod_timezone + per-channel LFDs (terminal/rail/line from import_deadlines) in get_container. - equipment.length numeric-enum guard (no "" sentinel); drops the phantom `updated_at` the API never returns on containers. - Drops the duplicate `_mapped` payload from get_container and get_shipment_details (no more format:both with no mapper); returns the curated summary only. - Removes the dead getContainerTool / getShipmentDetailsTool objects; the Zod input schema in server.ts is the single source of truth. Closes DEV-10659 Closes DEV-10661 Closes DEV-10664 Closes DEV-10665 Green gate: SDK build/type-check/test (51 pass, 2 skip) + MCP build/type-check/ test (106 pass) all green; oxlint clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…not the container line_tracking_stopped_at / line_tracking_stopped_reason live on the SHIPMENT schema (per the generated OpenAPI types), not the container. The previous code read them off the container attributes, so on real API data the tracking-stopped branch never fired and the "suppress demurrage urgency when tracking is stopped/closed" acceptance criterion was unmet. get_container now reads these fields from the shipment already resolved out of the JSON:API included[] (relationships.shipment.data.id), and the container resource resolves the sideloaded shipment the same way. When the shipment is not included, both fall back to treating tracking as not-stopped rather than crash. Adds unit tests proving suppression fires when the sideloaded shipment has line_tracking_stopped_at set, does not fire when absent, and does not crash when the shipment is missing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:c14098b185
ℹ️ 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".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…D safe Address PR #277 review feedback (codex P2 / greptile P1 + P2s) and known residuals: - evaluateDemurrageUrgency now accepts a terminal-local `days_until_lfd` and classifies `urgency` from it, so the displayed count and the urgency band can no longer disagree at a threshold boundary (e.g. 3 vs 4 days). get- container.ts feeds dayDeltaInZone() in instead of patching days_until_lfd after the fact. - temporal: treat a date-only value (e.g. "2099-01-10") as a literal calendar day in the terminal timezone in formatInZone/localCalendarDate/dayDeltaInZone, fixing a one-day-early render/delta in west-of-UTC zones from the UTC-midnight parse. - container resource: drop the stray double blank line at the bottom of the markdown when urgency isn't suppressed and there's no rail section. - demurrage: correct the total_amount JSDoc wording. - tests: add temporal date-only cases, demurrage urgency-from-local-days cases, and container resource-level tests for status/fees/LFD-urgency rendering. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
dodeja
commented
Jun 26, 2026
Triaged the automated review (Codex + Greptile) plus the known residuals, and pushed Addressed
Skipped
Gate green from the worktree: SDK 51 pass / 2 skip, MCP 121 pass; both builds + type-checks clean. No SDK public-surface change, so no docs/sdk/reference regen needed. Only the changed MCP files were oxfmt-formatted (no mass reformat). |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:6b705cf45d
ℹ️ 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".
| if (!attrs.pod_discharged_at) return 'arrived'; | ||
| // Only the API's explicit delivery confirmation may yield "delivered". | ||
| if (attrs.delivered_at) return 'delivered'; |
There was a problem hiding this comment.
Derive delivery from a real API field
For delivered containers this branch never fires because the public container attributes schema/generated types do not define delivered_at (the actual payload exposes current_status: "delivered" and full-out timestamps). That means _metadata.derived_lifecycle and presentation_guidance fall through to discharged/at_terminal, so the tool can steer the LLM toward pickup/demurrage actions instead of confirming delivery. Use current_status === 'delivered' or a real container milestone field for this gate.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Flagging for a maintainer decision rather than auto-resolving — this is a legitimate open design question, not something already fixed.
Confirmed details:
delivered_atis real in the API payload (present in the container fixtures:containers.get.base.json,containers.get.include.json,containers.list.json) but is NOT yet in the generated OpenAPI container-attributes schema (current_statusandimport_deadlinesare the only attributes typed in that region ofgenerated/terminal49.ts). So the comment is right that the typed schema does not define it, but it is a field the API does emit.- The headline
statusis unaffected:resolveContainerStatussurfacescurrent_statusverbatim, so acurrent_status: "delivered"container correctly reads "delivered" to the user. Thedelivered_atgate only governs the explicitly non-authoritativederived_lifecycleand thepresentation_guidance/suggestionssteering keyed off it. - The strict
delivered_at-only gate is a deliberate, documented design rule incontainer-status.ts("never label a containerdeliveredunless the API has actually setdelivered_at"). The fixtures also showcurrent_status: "picked_up"withdelivered_at: nullandpod_full_out_atset, i.e. terminal-complete containers where the derived lifecycle would fall through toat_terminal/dischargedand the guidance could lean toward pickup/demurrage.
So the trade-off is real: switching the gate to current_status === "delivered" (or a full-out milestone) would make the steering metadata track delivery, but it changes the deliberate conservative derivation and would need test updates (container-status.test.ts asserts the current delivered_at behavior). Not resolving — please confirm the intended steering behavior for delivered/picked_up containers and whether to broaden the gate.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Summary
Production-readiness fixes for the
get_container/get_shipment_detailstools and thet49:container/{id}resource. Extracts three pure, unit-tested modules underpackages/mcp/src/lib/(the deep-module seam) and wires them in:container-status.ts) — surfaces the APIcurrent_statusas the authoritative headline status in bothget_containerandresources/container.ts, ending the three-way status-vocabulary divergence (API status vs. derived lifecycle vs. search status). The heuristic lifecycle is retained only as clearly-labeled, non-authoritative steering metadata and never reportsdeliveredunlessdelivered_atis set.demurrage.ts) — removes the fabricated~$75-150/day demurrage accruing / URGENTmessage. Surfaces the realfees_at_pod_terminal(amount + currency code) and suppresses urgency whenterminal_checked_atis stale (>7d) / never set, or when tracking is stopped/closed.temporal.ts) — renders terminal timestamps inpod_timezoneand computes day-deltas in terminal-local time, fixing theget_shipment_details"ETA in N days" UTC off-by-one.Additional correctness fixes:
get_containerincludenow augments the default[shipment, pod_terminal]instead of replacing it (previouslyinclude=[transport_events]silently dropped shipment + pod_terminal).pod_timezone+ per-channel LFDs (terminal/rail/linefromimport_deadlines) inget_container.equipment.lengthnumeric-enum guard (no""sentinel); drops the phantomupdated_atthe API never returns on containers._mappedpayload fromget_containerandget_shipment_details(no moreformat:'both'with no mapper); returns the curated summary only.getContainerTool/getShipmentDetailsToolobjects — the Zod input schema inserver.tsis the single source of truth.Issues
Closes DEV-10659
Closes DEV-10661
Closes DEV-10664
Closes DEV-10665
(DEV-10665 partial: include-merge, equipment shape, drop phantom
updated_at, dead*Toolremoval — scoped to these tool/resource files.)Green gate
build @terminal49/sdkbuild @terminal49/mcptype-check @terminal49/sdktype-check @terminal49/mcptest @terminal49/sdk --runtest @terminal49/mcp --runoxlint srcTDD: the three pure modules have failing-first unit tests (
*.test.tsalongside each module) plus expandedget_container/get_shipment_detailscontract assertions.Note
AI-drafted PR for human review. Touches only the
get_container/get_shipment_details/ container-resource files plus the newlib/modules and their tests; nolist_*/search/ server-wide code was modified.🤖 Generated with Claude Code
Greptile Summary
This PR extracts three pure, tested modules (
container-status.ts,demurrage.ts,temporal.ts) and wires them intoget_container,get_shipment_details, and thet49:container/{id}resource to fix fabricated demurrage urgency messages, three-way status vocabulary divergence, UTC off-by-one in day-delta presentation, and theincludeparameter silently dropping the default sideloads.current_statusverbatim as the headline;deliveredlifecycle gated ondelivered_at.~$75-150/day URGENTmessage with realfees_at_pod_terminaldata; suppresses urgency when data is stale, never checked, or tracking stopped \u2014 readingline_tracking_stopped_*from the sideloaded shipment.pod_timezoneand computes day deltas between local calendar dates, fixing the UTC off-by-one; surfaces per-channel LFDs in both tool and resource.Confidence Score: 3/5
Safe to merge for most inputs, but LFD urgency classification and the displayed day count can silently disagree by one day for LFDs near a threshold boundary in a non-UTC terminal timezone.
The urgency label is computed from a UTC millisecond ratio inside
evaluateDemurrageUrgency, whileget-container.tsthen patches onlydays_until_lfdwith the terminal-local calendar value fromdayDeltaInZone. When these straddle a threshold — e.g. UTC rounds to 4 days (none) while the local calendar gives 3 (imminent) — the tool returnsurgency: 'none'withdays_until_lfd: 3, the exact inconsistency the timezone fix was meant to eliminate.packages/mcp/src/tools/get-container.ts lines 217–232 (where rawDemurrage urgency is computed then days_until_lfd is patched independently) and packages/mcp/src/lib/demurrage.ts (where the UTC-millisecond daysUntilLfd drives urgency classification).
Important Files Changed
current_statuswith a clearly-labeled derived lifecycle fallback; delivery gated ondelivered_at. Logic is straightforward and well-tested.total_amount; urgency classification uses UTC milliseconds while the caller patchesdays_until_lfdwith local-calendar values, leaving them potentially mismatched at thresholds.en-CAlocale trick for YYYY-MM-DD output is correct; DST-safe midnight comparison is sound.urgencyis classified using UTC-based millisecond rounding insideevaluateDemurrageUrgency, thendays_until_lfdis patched with the local-calendar value — these two can disagree by 1 day near the 3/4-day or 0/-1-day thresholds._mappedbloat, switches toformat: 'raw', adds timezone-aware ETA day count viadayDeltaInZone. Clean change with no identified issues.Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant Caller participant get_container participant Terminal49Client participant ContainerStatusResolver participant DemurrageEvaluator participant TemporalFormatter Caller->>get_container: executeGetContainer(args) get_container->>get_container: resolveIncludes(args.include) get_container->>Terminal49Client: "containers.get(id, includes, {format:'raw'})" Terminal49Client-->>get_container: raw JSON:API response get_container->>ContainerStatusResolver: resolveContainerStatus(container.attributes) ContainerStatusResolver-->>get_container: "{status, status_source, derived_lifecycle}" get_container->>DemurrageEvaluator: "evaluateDemurrageUrgency({fees, pickup_lfd, terminal_checked_at, tracking_stopped})" Note over get_container,DemurrageEvaluator: tracking_stopped read from sideloaded shipment DemurrageEvaluator-->>get_container: "{urgency, fees, days_until_lfd (UTC-based)}" get_container->>TemporalFormatter: dayDeltaInZone(pickup_lfd, pod_timezone) TemporalFormatter-->>get_container: localDaysUntilLfd (calendar-date based) get_container->>TemporalFormatter: formatInZone(timestamps, pod_timezone) TemporalFormatter-->>get_container: human-readable local strings get_container-->>Caller: ContainerStatus (curated summary, no _mapped bloat)%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant Caller participant get_container participant Terminal49Client participant ContainerStatusResolver participant DemurrageEvaluator participant TemporalFormatter Caller->>get_container: executeGetContainer(args) get_container->>get_container: resolveIncludes(args.include) get_container->>Terminal49Client: "containers.get(id, includes, {format:'raw'})" Terminal49Client-->>get_container: raw JSON:API response get_container->>ContainerStatusResolver: resolveContainerStatus(container.attributes) ContainerStatusResolver-->>get_container: "{status, status_source, derived_lifecycle}" get_container->>DemurrageEvaluator: "evaluateDemurrageUrgency({fees, pickup_lfd, terminal_checked_at, tracking_stopped})" Note over get_container,DemurrageEvaluator: tracking_stopped read from sideloaded shipment DemurrageEvaluator-->>get_container: "{urgency, fees, days_until_lfd (UTC-based)}" get_container->>TemporalFormatter: dayDeltaInZone(pickup_lfd, pod_timezone) TemporalFormatter-->>get_container: localDaysUntilLfd (calendar-date based) get_container->>TemporalFormatter: formatInZone(timestamps, pod_timezone) TemporalFormatter-->>get_container: human-readable local strings get_container-->>Caller: ContainerStatus (curated summary, no _mapped bloat)Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "fix(mcp): read line_tracking_stopped_* f..." | Re-trigger Greptile