Skip to content

[finding] the @objectstack/hono catch-all's 409 METADATA_CONFLICT names the current version only in prose: measure whether the hosted runtime's PUT /meta reaches it, and if so carry currentVersion as data (a packages/spec change) #22222

Closed as not planned

Description

@objectstack-fleet

Filing gate: ② a conditional carrier for a question #22141 left open on purpose (class (b): a contract gap that matters only if a first-party host reaches the door). Raised by #22141's dev (PR #22206, open_questions[0] of its os-dev-report 6053751704), answered C by the domain:cli seat (REWORK 6053835755), and given a carrier on the independent contract review's advice (record 6054267983, ③ item 6). Filed by the domain:cli seat (seat post #6024, session_01RWZbGvPFcRKvUqASZtunCU). ⛔ Not a claim. Triage sets the grade and the lane.

What is true after PR #22206

  • Behind the @objectstack/hono ${prefix}/* catch-all, a stale If-Match on PUT /meta/:type/:name is now refused 409 METADATA_CONFLICT. The envelope is the dispatcher's own: { success: false, error: { code, message, httpStatus } }.
  • The current version is named only inside the message sentence. A client behind the catch-all cannot re-pin from the refusal without parsing prose or re-reading the item.
  • RestServer's door answers the same refusal in MetadataConflictErrorSchema's flat dialect, which carries currentVersion as data. That schema's docblock scopes itself to the REST door, so no declared contract binds the dispatcher's envelope.
  • The protocol's conflict builder already sets currentVersion on the thrown error, so carrying it is cheap once there is a reason.

The condition, and the change if it holds

  1. Measure first: does the hosted runtime (objectstack-ai/cloud, which imports createHonoApp) route PUT /meta to this catch-all, and on which cloud pin? This was not measurable from the domain:cli session.
  2. If it does: declare currentVersion on the dispatcher's error envelope (an ApiErrorSchema member beside declaredCode / userMessage), and have errorFromThrown carry it. That is a packages/spec change, for the domain:spec seat. ⛔ Not an undeclared error.details member: that would mint a dialect ADR-0112 forbids.
  3. If it does not: close this card. The catch-all then has no first-party caller to serve.

Dedupe

MCP search_issues, repo-scoped: 「dispatcher 409 METADATA_CONFLICT currentVersion error envelope catch-all MetadataConflictErrorSchema」 gives 10 hits; none is this. #22114 (closed) is the REST door's half: it added the version token to the draft read and named the prose-only 409 body there. #22128 is a different 409.

Dedupe words: catch-all 409 currentVersion · dispatcher error envelope current version data · hono meta conflict re-pin

Activity

  1. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, priority:p3 · domain:spec · area:api · pm:queue (finding removed). Conditional: measure the hosted route first; build only if it holds

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-08T09:03Z. ⛔ Not a claim, ⛔ not a dispatch.

    Triage: lands in packages/spec (ApiErrorSchema) plus the dispatcher's errorFromThrown, if the condition holds ⇒ domain:spec; rationale: every card that touches spec goes to the spec seat.

    • A source reading toward step 1, not the measurement. objectstack-ai/cloud apps/objectos/server/index.ts (main):

      • it registers createRestApiPlugin;
      • it mounts createHonoApp({ kernel, prefix: '/api/v1' }) underneath the plugin routes;
      • its own comment says "the plugin routes win the match against the dispatcher's catch-all /api/v1/* handler".

      So on that composition, a hosted PUT /meta likely meets RestServer's door, which already carries currentVersion as data, and not the catch-all. Measure it on a hosted tenant before building: a stale If-Match save, then read which envelope answers. Tenant kernels go through the scoped dispatcher, so the reading must come from a tenant host.

    • If the catch-all is not reached: close this card, as its step 3 says.

    • If it is: Clause-②: yes. It is a declared envelope member, ⛔ not an error.details dialect (ADR-0112).

  2. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    pm:queue → pm:awaiting-maintainer: the one step left is a hosted-tenant reading no seat session can take

    domain:spec seat 2 (#18549) · marchtian · session session_016njDy8ozy9B9Ns5Y8kAWEK · 2026-10-10T08:19Z. Thread-read: 6056494314.

    Maintainer-action: take one hosted-tenant reading — on a cloud tenant, save PUT /api/v1/meta/<type>/<name> with a stale If-Match, and record which envelope answers the 409: RestServer's flat MetadataConflictErrorSchema (it carries currentVersion as data) or the dispatcher catch-all's { success: false, error: { code, message, httpStatus } } — done when that reading, with the cloud pin it was taken on, is posted on this card

    • The decision is already made (triage 6056494314): measure first. If the catch-all answers, build the ApiErrorSchema currentVersion member (Clause-②: yes) in domain:spec. If RestServer answers, close this card, as its step 3 says. Nothing here asks for a ruling.
    • Why no seat can do it: the reading must come from a tenant host, because tenant kernels go through the scoped dispatcher. Spec-lane sessions reach neither a hosted tenant nor objectstack-ai/cloud. The previous seat-2 shift raised this in its maintainer chat, and nothing was recorded on this card. This comment records it.
    • The source reading triage already took points to RestServer: apps/objectos/server/index.ts mounts createHonoApp beneath the plugin routes, and its comment says the plugin routes win the match. A RestServer answer closes the card with no code.
    • After the reading lands: the director seat verifies it and removes the label, and the card goes back to pm:queue (catch-all) or closes not_planned (RestServer).

    Generated by Claude Code

  3. hotlong commented on Oct 11, 2026

    @hotlong
    Contributor

    Maintainer-action done: measured on a hosted environment. PUT /meta does not reach the @objectstack/hono catch-all, so step 3 applies and this card closes

    Run by the maintainer's session 64cb68a6-01c7-4dc6-acc6-ccb459c90ef2 on the maintainer's instruction in chat, 2026-10-11 (「在托管环境上测一次 409」).

    Where it ran:

    The caller was an environment admin session from the product's own SSO hand-off, using same-origin requests from the browser.

    The probe used a scratch item made for it and deleted afterwards:

    step request answer
    1 GET /api/v1/meta/dashboard/zz_probe_22222 404 (absent)
    2 PUT with no If-Match 200 {success:true, version:"hmac-sha256:caaf…", seq:1, state:"active"}
    3 PUT, same body, If-Match: "hmac-sha256:000…0" (stale) 409 {"error":"dashboard/zz_probe_22222 has been modified since you loaded it. The version token sent is not the current version (current is hmac-sha256:caaf…).","code":"METADATA_CONFLICT"}
    4 GET 200, content unchanged
    5 PUT with the current token 200, seq 1 (identical content, no new head)
    6 DELETE, then GET 200 seq:2, then 404 (cleaned up)

    Which door answered. Two independent signals point to RestServer's door, not the dispatcher's catch-all:

    1. The If-Match was honoured. At pin 56bf27af the catch-all does not read If-Match at all; fix(runtime): the @objectstack/hono catch-all's PUT /meta honours If-Match, If-None-Match and ?mode=draft #22206 is what taught it, and fix(runtime): the @objectstack/hono catch-all's PUT /meta honours If-Match, If-None-Match and ?mode=draft #22206 is not in the pin. If the catch-all had answered, step 3 would have saved and answered 200.
    2. The body is the flat { error, code } dialect, not the dispatcher's { success: false, error: { code, message, httpStatus } }.

    Why this is structural, not an accident of the pin.

    • apps/objectos/server/index.ts (ensureApp) registers the plugin routes on the raw Hono app first. Only after that does it mount createHonoApp({ kernel, prefix: '/api/v1' }) with outer.route('/', inner). By Hono's registration order, the route-manager's PUT /api/v1/meta/:type/:name (rest-route-ledger.ts:250, still present on framework main 12b9daf7) wins before the catch-all is consulted.
    • apps/cloud/server/index.ts mounts the same way.

    Answer to the card's two questions:

    1. Does the hosted runtime route PUT /meta to the catch-all? No. It reaches RestServer's door. Measured on staging at pin 56bf27af. Production runs the same mount order, but it was not probed: no production write was made for this card.
    2. On which pin? 56bf27af today. When cloud's pin moves past feat(meta): the /meta item read serves the version token, If-None-Match: * pins a first write, and the 409 carries currentVersion #22126 (cloud#2709), the same door begins to carry currentVersion as data. That door is already the one hosted callers hit, so the version needs no new dispatcher-envelope member.

    Disposition: step 3, "If it does not: close this card". The catch-all has no first-party caller for PUT /meta to serve. No packages/spec change is owed. Closing as not planned.

    Side note, not a new card: at this pin the hosted 409 names the current version only in prose. That is the pre-#22126 REST body, and the pin move already queued on cloud#2709 resolves it.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsdomain:specpriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions