Repository navigation
fix(mcp): artifact URLs actually resolve — route /artifacts + durable store - #510
Merged
Merged
Conversation
… store
Two stacked bugs made every artifact handle a dead end on the hosted
deploy:
1. The Build Output API routing table (services/mcp/build.sh) never
included /artifacts/(.*) — the path never reached the function and
Vercel's platform answered 403. handleArtifactRequest was dead code
in prod. Route added (GET/HEAD/OPTIONS -> the mcp function).
2. The artifact store was a per-instance in-memory Map (documented as
such), so even with the route fixed, a handle minted by export_gerber
on one serverless instance was unreadable on every other:
quote_manufacturing rejected a fab_artifact_id minted minutes earlier
("Unknown or expired") and cold instances 404'd the download.
The store now mirrors session-store.ts exactly: the in-memory registry
becomes a warm-instance cache in front of a durable, service-role-only
mcp_artifacts table (migration 033), gated by the same
SUPABASE_URL/SUPABASE_SERVICE_ROLE_KEY env and using the same injectable
sessionFetch seam for tests.
- Writes stay sync for callers: storeArtifact caches, kicks off a
best-effort persist, and entry.ts awaits flushArtifacts() alongside
flushTelemetry() so the write survives the instance freeze.
- Cross-instance reads go async with hydrate-on-miss: the /artifacts
route and quote/place_order use getArtifactAsync /
resolveArtifactRefAsync; expired durable rows read as absent and are
lazily deleted, with a cleanup_expired_mcp_artifacts() sweep for
never-read rows.
- Durability is observable: server_info and /health now report
artifact_store: supabase|in-memory next to session_store.
- A durable outage degrades to warm-cache-only (old behavior), never a
tool failure.
Field repro that motivated this (TMP-1 motor board session, prod
f6d9258): export_gerber returned art_LWz9vu0YPGcqL5HA; the URL 403'd
from two networks and quote_manufacturing could not bind it 2 minutes
later on the same deployment.
Tests: 6 new durable-store cases (cold-start hydrate, route serve from
durable row, cross-instance quote binding, expiry sweep, degraded
write); full @vcad/mcp suite green (721 passed / 47 files).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018uDJYmC9cSQhB3tPwNUtnD
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
What shippedArtifact links generated by export_gerber and export_cad now work reliably. Two bugs were fixed: the download route was never connected to the hosted deployment, so every artifact URL returned an error regardless of network; and the files themselves were only held in memory on the server instance that created them, so a handle produced in one request was invisible to any other — causing quote_manufacturing to reject a valid fab file as "unknown or expired" minutes after it was exported. Artifacts are now stored durably so any request can retrieve them, and the download route is properly wired. Plain-English summary generated by Choji from this pull request. |
ecto
added a commit
that referenced
this pull request
Jul 8, 2026
…METADATA - adopt resolveArtifactRefAsync/getArtifactFileAsync in the ordering and kerf-intent paths (durable artifact store from #510) - keep main's post-connect maybeAutoDock retry with our typed hostContext - TOOL_METADATA entries for get_sim_replay/get_sim_version/get_order_feed - tool-surface fixture regenerated from the merged surface (770/772 green) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ecto
added a commit
that referenced
this pull request
Jul 8, 2026
Collided with 033_mcp_artifacts from #510 (duplicate schema_migrations version key in the Verify migrations check). Comment references updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ecto
added a commit
that referenced
this pull request
Jul 9, 2026
…rail, receipt-gated ordering (#511) * feat(mcp): agent-native factory — sim replay viewer, order dock, live kerf quote rail, receipt-gated ordering Implements docs/agent-native-factory.md (M0–M4 + kerf rail): - Physics replay in the inline viewer: create_robot_env mounts the canvas with a play/pause/scrub/speed transport bar, reward sparkline, and live-follow; trajectories ride a 600-step ring buffer and per-step instance transforms come from kernel solveForwardKinematics (FK stays single-source-of-truth in Rust; euler order aligned to the kernel's Rz·Ry·Rx). get_preview_glb gains an instances mode with per-instance named nodes, node TRS, and shared meshes. - Order dock: app-only get_order_feed renders the fused vcad+kerf lifecycle (six-stop chips, pricing-basis pills, approval banner with vcad.io deep link, receipt/evidence chips, wallet footer). The iframe stays read-only for money. - Live kerf rail: mirrored @kerf/core contract + byte-exact intentHash, production KerfClient (timeouts, bearer auth, degrade-to-estimate), SendCutSend adapter with vendor-native SCS config + single-DXF bytes_base64 wire contract. Scripted-mode rehearsal prices are honestly downgraded to estimate basis; only live runs carry "quoted". - Protocol-native approval: authorize_spend issues URL-mode elicitation when the client supports it (capability-gated, no flag); decline revokes via compare-and-set. - Receipt-gated ordering (always-on, fail-closed when evidence exists): place_order re-verifies clearance claims and the quote doc_hash before any debit, with durable refusals (EXPIRED on geometry drift, persisted receipt_status "violated") and consumed-authz replay finalization. - M0: deprecated-surface tripwire (roots/sampling/logging never adopted). - Supabase migration 033 (orders.fab_artifact/receipt_status/ kerf_intent_hash, quotes.kerf_intent_hash/kerf_job_id) — not pushed. Hardened by a 133-agent adversarial review (42 findings triaged; all confirmed money-path bugs fixed, incl. signed-in session hydration before the gates, event-history preservation, and column-skew-only tolerant retries). Tests: 743 passed / 2 skipped across 54 files (baseline 706/46); companion kerf HTTP API on ecto/kerf feat/http-quote-api. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(supabase): renumber fabricate enrichment migration 033 -> 034 Collided with 033_mcp_artifacts from #510 (duplicate schema_migrations version key in the Verify migrations check). Comment references updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This branch was successfully deployed
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.
What
Every artifact handle on the hosted deploy was a dead end, from two stacked bugs:
The
/artifactsroute was never wired into the deployment. The Build Output API routing table inservices/mcp/build.shhas explicit routes for/mcp,/health,/live/*,/oauth/*— but not/artifacts/(.*). The path never reached the function; Vercel's platform answered 403.handleArtifactRequestwas dead code in prod. Added the route (GET/HEAD/OPTIONS → the mcp function).The store was a per-instance in-memory
Map(documented as such in the file header, with this exact fix named as the follow-up). Even with the route, a handle minted byexport_gerberon one serverless instance was unreadable everywhere else:quote_manufacturingrejected afab_artifact_idminted 2 minutes earlier on the same deployment ("Unknown or expired"), and cold instances 404'd downloads.The store now mirrors
session-store.tsexactly: the in-memory registry becomes a warm-instance cache in front of a durable, service-role-onlymcp_artifactstable (migration 033), gated by the sameSUPABASE_URL/SUPABASE_SERVICE_ROLE_KEYenv and using the same injectablesessionFetchtest seam.storeArtifactcaches, kicks off a best-effort persist, andentry.tsawaitsflushArtifacts()alongsideflushTelemetry()so the write survives the instance freeze./artifactsroute andquote_manufacturing/place_orderusegetArtifactAsync/resolveArtifactRefAsync. Expired durable rows read as absent and are lazily deleted;cleanup_expired_mcp_artifacts()sweeps never-read rows (same cron follow-up as migration 025's session sweep).server_infoand/healthnow reportartifact_store: supabase|in-memorynext tosession_store.Field repro
TMP-1 motor-board session on prod
f6d9258:export_gerberreturnedart_LWz9vu0YPGcqL5HAwith a 24 h expiry; the URL 403'd from two independent networks, andquote_manufacturingcouldn't bind the freshly re-mintedart_FeCf9KhrUVklPMAQtwo minutes after creation. The session had to fall back toexport_kicad's inline path to deliver fab files at all.Tests
artifact-store.test.ts: cold-start hydrate (registry cleared between write and read — the shipped failure), route serving from the durable row, cross-instance quote binding, warm-cache rehydration (no double fetch), expiry-as-absent + lazy delete, and degraded-write fallback.@vcad/mcpsuite: 721 passed / 47 files.@vcad/mcpbuild green.Deploy notes
supabase/migrations/033_mcp_artifacts.sqlbefore/with the deploy (table is additive; no existing reads change until it exists — persist failures just log).🤖 Generated with Claude Code
https://claude.ai/code/session_018uDJYmC9cSQhB3tPwNUtnD
Generated by Claude Code