Repository navigation
fix(runtime,core,platform-objects): the catalog activation door switches an environment-authored position or permission set, and keeps ADR-0112's code and status together - #22811
Conversation
…authored position or permission set Claude-Session: https://claude.ai/code/session_014ifLBrKenJNWpsLxhLfhUg Co-authored-by: Claude <noreply@anthropic.com>
…hes an environment-authored position or permission set, and its refusals keep ADR-0112's code and status together Claude-Session: https://claude.ai/code/session_014ifLBrKenJNWpsLxhLfhUg Co-authored-by: Claude <noreply@anthropic.com>
…l, as the shared ledger row's is Claude-Session: https://claude.ai/code/session_014ifLBrKenJNWpsLxhLfhUg Co-authored-by: Claude <noreply@anthropic.com>
…hored items Claude-Session: https://claude.ai/code/session_014ifLBrKenJNWpsLxhLfhUg Co-authored-by: Claude <noreply@anthropic.com>
…d-activation-door-package-less
📓 Docs Drift CheckThis PR changes 4 package(s): 13 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 8 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 48 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 49c16919862432de9263d42db4d67f02d5334c6d && git checkout 49c16919862432de9263d42db4d67f02d5334c6d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 23419bafb742fc13fa3c22b446263bbd6041c78e 1e13bb28fcb63bc5d73fa546b5c75c032957ad0e && git checkout -B drift-repro 23419bafb742fc13fa3c22b446263bbd6041c78e && git merge --no-ff 1e13bb28fcb63bc5d73fa546b5c75c032957ad0e
node scripts/docs-audit/affected-docs.mjs --json 23419bafb742fc13fa3c22b446263bbd6041c78e
|
Contract reviewServed-tier: Stage 2d of #15204 (claim amendment 6106681412, dispatched from F5 of objectui#12089 round 2; dev report 6107337287). Read on GitHub only: the card body and all 88 comments (the 2c records 6104861792 and 6105494730 on #22736, the 2c Landed 6105781131, the stage-1 ACCEPT 6102862135 and the rulings among them), objectui comment 6106225321 (F5, the measured defect) and the seat note 6106420299 on objectui#7611, the PR body, its 9-file list (+341 / −30), the net diff against ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
…riptions cover environment-authored items; the action door's changed answer is named Claude-Session: https://claude.ai/code/session_014ifLBrKenJNWpsLxhLfhUg Co-authored-by: Claude <noreply@anthropic.com>
2d (#22811): the text-only push is checked; queued · epic PM
|
Part of #15204
Clause-②: yes (widening)
Stage 2d of #15204 (claim amendment 6106681412): the security catalog's activation door switches a position or permission set the environment authored, and a ledger write it refuses answers with the status its code means (ADR-0112). This PR does not close the card: the other stages stay open.
The defect (F5 of objectui#12089 round 2), reproduced first
On
origin/main9f5eca5, the dogfood case added here failed before any fix:handleCatalogActivationWritewrotepackageId: entry.packageId ?? ''. An item saved throughPUT /meta/position/:nameorPUT /meta/permission/:namehas no_packageId, so the row carried''.sys_metadata_activation.package_idwasField.text({ required: true }), so the engine refused the row:VALIDATION_FAILED, with no status of its own.catchkept the throwncodebut defaulted the status to503. The code and the status disagreed.Who writes and reads
package_id(measured on9f5eca5)package_idforruntime/src/domains/catalog-activation.ts)entry.packageId ?? ''(the producer of F5)runtime/src/domains/actions.ts:223)_packageId, else''service-automation/src/engine.ts)ObjectStoreMetadataActivationStore.list()(core)packageId; the flow and action hydrators (hydrateFlowActivations,ActionActivationProjection.hydrate) read onlynameandactivereadDisabledCatalogNames(core/src/security/resolve-authz-context.ts)catalog-set-in-effect.ts,last-admin-guard.ts(plugin-auth),sharing-rule-service.ts(plugin-sharing)maina194b4egit grep package_idover the ledger readers: no hitThe row identity is
(metadata_type, name)(the'global'unique index). No reader decides anything frompackage_id.The fix, and why option (i)
(i)
package_idbecomes optional, and "no package" has exactly one spelling.sys_metadata_activation.package_id:requiredis dropped. The field's description now says it is empty when no package ships the item. The column was already nullable on disk: under ADR-0113requiredis a write-time check, and NOT NULL comes only fromstorage.notNull. So no DDL and no migration.MetadataActivationRow.packageIdis optional (@objectstack/core). The store writesnullwhen it is absent. It refusespackageId: ''with aTypeError, before any read or write.list()returnspackageIdonly when the row names a package. So a package-less row has one spelling, and no reader can take an environment item for a package named''.entry.packageIdas it is. The?? ''is gone.catchanswers throughdeps.errorFromThrown(err, 503). That is the dispatcher's one thrown-error rule (resolveThrownHttpError). A record validation refusal becomes400 VALIDATION_FAILED. A hook refusal keeps its own pair (403 PERMISSION_DENIED). An error with no status of its own becomes503 SERVICE_UNAVAILABLE.FlowActivationRow.packageIdis optional to match (type only). Without that,ObjectStoreFlowActivationStore implements FlowActivationStoreno longer type-checks. This was reverse-verified: putting backpackageId: stringgives 10 × TS2345 in service-automation.On the four axes:
package_id. A marker string (ii) would serve no reader.null.?? ''is gone, and the store loudly refuses the''placeholder a caller would reach for. (ii) would make the marker a magic value that an AI-written reader could easily treat as a real package.Pins
catalog-activation-door.test.ts(runtime, unit). New tests:{ metadata_type, name, package_id: null, active };packageIdundefined, beside a packaged control that namescrm;400withVALIDATION_FAILED, and nothing is written.metadata-activation-store.test.ts(core). Inserting and updating with no package writepackage_id: null.''is refused before any engine call.list()reads a package-less row back withpackageIdabsent, beside a packaged row.catalog-activation-door.dogfood.test.ts(real showcase boot). Both items are saved through/meta. Anti-vacuity: the booted catalog reader resolves both with nopackageId. A holder is assigned the position. The test then checks the door, the ledger row (package_idnull), andresolveUserAuthzGrantsdropping and restoring the grants, for the position and for the set.Verification
9f5eca5, the dogfood case was red (503 above). With the fix it is green:Tests 8 passed (8).ablation-replace.mjsandablation-dist-preflight.mjs. Onlyrequired: truewas put back onpackage_id, and@objectstack/platform-objectswas rebuilt (the marker indist/was confirmed). Result: the dogfood case went red as{"code":"VALIDATION_FAILED",...,"httpStatus":400,"details":{"fields":[{"field":"package_id","code":"required",...}]}}. That is now 400, not 503, so the code-and-status fix is shown on a real engine. Restore: the blob is byte-identical to HEAD, the build was redone, and the marker is absent from all 66 dist files.db73adc341:bf3f89075e: 245 files passed and 1 skipped; 1978 tests passed and 9 skipped.dispatch-gates --commandsderived 100 families, and all 100 exited 0 onbf3f89075e.--ranreconciles them: "100 derived famil(ies) accounted for — 100 run, 0 NOT-MEASURED (a DERIVED zero)".Acceptance notes
''. That write was refused before (503VALIDATION_FAILED, from the engine) and is still refused, with nothing written. Its answer is now 503SERVICE_UNAVAILABLE, from the store'sTypeError. Whether an environment-authored action gets a switch is a product question, not decided here. Itscatchhas the same shape as the one this PR fixes, butactions.tsis outside this stage's declared files.manage_metadata, operator-only under a wall) is unchanged ([Decision] 切换后谁能在 Setup 里新建/编辑岗位和权限集:保持今天的权限(持 manage_metadata 者),还是放开给组织管理员(安全边界) #22621 → A). The anchors' 400, the unknown-name 404 and the last-admin 403 are unchanged.Generated by Claude Code