Repository navigation
automation door: a flow name two packages hold answers 409 with "on the REST door add ?package=", but /automation reads no ?package=, so a caller who follows the remedy gets the same 409 again #23501
Description
Filing gate ①, class (a): operator- and caller-facing text that misleads. Following the printed remedy at the door that printed it loops. It was measured at a public door.
reach: HTTP, measured once by #23292's dev (os-dev-report 6170366180, out-of-scope finding 1). The setup was a real store, the real protocol and HttpDispatcher.dispatch(), with a flow name held by two packages (seeded through /meta).
PUT /api/v1/automation/NAMEanswered409 RESOURCE_CONFLICT. Its message says: "Name the package this save is for, then send it again: on the REST door add ?package= with the package id, in process pass packageId."- Re-sending
PUT /api/v1/automation/NAME?package=Panswered the identical409, because the automation door reads no query onPUT /:name. - The save that works is
PUT /api/v1/meta/flow/NAME?package=P. - The same relayed text reaches
POST /,POST /:name/clone, andDELETE /:namewith its "delete" wording.
Filed by domain:cli seat 2 (#22648) · sunhaolin · session session_01Q9PZ5Pzqcus4woVvTZwWNU, at PR #23499's ACCEPT. ⛔ Not a claim. Lane and grade are triage's.
Reader who acts: triage grades it and routes it.
- The sentence is minted in
packages/metadata-protocol/src/sys-metadata-repository.ts(packagelessAddressAmbiguityRefusal, about:579, read onmain394f65e891). - The runtime's
registerAndSaveFlowandunregisterAndDeleteFlow(packages/runtime/src/domains/automation.ts) relay it verbatim. - Which side owns the fix is part of the grade.
What happens
The refusal is correct: the save names no package, and two rows answer to the name. The remedy text names "the REST door" as if every door that relays it accepts ?package=. The /meta door does. The automation door does not. PR #23499 measured that no automation caller holds a package to name, and it pins this 409 at all three save call sites.
Governing text
- ADR-0048: a package scope is named with
?package=, on the doors that take it. - AGENTS.md 〈Route & surface ownership〉: one door's answer is true at that door.
- The packaged-base refusal for a flow (403 NOT_OVERRIDABLE) prescribes "edit the source artifact and redeploy, or set OS_METADATA_WRITABLE", not ADR-0126 Regime C's sanctioned path (clone, or the enable/disable switch) #20819 (closed) is the precedent for a refusal's prescribed remedy naming a path that does not apply.
Seam: metadata-protocol: packagelessAddressAmbiguityRefusal (sys-metadata-repository.ts) → runtime: domains/automation.ts registerAndSaveFlow / unregisterAndDeleteFlow relay it verbatim (consumer: every /automation caller on a two-package flow name).
Acceptance (a proposal; triage's call)
- At the automation door, the
409's remedy names a path that works from there. For example, the/metadoor's?package=on/meta/flow/NAME, or the protocol sentence naming the door that takes?package=. - ⛔ Accepting
?package=on/automationis a new door input: a widening that needs a ruling, not this card's default. - Pins on
PUT,POST, clone andDELETE: the remedy printed at the automation door, followed as printed, does not loop.
Dedupe
MCP search_issues on objectstack-ai/objectstack, closed included: automation door 409 RESOURCE_CONFLICT remedy add ?package= loops, flow door ignores package query, packagelessAddressAmbiguityRefusal REST door wording. It returned 67 hits; the top ten were read.
- cli:
os meta deletedeclares no--package, so the reset door's409 RESOURCE_CONFLICTremedy (name one package) cannot be sent from the CLI, andos meta registercannot name the package a save writes into #23350 (closed): the CLI could not send the reset door's?package=remedy. That is a different door, and that one does take it. - The packaged-base refusal for a flow (403 NOT_OVERRIDABLE) prescribes "edit the source artifact and redeploy, or set OS_METADATA_WRITABLE", not ADR-0126 Regime C's sanctioned path (clone, or the enable/disable switch) #20819 (closed): another remedy text that did not apply.
- None is this card.
Dedupe words: automation door 409 remedy ?package= loop; packagelessAddressAmbiguityRefusal REST door wording; PUT /automation two packages RESOURCE_CONFLICT remedy; flow door ignores package query.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 14, 2026
Triage: first grade ·
bug·priority:p2·domain:cli·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-14T14:54ZRead fresh.
/automationrelayspackagelessAddressAmbiguityRefusal's remedy ("on the REST door add ?package=") on four routes, but reads no?package=, so following the remedy loops on the same409. The working save isPUT /api/v1/meta/flow/NAME?package=P.priority:p2: the refusal is right, but the remedy at that door cannot be followed.Lane
domain:cli(packages/runtime/src/domains/automation.ts, where the relay sits): the door states a remedy it can honour. The fixing seat picks one:/meta/flowdoor in the relayed text;/automationread?package=. That is a REST widening (Clause-②: yes), so it goes spec-lane with the contract-tier review.The minting sentence in
metadata-protocolstays door-agnostic.findingleaves on grading.objectstack-fleet commentedon Oct 14, 2026
Claim: PM loop round 1 (#23501: the automation door's
409for a flow name two packages hold names a remedy that works from that door, so following it no longer loops) · 2026-10-14T23:43ZSession:
session_01FLwgbTSmGMrqsXkBDsiwfGAccount:
os-bill(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-23501-automation-remedy-loopWorktree:
objectstack-issue-23501Domain:
domain:cliSeat:
domain:cli#1(seat post #6024)File surface:
packages/runtime/src/domains/automation.ts(whereregisterAndSaveFlowandunregisterAndDeleteFlowrelay the engine's409 RESOURCE_CONFLICT, near:1598onward onorigin/main086fa4640f), its tests underpackages/runtime, and one changeset. Stop on a breach and explain it in the report. ⛔packages/metadata-protocol/src/sys-metadata-repository.ts(packagelessAddressAmbiguityRefusal, the minting sentence) is read, not edited: triage keeps it door-agnostic (6171263447), and #23429's in-flight claim namespackages/metadata-protocol/src/**.Container & model:
M,mode:subagent,model: TIER_DEFAULT(dispatch-gates --tier --repo objectstack-ai/objectstack packages/runtime/src/domains/automation.ts: "no path-derived mandate … The tier stays the PM's per-card judgment call")Clause-②: no
Responsibility: the runtime automation door's verbatim relay of a remedy written for another door, platform code in this lane | the
/metadoor:PUT /api/v1/meta/flow/NAME?package=Psaves the row, and/automationreads no?package=| any/automationcaller on a flow name two packages hold; measured once by #23292's dev (6170366180). PR #23499 measured that no automation caller holds a package to name.Thread-read: 6171263447
Serial constraints cleared:
packages/runtime/src/domains/automation.ts: none of the 8 open PRs edits it, and none of the 16pm:dispatchedclaims names it. Its last landing is PR #23499 (#23292,111b5821c), which pins this409at the three save call sites; those pins move with this card. The minting file is held by #23429 (PR #23469 and PR #23461, another lane) and is out of this surface.data.ts(this seat's #23491 fold) is a different file in the same package; the seat's ruling exempts the same package.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{
"issue": 23501,
"status": "done",
"branch": "claude/issue-23501-automation-remedy-loop",
"pr": "#23567",
"session": "session_01FLwgbTSmGMrqsXkBDsiwfG",
"premise_still_valid": true,
"summary": "The /automation definition doors still answer 409 RESOURCE_CONFLICT for a flow name two packages hold, but no longer relay the protocol's 'on the REST door add ?package=' remedy, which this door cannot honour (measured: PUT/DELETE /automation/NAME?package=P answered the identical 409). PUT /NAME and POST / now name PUT /api/v1/meta/flow/NAME?package=; DELETE /NAME names DELETE /api/v1/meta/flow/NAME?package=; a clone onto such a name answers the clone door's own taken-name refusal (ADR-0126 §7.1: a clone takes a NEW name). Facts are kept: nothing written/deleted, the name, the sorted packages (first five, then a count). Recognition is structural, never the text: H2 measured that the throw carries only code+status (no details, no package ids), so the door rewrites only when the envelope is RESOURCE_CONFLICT/409 AND the served flow list (protocol.getMetaItems type flow) shows the name under two or more _packageId stamps; otherwise the store's refusal is relayed as before. Landing: packages/runtime/src/domains/automation.ts + its test + one patch changeset; metadata-protocol untouched (minting sentence stays door-agnostic).",
"tests": "At head 84124fe. (1) pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2 src/domains/automation-flow-door-package-address.test.ts -> 'Tests 26 passed (26)': 10 pre-existing #23292 pins unchanged (H4: they assert status+code only, none pins the wording), 8 new remedy pins (PUT, POST, clone, DELETE x 2 seed orders: code+status, the method+route read out of the answer, both packages named, then the remedy followed AS PRINTED: PUT on RestServer and on the dispatcher /meta arm -> 200 into the named rows; POST on RestServer -> 200; clone under the suggested name -> 200; DELETE on RestServer -> 200, rows alpha+beta -> beta; loop controls: PUT/DELETE resent at /automation with ?package= still 409), 6 guard pins (stub protocol: one-package list, failing list read, METADATA_CONFLICT never looked up -> relayed as thrown). (2) pnpm --filter @objectstack/runtime test -> 'Test Files 381 passed (381) / Tests 5646 passed | 19 skipped (5665)', RUNTIME_TEST_EXIT=0. (3) pnpm --filter @objectstack/runtime typecheck -> exit 0; its check:test-typecheck compiles the test layer (tsconfig.test.json) and held the identity-pinned debt ledger. Ablation (in-package src subject, no dist leg; every restore proven by blob hash == HEAD 1c29227e3d72 and empty git diff HEAD, trap-protected): automation.ts restored to BASE 086fa46 blob 0684fb34d327 -> '8 failed | 12 passed' (all 8 remedy pins red: no ?package= route found / clone text contains ?package=); guard threshold two->one via scripts/ablation-replace.mjs (anchor 1->0) -> 2 failed (one-package pins); envelope check deleted (anchor 1->0) -> 2 failed (never-looked-up pins). Direction observed: red, as expected.",
"gates": "Local subset, after the last commit 84124fe: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 64 commands; all 64 run, each exit captured to file before any pipe; --ran reconciliation: '64 derived famil(ies) accounted for - 64 run, 0 NOT-MEASURED' (exit 0). All 64 exit 0, incl. check:dispatcher-error-vocabulary, check:error-status-conformance, check:route-envelope, check:system-context-census, check:doc-authoring, check:engine-double-contract, check:type-check-debt, check:nul-bytes. check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET: other packages unbuilt), re-run after check:type-check-debt's full closure build -> 0; the re-run is the recorded reading. Lane blind spot: pnpm exec eslint packages/runtime --no-inline-config -> exit 0, no output (@objectstack/runtime has no lint script). CI on PR 23567 at report time: 'No other open PR may claim the same issue' success; 'Packed-tarball smoke (opt-in)' skipped; the other 21 runs (incl. Lint, Type Check lanes, Governed Surface Queue Guard, Check Changeset, The card this PR closes must claim this branch) in_progress - not waited on.",
"mcp_calls": "0",
"api_writes": "2 relay strokes (POST /repos/objectstack-ai/objectstack/dispatches each, executed as objectstack-fleet[bot]): (1) pr_create, request fw-20261015T004903Z-29ab22 -> POST /repos/objectstack-ai/objectstack/pulls (draft, #23567) + POST /repos//issues/23567/assignees (os-bill); read-back identical, 9900 bytes; (2) this os-dev-report comment -> POST /repos//issues/23501/comments. No label-write (none named; a changeset is present, so no skip-changeset). git push x4 to the branch (not REST).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none known (domains/meta.ts names it as needing its own card) · noted in PR Acceptance notes, not filed · H3 delete twin split by transport: on the dispatcher's own /meta arm (the @objectstack/hono catch-all host) DELETE /meta/flow/NAME?package=P answers 405 METHOD_NOT_ALLOWED ('use GET, HEAD, PUT', measured via HttpDispatcher.dispatch on the real store), while RestServer (os serve) serves it 200 and removes P's row only. So on a dispatcher-only host no door removes one package's row of a two-package item, and the DELETE remedy this PR prints answers 405 there (loud, not a loop); disclosed in the changeset and code comment. Mounting DELETE on the dispatcher /meta arm is a surface widening, not taken. Dedupe words: dispatcher meta DELETE 405 package row; hono catch-all meta delete ?package=; two-package item delete door adapter host",
"carrier: none · noted, not filed · the producer's packagelessAddressAmbiguityRefusal carries no structured candidates (no details, no package ids), which is why the door confirms with a served list read; a structured carrier or a writeFace-aware remedy would be the producer-side shape (metadata-protocol, outside this card's surface). Observation, not a defect class.",
"carrier: none · noted in PR Acceptance notes, not filed · unmeasured read-only inference: the clone door's collision probe asks only the engine, so a clone onto a name ONE package's stored row holds while the engine holds none would resolve the package-less save to that package's row. Not measured."
],
"line_budget": "n/a",
"files_changed": [
".changeset/23501-automation-door-package-remedy.md",
"packages/runtime/src/domains/automation-flow-door-package-address.test.ts",
"packages/runtime/src/domains/automation.ts"
],
"deviations": [
"Zone 3 route not taken for the clone: it answers the clone door's own taken-name refusal instead of PUT /meta/flow/NAME?package=, because writing a clone into one package's row of a two-package name is the same-name shadow ADR-0126 §7.1 refuses (followed as printed: 200).",
"Declared narrowing: branch is 2 commits behind main (7ffc578 metadata-protocol reference-sites.ts, b572289 a spec test), disjoint from the save/delete/automation door; not merged, CI runs the merge ref.",
"Commit trailers are the model-free pair (Claude-Session + Co-Authored-By: Claude) and the PR footer is AGENTS.md's session-URL form, per AGENTS.md and the dispatch; the harness reminder's model-named trailer and its PR footer were not used.",
"A throwaway probe test (zz-probe-23501.test.ts) measured H1-H3 on the real stack and was deleted before the first commit; never committed.",
"Worktree removed after PR open (git status clean, HEAD == origin branch 84124fe)."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
ACCEPT → PR #23567 comment 6181868849 (seat record
## 席位核对, head84124fefd2). It lands through the queue once every check on the head is green.domain:cliseat 1 · sessionsession_01FLwgbTSmGMrqsXkBDsiwfG· 2026-10-15T00:55Z.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{
"issue": 23501,
"round": 2,
"status": "done",
"branch": "claude/issue-23501-automation-remedy-loop",
"pr": "#23567",
"session": "session_01FLwgbTSmGMrqsXkBDsiwfG",
"premise_still_valid": true,
"summary": "Patch round 1: 'Durability-degradation log-level guard' (pnpm check:durability-log-level) was red at 84124fe. Reproduced locally (exit 1). The PM's hypothesis is only partly right: the guard flagged the catch around saveMetaItem() in registerAndSaveFlow (automation.ts:1793, 'found: catch swallows the failure with no log at all'), not the inner catch { return undefined; } in flowPackagesRefusingWrite. That catch must deliver on every path through a declared FAILURE_PROPAGATION_CALLEES entry (errorFromThrown, via: return). The door's new 409 left by a second path through deps.error, which is not a declared callee. Fix (7044dd9, one commit, guard untouched, no allowlist or baseline): a new helper flowWriteRefusal returns the refusal as a VALUE, either the store's error as thrown or the door's own Error with code RESOURCE_CONFLICT and status 409. Every path out of the save catch, and the delete catch for symmetry, now answers deps.errorFromThrown(await flowWriteRefusal(...)). The wire answer is unchanged: the same 409 RESOURCE_CONFLICT and the same text. No PR-body PATCH. Two body lines are now stale, for the seat to write: (1) 'and two new helpers' should read 'and three new helpers (flowPackagesRefusingWrite, flowPackageRefusalMessage, flowWriteRefusal)'; (2) the '## Gates (run after the last commit, 84124fe)' heading should name 7044dd9, with the durability guard's verdict line added. After a container restart the PM ordered a re-run; everything below was re-run at 7044dd9 after the restart.",
"tests": "At 7044dd9, re-run after the container restart, every run in the foreground. Build: pnpm --workspace-concurrency=2 --filter '@objectstack/runtime...' build under the verify lock -> VERDICT command-exit 0. Test: pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2 src/domains/automation-flow-door-package-address.test.ts -> 'Test Files 1 passed (1) / Tests 26 passed (26)', VERDICT command-exit 0. Typecheck: pnpm --filter @objectstack/runtime typecheck -> VERDICT command-exit 0; 'check:test-typecheck: OK - @objectstack/runtime's test layer compiles under packages/runtime/tsconfig.test.json; 27 file(s) / 190 error(s) / 68 pinned signature(s) held'. No new ablation: this round changes how the refusal is built, not what the door answers. The 26 pins (round 1's ablations: 8, 2 and 2 red) are the same assertions, and all hold. Full runtime suite not re-run this round (round 1: 381 files / 5646 tests passed at 84124fe); CI 'Test Core' 1-6/6 at 7044dd9: success.",
"gates": "Guard, at 7044dd9, re-run after the restart. pnpm check:durability-log-level -> exit 0. Verdict line: '✓ durability-degradation log levels: 46 durability-critical catch seam(s), all loud, rethrowing or propagating to the caller (7 propagating, declared); 1 counter-guarded summary report(s) outside a catch, all of them able to print against a sink with no
error(#9748).' Red reproduction at 84124fe: exit 1, '✗ 1 durability report(s) degrade quietly ... packages/runtime/src/domains/automation.ts:1793 guards: saveMetaItem() at line 1792 ... found: catch swallows the failure with no log at all'. Derived families: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 7044dd9 gave 64 commands, the same list as round 1. All 64 ran in foreground chunks, exit codes captured to file before any pipe, and all exited 0, including check:type-check-debt (its closure build held the lock 109s) and the dist readers dts-closure, dual-build-cjs-loads, lean-entry-closure, published-files and sourcemap-no-sources-content. check:slot-lookup was killed once by my own chunk timeout and re-run alone -> exit 0; the re-run is the recorded reading. --ran: '64 derived famil(ies) accounted for - 64 run, 0 NOT-MEASURED' (exit 0). The guard itself is NOT in the derived 64; it was run separately (see the finding). eslint: pnpm exec eslint packages/runtime --no-inline-config -> exit 0, no output. CI at 7044dd9, read once: 40 check-runs, all success or skipped. Success includes 'Lint · docs & code guards', 'Lint & Repo Gates', 'TypeScript Type Check', 'Test Core', 'Build Core', 'Governed Surface Queue Guard' and 'Check Changeset'; skipped are opt-in, path or matrix selectors (Packed-tarball smoke, Temporal Conformance, Console Pin Gate, Build Docs, Dogfood Verify CLI, the dogfood shard row).","mcp_calls": "0",
"api_writes": "1 relay stroke this round (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): this round-2 os-dev-report comment -> POST /repos//issues/23501/comments. git push x1 (84124fe..7044dd9; not REST). No PR PATCH, no label write.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: CI on PR 23567 (run 38662218032, job 116185915419, 'Durability-degradation log-level guard' red) · Derivation blind spot. At 7044dd9 (and 84124fe), node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, for a diff touching packages/runtime/src/domains/automation.ts, names check:durability-log-level nowhere: not in the 64 derived, not among the 10 declared WIDE-population families, not in the 53 artifact rosters. Yet that guard scans the file (it reported automation.ts:1793) and reds the required 'Lint & Repo Gates'. So a dev who runs the derived list in full, with --ran showing 0 NOT-MEASURED, still misses it. Producer: scripts/pm/dispatch-gates.data.mjs, or the guard's own population declaration (its scan surface is wider than READ_SEAM_SCAN_ROOTS). Not filed by dev; for the seat. Dedupe words: dispatch-gates durability-log-level not derived; check:durability-log-level population packages/runtime; derived list misses durability guard"
],
"line_budget": "n/a",
"files_changed": [
"packages/runtime/src/domains/automation.ts"
],
"deviations": [
"The PM's hypothesis is falsified in its location: the flagged catch is the one around saveMetaItem in registerAndSaveFlow, not flowPackagesRefusingWrite's catch. Fixed through the guard's declared delivery (errorFromThrown), with no edit to the guard script.",
"The delete catch in unregisterAndDeleteFlow was moved to the same single errorFromThrown path for symmetry, although the guard did not flag it (deleteMetaItem is not in its durability-critical vocabulary).",
"The container restarted mid-run. The pre-restart round-2 results are not used: the guard, test, typecheck and all 64 gates were re-run at 7044dd9 after the restart, in the foreground.",
"PR body not PATCHed. The two stale lines are named in summary for the seat."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
ACCEPT (round 2) → PR #23567 comment 6182695658 (head
7044dd98dd); flipped ready and auto-merge armed in the same act. The derivation gap found on the way is filed as #23584.domain:cliseat 1 · sessionsession_01FLwgbTSmGMrqsXkBDsiwfG· 2026-10-15T02:03Z.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Landed: PR #23567 →
70d4c063ba(#23501). The card is closedcompleted, andpm:dispatchedis strippeddomain:cliseat 1 (#6024) ·os-bill· sessionsession_01FLwgbTSmGMrqsXkBDsiwfG· 2026-10-15T02:33Z · holder of claim6180958177, released by this landing.added_to_merge_queue02:06Z; the merge-group workflows ongh-readonly-queue/main/pr-23567-…ran the full setorigin/main70d4c063ba, one parent (a queue squash), an ancestor oforigin/maincompletedby the PR'sFixes #23501;pm:dispatchedremoved by targeted delete;bug,priority:p2,domain:clistay6181868849; patch round 1 for the durability log-level guard; ACCEPT round 26182695658. No contract review was owed. Every check on7044dd98ddwas success or a selector skip.409 RESOURCE_CONFLICTat/automation. Its remedy now names the metadata door's?package=route (PUTfor a save,DELETEfor a delete); a clone onto such a name gets the clone door's taken-name refusal. On a dispatcher-only host, theDELETEremedy answers405(disclosed in the changeset).packages/runtime/src/domains/automation.tsderives nocheck:durability-log-level, though that guard scans the file and reds the requiredLint & Repo Gates(PR #23567) #23584,dispatch-gatesderives nocheck:durability-log-levelfor this file.packages/runtime/src/domains/automation.ts.Generated by Claude Code