Skip to content

fix(mcp): artifact URLs actually resolve — route /artifacts + durable store - #510

Merged
ecto merged 1 commit into
mainfrom
claude/artifact-store-durable
Jul 8, 2026
Merged

ecto merged 1 commit into
mainfrom
claude/artifact-store-durable

Conversation

@ecto

@ecto ecto commented Jul 8, 2026

Copy link
Copy Markdown
Owner

What

Every artifact handle on the hosted deploy was a dead end, from two stacked bugs:

  1. The /artifacts route was never wired into the deployment. The Build Output API routing table in services/mcp/build.sh has explicit routes for /mcp, /health, /live/*, /oauth/* — but not /artifacts/(.*). The path never reached the function; Vercel's platform answered 403. handleArtifactRequest was dead code in prod. Added the route (GET/HEAD/OPTIONS → the mcp function).

  2. 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 by export_gerber on one serverless instance was unreadable everywhere else: quote_manufacturing rejected a fab_artifact_id minted 2 minutes earlier on the same deployment ("Unknown or expired"), and cold instances 404'd downloads.

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 test seam.

  • 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_manufacturing/place_order use getArtifactAsync / 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).
  • Observable: server_info and /health now report artifact_store: supabase|in-memory next to session_store.
  • Degrades safely: a durable outage falls back to warm-cache-only (today's behavior) — never a tool failure.

Field repro

TMP-1 motor-board session on prod f6d9258: export_gerber returned art_LWz9vu0YPGcqL5HA with a 24 h expiry; the URL 403'd from two independent networks, and quote_manufacturing couldn't bind the freshly re-minted art_FeCf9KhrUVklPMAQ two minutes after creation. The session had to fall back to export_kicad's inline path to deliver fab files at all.

Tests

  • 6 new durable-store cases in 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.
  • Full @vcad/mcp suite: 721 passed / 47 files.
  • Routing heredoc JSON validated; @vcad/mcp build green.

Deploy notes

  • Apply supabase/migrations/033_mcp_artifacts.sql before/with the deploy (table is additive; no existing reads change until it exists — persist failures just log).
  • No env changes: durability keys off the same Supabase env sessions already use.

🤖 Generated with Claude Code

https://claude.ai/code/session_018uDJYmC9cSQhB3tPwNUtnD


Generated by Claude Code

… 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
@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
vcad-mcp Building Building Preview, Comment Jul 8, 2026 10:47pm
3 Skipped Deployments
Project Deployment Actions Updated (UTC)
mecheval Ignored Ignored Jul 8, 2026 10:47pm
vcad Ignored Ignored Jul 8, 2026 10:47pm
vcad-docs Ignored Ignored Jul 8, 2026 10:47pm

Request Review

@ecto
ecto merged commit 2f6a4dc into main Jul 8, 2026
15 checks passed
@chojiai

chojiai Bot commented Jul 8, 2026

Copy link
Copy Markdown

What shipped

Artifact 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

1 active deployment
Preview – vcad-mcp — d8f14fa9 Deployed Jul 8, 2026 by vercel[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.

2 participants