Repository navigation
fix(runtime)!: REST's /actions door refuses an action on an object whose owning package is disabled 404 OBJECT_PACKAGE_DISABLED, before any handler runs - #25305
Conversation
…s 404 OBJECT_PACKAGE_DISABLED Claude-Session: https://claude.ai/code/session_013txgcKJ9EWEZJhQ41e7kZg Co-authored-by: Claude <noreply@anthropic.com>
…ing package is disabled 404 OBJECT_PACKAGE_DISABLED before any handler runs Claude-Session: https://claude.ai/code/session_013txgcKJ9EWEZJhQ41e7kZg Co-authored-by: Claude <noreply@anthropic.com>
…ctions Claude-Session: https://claude.ai/code/session_013txgcKJ9EWEZJhQ41e7kZg Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 27 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 37cc8788866e86f41f115d6158e885afabd1e7d7 && git checkout 37cc8788866e86f41f115d6158e885afabd1e7d7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5801a4f2c9df6a10f808063d7c3b001b7d4381c7 99d375e84c7ba42293d674b0e06f41d316309d5d && git checkout -B drift-repro 5801a4f2c9df6a10f808063d7c3b001b7d4381c7 && git merge --no-ff 99d375e84c7ba42293d674b0e06f41d316309d5d
node scripts/docs-audit/affected-docs.mjs --json 5801a4f2c9df6a10f808063d7c3b001b7d4381c7
|
|
dogfood_battery · head
Owed once on the final head by the Generated by Claude Code |
Contract reviewServed-tier: ① Derived judgmentsThe gate's position, read in Reuse, as the card's Shape demands. The wire envelope. The door answers through Can it widen? No. The function swallows every throw but the one code; Does an outage of the by-name read change any answer? No. Is every changed answer named? The six Contract rows are each right in direction and in old/new value (the 200 script run, the record-load refusal, the D4 403, the ledger 409, the "no declaration" 404, the flow 503), and rows 4 and 6 are correctly derived by construction from the gate's position. The list is incomplete by three rows of the same class, each a later gate's verdict that the package refusal now precedes for a disabled package's object: (a) the D3 degraded Narrowing. A served answer ( Are the pins real pins? Yes. Check-runs on the head. Every run that ran concluded ② Semver level
The published surface does not move. ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
席位核对ACCEPT ·
Generated by Claude Code |
Requested by Sales · project thread
Fixes #25296
Clause-②: no (narrowing)
Metadata-protocol: none
REST's
POST /api/v1/actions/OBJECT/NAMEnow asks, before it reads the action's declaration, whether the object it names is served. An object whose owning package is disabled is refused404 OBJECT_PACKAGE_DISABLED, with the error the protocol raises for it on the data plane and onGET /api/v1/meta/object/NAME, before any gate, record load or handler. The question is asked through the by-name object read the data verbs' exposure gate already makes (readExposureDeclaration), so neither the package predicate nor the refusal is copied.The defect, measured on base
5801a4f2c9The
/actionsdoor (packages/runtime/src/domains/actions.ts, the one owner of the route) resolved the action's declaration throughresolveRouteActionDeclaration, which readsql.getSchema(OBJECT)and the registry'sactionitems. A disabled package's objects stay registered there, so the declaration resolved and the door dispatched, while every other door treats the package as gone.Real kernels (
ObjectKernel+MetadataPlugin+ObjectQLPluginover a better-sqlite3SqlDriver), in the two shapes the #25120 rig pins (env_localwithcreateStandaloneStack's options, and noenvironmentId), driven through the runtime dispatcher'sdispatch(). The fixture package, registered through themanifestservice during the boot and then disabled (registry.disablePackage): one object carrying a script actionping(its handler registered), a declarative updatemark_done, a script actionadmin_pinggated byrequiredPermissions: ['manage_metadata'], and a top-level declarative updatebound_donebound to the object.manage_metadataGET /data/OBJECT(the data plane, unchanged)404 OBJECT_PACKAGE_DISABLEDPOST /actions/OBJECT/ping200 { pong: true }, the handler ran404 OBJECT_PACKAGE_DISABLEDPOST /actions/OBJECT/ping/r_any200, the handler ran404 OBJECT_PACKAGE_DISABLEDPOST /actions/OBJECT/mark_donewith arecordId404 OBJECT_PACKAGE_DISABLEDPOST /actions/OBJECT/bound_donewith arecordId404 OBJECT_PACKAGE_DISABLEDPOST /actions/OBJECT/admin_ping403(the D4 gate read the declaration)404 OBJECT_PACKAGE_DISABLEDPOST /actions/OBJECT/qa_nowhere404(no code)404 OBJECT_PACKAGE_DISABLEDping, anonymous401401(the anonymous baseline stays first)ping, afterregistry.enablePackage200200The base column is the pins' main leg below.
What changed
packages/runtime/src/action-execution.ts.refuseDisabledPackageObject(deps, requestContext, object, scopeId?): it asksreadExposureDeclaration(the protocol's by-name object read on its execution face, whichrefuseUnexposedDataActionalready asks) and rethrows only the protocol'sOBJECT_PACKAGE_DISABLEDrefusal (isObjectPackageDisabledRefusal). Any other answer is not its to give: an absent object is the declaration resolution's to answer, and so is an unreadable metadata plane (ADR-0110 D3). Not published byindex.ts.packages/runtime/src/domains/actions.ts.handleActionsRequestasks it once the engine is resolved and the activation write door has had its turn, before the declaration resolution; a refusal is answered throughdeps.errorFromThrown(err, 404), the exit every other domain catch uses, so the envelope carries the protocol error's owncodeandstatus. An object-less invocation (POST /actions/global/NAME,POST /actions//NAME) names no object and is not asked about..changeset/25296-rest-actions-disabled-package.md. Apatchfor@objectstack/runtime, with the BREAKING banner and the ADR-0087 dispositionnot-required (no-migration-prescription); both declaration lines are in the body.Contract
Every input whose answer changes, old to new, on
POST /api/v1/actions/OBJECT/NAME[/RECORD]where OBJECT's owning package is disabled, on every kernel shape:200, the handler's result (its body ran,isSystem-elevated as a script body always is). New:404 OBJECT_PACKAGE_DISABLED; the handler is not called.404 OBJECT_PACKAGE_DISABLEDbefore the record load.requiredPermissionsthe caller lacks. Old:403. New:404 OBJECT_PACKAGE_DISABLED. The data plane also answers the package refusal ahead of any permission check, and the refusal names the object, which the data plane already discloses to the same caller.409 ACTION_DISABLED(after D4). New:404 OBJECT_PACKAGE_DISABLED. Not pinned separately: the gate sits ahead of the ledger consult by construction, as it sits ahead of D4 in case 3.404with its prescription. New:404 OBJECT_PACKAGE_DISABLED.flowaction. Old: dispatched through the automation service (or503without one). New:404 OBJECT_PACKAGE_DISABLED. Not pinned separately: the rig has no automation service, and the gate runs before the type dispatch for every type.Unchanged: the anonymous baseline (
401) and the405stay first; an enabled package's actions run as before behind every gate (pinned as the control on both shapes); re-enabling the package restores its actions at once, because nothing records the refusal (pinned); an object-less action; the activation door (POST /actions/_activation/OBJECT/NAME); a host whose protocol has no by-name read (no refusal, as before). The cost is one by-name object read per invocation on an object route, the read the data verbs' gate already makes per call.Pins
packages/runtime/src/domains/actions-disabled-package.test.ts, new, 24 cases on real kernels. Per shape: the ENABLED control (pinganswers200 { pong: true }and its handler runs once;mark_doneandbound_doneupdate a record seeded throughPOST /data/OBJECT,200;admin_pinganswers403; an undeclared name answers404with no package code), and the DISABLED package (precondition:GET /data/OBJECTanswers404 OBJECT_PACKAGE_DISABLED; each of the six invocations of the table answers404 OBJECT_PACKAGE_DISABLEDwith the handler never called and the protocol'sgetData/updateDataand the engine'sexecuteActionnever reached; the anonymous caller still answers401; afterenablePackage,pingruns again). A new file, not the mcp: list_objects / describe_object cannot see any package-installed object (a marketplace app's whole schema) — the HTTP bridge reads the metadata service, not the SchemaRegistry REST serves #25120 rig file, which the mcp: a package's object-less (global) action is invisible to list_actions / run_action on every kernel shape, while REST's POST /api/v1/actions/global/NAME runs it #25297 card holds.Red before, green after. The directions were predicted before each run. Both mutated legs ran the pin file under the verify lock on
99d375e8(this PR's head); the test imports the runtime's source, so nodist/is involved.action-execution.tsanddomains/actions.ts(git restore --source=5801a4f2, worktree only), restored bygit checkout HEAD,git diff HEADemptyrefuseDisabledPackageObjectswallows the refusal too (scripts/ablation-replace.mjs: anchor 1 to 0, blob9e202cd4e8a7to0711946f42aa; restored blob equals HEAD,git diff HEADempty)Local verification
On
99d375e84c, overorigin/main5801a4f2c9. Every heavy run went through the verify lock; each line cites itsVERDICT.pnpm exec turbo run build --filter='@objectstack/dogfood^...' --filter=@objectstack/driver-turso --concurrency=3:VERDICT command-exit 0, 66 of 66 tasks (the runtime's closure included;driver-tursobecause the runtime's test typecheck reads it).pnpm --filter @objectstack/runtime exec vitest run --maxWorkers=2 src/domains/actions-disabled-package.test.ts):VERDICT command-exit 0, 24 passed.@objectstack/runtimeunit suite (pnpm --filter @objectstack/runtime test):VERDICT command-exit 0; Test Files 437 passed (437); Tests 6952 passed, 19 skipped (6971).pnpm --filter @objectstack/runtime typecheck:VERDICT command-exit 0(tsc --noEmit, andcheck:test-typecheckOK). A first run beforedriver-tursowas built answered one TS2307 inorganization-ownership-boot-guard.turso-remote.kernel.test.ts(a missing dist, not this diff).eslint --no-inline-configon the three changed source and test files: exit 0.node scripts/pm/dispatch-gates.mjs --commands: 68 families derived, each run, every oneexit 0. Two first answeredexit 3(prerequisites, not verdicts):check-plugin-teardown-shape --self-testuntil its pinned fixture commit was fetched, andcheck:dual-build-cjs-loadsuntil the eight dists it named were built.check:type-check-debtran under the lock.--ranreconciliation:68 derived, 68 run, 0 NOT-MEASURED (a DERIVED zero — all 68 recorded an exit code and none of them is 3).!and the(narrowing)arm); its counts follow as a PR comment.Acceptance notes
objectName, shipped by the package (type: 'script', its handler registered underglobal), answered200with its handler's result onPOST /actions/global/NAMEandPOST /actions//NAMEafterregistry.disablePackage, on both shapes, whileGET /api/v1/meta/action/NAMEanswered404 RESOURCE_NOT_FOUND. Scratch probe over this PR's rig, not committed. The card's shape is the object route and itsOBJECT_PACKAGE_DISABLEDanswer; an object-less action has no object to refuse, and which answer it owes (the "no declaration"404, as the by-name/metaread answers it, is the likely reading) is the next card's to settle. The mcp: a package's object-less (global) action is invisible to list_actions / run_action on every kernel shape, while REST's POST /api/v1/actions/global/NAME runs it #25297 card moves MCP's object-less actions onto the merged action listing, which omits them.refuseAmbiguousActionActivationwas not needed. The card asked whether this fix needs the activation-ambiguity check to read the served listing. It does not: the fix reads no collection, and the check serves the activation door, which this PR leaves alone. Whether that check sees a package object's actions on anenvironmentIdkernel is unchanged and unmeasured here.claude/project-thread-vlqmr4, the name the session was given, notclaude/issue-25296-…; the Duplicate Fix Guard may say so. The claim (comment 6352124313) names it.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_013txgcKJ9EWEZJhQ41e7kZg