Repository navigation
fix(metadata-protocol): the save door stores an action as its ActionSchema parse, so requiresFeature / requiresMembershipReach are lowered into visible - #26880
Conversation
…chema parse, so requiresFeature / requiresMembershipReach are lowered into visible An object's own actions and a standalone action item were judged by ActionSchema and then stored as written: the gate keys rode along raw and no visible was stored, so the console offered the button to everyone. The door now stores the parse it already ran (withParsedStoredActions), the shape the code-package registration door stores since #25981. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5kBygwShyUWpFansVP
…e in the pinned ledger Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5kBygwShyUWpFansVP
📓 Docs Drift CheckThis PR changes 1 package(s): 5 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 — 11 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 193949654708c630ac0f2ab0aa30c5bd6190302a && git checkout 193949654708c630ac0f2ab0aa30c5bd6190302a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1a8b7ebef23aefacc78f5ed0f1850ba9fe6f59dc 91803f6ff4b06680284c5fa8b4bc88c5e27fb507 && git checkout -B drift-repro 1a8b7ebef23aefacc78f5ed0f1850ba9fe6f59dc && git merge --no-ff 91803f6ff4b06680284c5fa8b4bc88c5e27fb507
node scripts/docs-audit/affected-docs.mjs --json 1a8b7ebef23aefacc78f5ed0f1850ba9fe6f59dc
|
席位核对ACCEPT · The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」.
Generated by Claude Code |
Requested by Leehom · project thread
Fixes #26723
Clause-②: no
Metadata-protocol: none
维护者速读
/meta保存对象、单独保存动作,草稿与发布两种模式)现在把动作按ActionSchema解析后的结果存盘,requiresFeature/requiresMembershipReach会被并入visible。visible,控制台只读visible,所以普通成员也能看到只给管理员或特定功能开放的按钮。packages/metadata-protocol/src/protocol-stored-action-parse.ts(新 helper);protocol.ts保存入口里新增的那一行。pnpm --filter @objectstack/metadata-protocol exec vitest run src/protocol.stored-action-gate-lowering.test.ts,应见 7 passed。What changes
saveMetaItem(packages/metadata-protocol) judges anobjectoractionbody by its spec schema. It then stored the request body as written, apart from the operator and form-section grafts.ActionSchema's parse is what lowersrequiresMembershipReachandrequiresFeatureintovisible, so a stored action kept both gate keys raw and had novisible. The console readsvisiblealone, so the button was offered to every user. This was measured onorigin/main4c29f38447by the red leg below.The door now stores the parse it already ran.
withParsedStoredActions(new,protocol-stored-action-parse.ts) is called once, right after the existing grafts:object: the object's ownactionsarray becomes the parsed array (ObjectSchema'sactionsisActionSchema[]). Every other key of the body stays as written.action: the body is the parsed action.actionsarray, is unchanged.This is the same parse the code-package registration door has stored since #25981 (
parsePackageActionin@objectstack/objectql). The lowering is not re-implemented here: the values are read off the parse tree the door already computed. The parse is idempotent over its own output, so a GET then PUT round trip stores the same bytes (pinned).The schema block runs before the draft/active split, so both modes are covered by one hunk.
The action endpoint (measured, ruled)
Asked by the triage line: does
POST /api/v1/actions/:object/:actionenforce the gate? No.requiredPermissions(actionPermissionError,packages/runtime/src/action-execution.ts:832), the activation switch and the action type.visibleis deliberately unread there (contract point 6,action-execution.ts:2484). It is pinned byaction-declarative-update.test.ts"point 6", run locally: 2 passed.ui/action.zod.tsdocblocks onvisibleandrequiresMembershipReach).type: 'api'platform-objects identity action calling a better-auth endpoint, whose own door is the authority.POST /actionsanswers 400 forapiandurlactions.The seat ruled option A on the card: no dispatch enforcement and no spec change. This PR fixes the save door only.
Tests
New:
packages/metadata-protocol/src/protocol.stored-action-gate-lowering.test.ts, through the realsaveMetaItem, reading thesys_metadatarow the save wrote:requiresFeature: 'ssoEnforced'and one carryingrequiresMembershipReach: 'delete_organization', is stored with neither key. Each action has its loweredvisible, deep-equal to whatObjectSchema.creategives the same declaration.visible, and the object's other keys are stored as written.actionskey is stored without one.actionsaved in draft and in publish mode is stored lowered, deep-equal toActionSchema.parse.Red first, ablation
protocol.tswritten back to the base blob4ccf8acedb(nowithParsedStoredActionsreference):Tests 5 failed | 2 passed (7). The two greens are the idempotency and no-actionscontrols. Restored: blob92f1deab22equals HEAD, andgit diff HEADis empty.scripts/ablation-replace.mjs --delete, anchorrequest.item = withParsedStoredActions(singularType, request.item, parsed.data);, hit once): blob92f1deab22to2ecf252721, anchor 1 to 0. Result:Tests 5 failed | 2 passed (7). The tool restored it:blob == HEAD (92f1deab22f0) and git diff HEAD is empty. The test imports./protocol.jsfrom source, so there is no dist leg.Acceptance notes
{ dialect, source }. That is the shape a packaged action is already served in.ui/**readsrequiresFeatureorrequiresMembershipReachat runtime.ui/packages/core/src/actions/ActionRunner.ts:469is a type declaration only, and the renderers readvisible.visibleand no gate key, as a packaged action already does.protocol.ts, and its hunks are far from this one: they sit at lines 216 to 11001, and this hunk is at about line 20950.Verification
Head
91803f6f.Package suites and typecheck
pnpm --filter @objectstack/metadata-protocol test:Test Files 271 passed | 4 skipped (275),Tests 21669 passed | 26 skipped (21695), exit 0.typecheckexit 0,buildexit 0. Run ata55b8f4b; the only later commit is the engine-double ledger JSON.@objectstack/runtime^...and@objectstack/rest^...closures with this fix inmetadata-protocol/dist:http-dispatcher,action-activation-unpackaged,field-error-code-catalog,mcp-package-object-discovery,actions-disabled-package,meta-overlay-read-your-writes,stored-metadata-body-boundary.pin,stored-metadata-reader-contexts.pin. Result:Test Files 8 passed (8),Tests 544 passed (544).rest.test.ts. Result:Tests 215 passed (215).eslint --no-inline-configover the three touchedmetadata-protocolfiles: exit 0.test:repoand the full runtime and rest suites are left to CI (Test Core).Gates
dispatch-gates.mjs --commandson this diff derives 69 families, and all 69 were run at91803f6f.--ranreconciles them as 68 run and 1 NOT-MEASURED.check:dual-build-cjs-loads, which exited 3 (PREREQUISITE NOT MET: it needs every package'sdist/, and this worktree has only the closures built). CI runs it.check:engine-double-contractfirst named the new test's double. The ledger was updated with--writein the second commit, and the family is green.check-plugin-teardown-shape --self-testexited 3 on the shallow clone. After fetching its control commit it passed (48 cases).🤖 Generated with Claude Code
https://claude.ai/code/session_01FEkQ5kBygwShyUWpFansVP
Generated by Claude Code