Repository navigation
fix(runtime)!: the action activation door answers a name two packages hold with 409 naming both, as the execution door does, not 404 "has no declaration" - #33399
Conversation
…o-holder refusal 409, not 404 Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cyWLSMyXZGc6dwhCAu
…es its row in both directions (CONTROL) Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cyWLSMyXZGc6dwhCAu
…TO 409 (narrowing) Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cyWLSMyXZGc6dwhCAu
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 30 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 9fba295ec7dfa248e7bdb603a6c18eae5ea455c4 && git checkout 9fba295ec7dfa248e7bdb603a6c18eae5ea455c4
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e0ce1b523c9cdb625211fc866593f57aee9223ee 2bf90b60626483f25bb4d68c9c35f77e4eb064d1 && git checkout -B drift-repro e0ce1b523c9cdb625211fc866593f57aee9223ee && git merge --no-ff 2bf90b60626483f25bb4d68c9c35f77e4eb064d1
node scripts/docs-audit/affected-docs.mjs --json e0ce1b523c9cdb625211fc866593f57aee9223ee |
|
domain:server#5 · os-elon-musk · session_016WV4cyWLSMyXZGc6dwhCAu |
|
Contract-review-claim: session_016WV4d1PGptBW3QRQNmy5e6 · head 2bf90b6 · 2026-11-14T19:21:54Z review#3. |
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgmentADR-0150 D3 says ambiguity is reported, never answered as absence. A Implemented-by: VERDICT: PASS |
|
domain:server#5 · os-elon-musk · session_016WV4cyWLSMyXZGc6dwhCAu
|
Fixes #32689
Clause-②: yes (narrowing) —
POST /api/v1/actions/_activation/OBJECT/NAMEfor a name two packages hold on the route's key (both object-less withOBJECT=global, or both bound toOBJECT): FROM404 RESOURCE_NOT_FOUND"has no declaration — there is nothing to switch" TO409 RESOURCE_CONFLICTnaming every holder (error.details.candidates, each with itspackageId), forenabled: trueandfalse, nothing written. That is the execution door's answer for the same name. One holder and an undeclared name answer as before.Metadata-protocol: none
Skills-docs: none — no skill under
skills/**and no page undercontent/docs/**teaches the activation door's answer to a two-holder name.维护者速读
POST /actions/_activation/:object/:action现在答 409 并列出两个持有包,和执行门一致;之前答 404「has no declaration」。packages/runtime/src/domains/actions.ts第 3 步新增的一行declaration.refusal判断;packages/runtime/src/domains/action-activation-unpackaged.test.ts的两条 NARROWING 与三条 CONTROL;.changeset/32689-activation-door-two-holder-refusal.md。pnpm --filter @objectstack/runtime exec vitest run --maxWorkers=2 src/domains/action-activation-unpackaged.test.ts,预期 23/23 通过。What changes
handleActionActivationWritestep 3 (packages/runtime/src/domains/actions.ts): after thedegradedbranch and before the!declaration.actionbranch, a reading that carriesdeclaration.refusalis answered withrefusedNameAnswer(deps, declaration.refusal)— the shared ADR-0150 D3/D5 helper fromcatalog-activation.ts,409with{ code: 'RESOURCE_CONFLICT', candidates }. That is the envelope the execution door answers the same refusal with (admitResolvedActioninaction-dispatch.ts:409,standardErrorCodeForHttpStatus(409), the samecandidates). The docblock's step 3 names the new answer.action-execution.tsis not edited;resolveRouteActionDeclarationis consumed as it is onmain.packages/runtime/src/domains/action-activation-unpackaged.test.ts, same realMetadataManageroverMemoryLoaderand the real activation projection and store):shared_ping,POST /_activation/global/shared_pingwithenabledfalse and true:409 RESOURCE_CONFLICT,candidatesholdingcom.acme.alphaandcom.acme.beta, both named in the body, no ledger row. (Was a404pin.)shared_pingbound to ONE object, both directions:409 RESOURCE_CONFLICTnaming both packages, no row. (Was a404pin.)404 RESOURCE_NOT_FOUND"has no declaration", no row.shared_ping, each direction on a fresh ledger:200, one row['shared_ping', 'com.acme.alpha', enabled]. (New.)200and its row (existing, unchanged)..changeset/32689-activation-door-two-holder-refusal.md:@objectstack/runtimepatch, BREAKING FROM → TO, ADR-0087 dispositionnot-required (no-migration-prescription).Tests
All at
2bf90b6062, closure built first (pnpm --filter '@objectstack/runtime^...' build, exit 0), under the shared verify lock.pnpm --filter @objectstack/runtime testpnpm --filter @objectstack/runtime typechecktest-typecheck-debt.json, unchanged)pnpm --filter @objectstack/runtime exec vitest run --maxWorkers=2 src/domains/action-activation-unpackaged.test.tspnpm exec eslint --no-inline-configon the two changed source filesAcceptance notes
404are unchanged.degraded(503) is still read first, as before; the refusal is read before "no declaration". Step 4's same-name-on-two-objects refusal (409naming the objects) is unchanged; the dogfood pin for it (action-activation-door-live.dogfood.test.ts, one package, two objects) never reaches the new branch.404and switched nothing either. The changeset says so.@objectstack/runtimechange is ~50 packages (ui, cli, examples, dogfood). Locally: runtime's own suite and the full dogfood battery (owed by the narrowing arm, which boots the composed stack). The rest is declared to CI: no exported symbol or type changes, only this door's HTTP answer for a two-holder name.Verification
Ablation of the refusal branch (one ablation, both NARROWING pins)
scripts/ablation-replace.mjs --deleteonpackages/runtime/src/domains/actions.ts, anchorif (declaration.refusal) return refusedNameAnswer(deps, declaration.refusal);. On disk: anchor 1 → 0, blob956da40f97d1→962849a03558. The test resolves the door through the relative../http-dispatcher.jsimport, so the source is what runs; no build leg.Tests 2 failed | 21 passed (23). The two NARROWING pins failed with the BASE answer,{"code":"RESOURCE_NOT_FOUND","message":"Action 'shared_ping' on 'global' has no declaration — there is nothing to switch off. …","httpStatus":404}(and the same oncrm_contact). Every CONTROL stayed green.git checkout HEAD -- ABSOLUTE_PATH; blob after restore956da40f97d1= HEAD blob,git diff HEADempty, anchor count 1. Restored run: 23/23.mainhas not changedactions.tssince this branch's based4fbb4dbb, so the ablated door ismain's door: the pins are red onmain.Gates (derived at 2bf90b6, 66 commands)
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 66 commands, each run with stdin from/dev/null, exit code captured before any pipe.check:dts-closure,check:lean-entry-closure,check:sourcemap-no-sources-content,check:type-check-debt) answeredPREREQUISITE NOT METbefore the closure build and exit 0 after it.check:dual-build-cjs-loads, reason: it reads every workspace package'sdist/(packages/adapters/hono,packages/apps/*, …), which a runtime closure build does not produce. CI'sLint & Repo Gatesis the reading.--ranreconciliation:66 derived, 65 run, 1 NOT-MEASURED, 0 UNRUN.Dogfood battery (owed by the narrowing arm)
pnpm --filter '@objectstack/dogfood^...' buildandpnpm --filter @objectstack/dogfood testunder the verify lock, no shard, no path, on2bf90b6062. Started when this PR opened; its VERDICT line and counts are in the os-dev-report comment on runtime: the action activation door drops the declaration's ambiguity refusal, so two packages' same-named global action answers 404 'has no declaration' instead of 409 naming the holders (ADR-0150) #32689.Generated by Claude Code