Repository navigation
fix(runtime): a flow started through the REST or action door carries its caller's posture rung beside their positions and permission sets - #28167
Conversation
…its caller's posture rung beside their positions and permission sets The trigger door (buildAutomationContext) and the action door (dispatchFlowAction) forwarded the request's positions, permission sets, organization and locale, but not its posture rung. A run that carries grants is not re-resolved at setup, so it ran with no rung, and a platform administrator's manual run was served the organization's ledger rows only (fail closed). - AutomationContext gains one optional member, posture, typed ExecutionContext['posture'] and documented beside positions and permissions. - Both doors copy ec.posture as-is, absent when the request has none; a request body key cannot name it. - seedRunGrants: when run setup resolves the grants itself, the rung of that resolution replaces a forwarded one, or none is carried. - record-view-auditing.mdx: the door case is no longer "not yet". Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4czNtyUvgvgGC1mdxrU
📓 Docs Drift CheckThis PR changes 3 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 173 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 80b94ca91a3b22ecd1c4c23a3b154281dbe0be20 && git checkout 80b94ca91a3b22ecd1c4c23a3b154281dbe0be20
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a3d24c991f2d793a1600e7f25fb0d5f577ed2fff b53b0297da66c321f28074e3b9d646cdba0c4301 && git checkout -B drift-repro a3d24c991f2d793a1600e7f25fb0d5f577ed2fff && git merge --no-ff b53b0297da66c321f28074e3b9d646cdba0c4301
node scripts/docs-audit/affected-docs.mjs --json a3d24c991f2d793a1600e7f25fb0d5f577ed2fff
|
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL Contract-review-claim: session_016WV4d1Np3hU7RQnA13YZcL · head 0c325aa · 2026-10-30T15:03Z |
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL Contract reviewServed-tier: Inputs: card #28099 (body; triage 6523377748, which grants the cross-domain member; the claim 6523907984; the os-dev report 6525918906), PR #28167 (body, file list, net diff against ① Derived judgmentsEach accept-set or public-surface change the diff implies, named right or wrong:
② Semver level
③ Boundary flags
④ Design judgment
The member holds. It is typed off the kernel's resolved rung rather than a new vocabulary; its docblock names the two setters, the as-is rule, the absent-narrows rule, the re-resolution rule, and the system arm, and every one of those sentences is true of the cited code at this head (①.2-①.8). Its first consumer chain, Attempts to break it:
Findings:
No must-fix. Implemented-by: VERDICT: PASS |
…henever run setup resolves the grants, and the trigger route names every fact the doors forward seedRunGrants drops a forwarded posture up front whenever the context carries no permission sets, then re-seeds it only from a resolution that answers grants; a resolver that rejects, answers nothing or is not wired now leaves the run with no rung instead of the forwarded one. flows.mdx: the trigger route's Identity paragraph lists the posture rung and language beside user, positions, permissions and tenant. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4czNtyUvgvgGC1mdxrU
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL Contract-review-claim: session_016WV4d1Np3hU7RQnA13YZcL · head b53b029 · 2026-10-30T15:31Z |
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL Contract reviewServed-tier: This record is a DELTA review: it supersedes record 6526196044 (PASS on ① Derived judgments
② Semver levelUnchanged from 6526196044 and re-read on this head: ③ Boundary flags
④ Design judgment
The member is unchanged in shape; only its docblock moved, and it moved toward the tree: the sentence "a rung stands only beside the grants it was derived from" is now held in every arm of Findings: none. S1 and S2 of 6526196044 are closed on this head; S2's "and email" clause in that record was wrong and is withdrawn. Implemented-by: VERDICT: PASS |
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL 维护者速读
席位意见:ACCEPT。 席位核对Head-sha:
|
Requested by Elon · project thread
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL
Fixes #28099
Clause-②: yes (widening) —
AutomationContext(@objectstack/spec/contracts) gains one optional member,posture, typedExecutionContext['posture']; the REST trigger door and the action door fill it from the request's resolved execution context, and a run that carries it is served what that rung admits.Metadata-protocol: none
维护者速读
buildAutomationContext)和动作门(dispatchFlowAction)启动的流程,现在把调用者请求上已解析的姿态等级(ec.posture)带进运行,和 positions、permissions 一起带。AutomationContext加一个可选成员posture。posture键仍是流程参数,不能伪造。门只带了 rung、没带 grants 时,运行准备自己解析 grants,并改用那次解析的 rung(没有就删掉)。回滚即 revert,无数据迁移。packages/runtime/src/domains/automation.ts与action-execution.ts各一行;run-resume.ts的seedRunGrants一处;automation-service.ts新成员的文档。pnpm --filter @objectstack/runtime exec vitest run src/flow-run-posture.test.ts(预期 7 passed),以及pnpm --filter @objectstack/service-automation exec vitest run src/runas-user-posture-rung.integration.test.ts(预期 20 passed)。What changes
packages/spec/src/contracts/automation-service.ts.AutomationContext.posture?: ExecutionContext['posture']. It is type-only and needs no new import (ExecutionContextis already imported as a type). It is documented besidepositions/permissions: who sets it (the two doors), that it is the caller's own rung as-is, how run setup treats it, and that a missing rung narrows.packages/runtime/src/domains/automation.ts(buildAutomationContext, which servesPOST /api/v1/automation/:name/trigger, the legacy trigger route and a declaredtype: 'flow'endpoint). One line copiesec.posturewhen it is a non-empty string, the wayec.localeis copied. It is read off the execution context only: a body key namedposturestays a flow param.packages/runtime/src/action-execution.ts(dispatchFlowAction, REST/actionsand MCPrun_action). The same copy, in the context handed toautomation.execute.packages/services/service-automation/src/run-resume.ts(seedRunGrants, one hunk). When run setup resolves the grants itself (the context carries nopermissions), the rung of that resolution replaces a forwarded one, or the forwarded one is deleted when the resolution has none. A door forwards positions and permission sets only when they are non-empty, so a rung can arrive alone. Without this hunk it would stand beside grants it was not derived from. A context that carries its grants keeps its rung as forwarded: setup neither re-derives nor raises it. The engine already carried a context-supplied rung to the data context (resolveRunDataContextreads it throughrunPosture, automation: a runAs:'user' run carries no posture rung, so a platform administrator's flow is served organization ledger rows only #28070), so nothing else in the package changes.content/docs/permissions/record-view-auditing.mdx. The sentence from fix(service-automation): a runAs:'user' run carries its user's posture rung, so a platform administrator's flow is served the ledger's deployment-level rows #28098 that said a door-started run "is not, yet" served the deployment-level rows now says it carries its caller's posture and is served them the same way.packages/runtime/src/flow-run-posture.test.ts(new, the producer half).runas-user-posture-rung.integration.test.tsgains five envelope cells and three real-stack cells (the consumer half)..changeset/28099-door-runs-carry-posture-rung.md. Patch for@objectstack/spec,@objectstack/runtimeand@objectstack/service-automation.Why
Triage
6523377748: 「The REST and action doors forward the caller's own posture rung beside their positions and permission sets. It is never derived from the flow's author, and never raised.」 The claim declares one optionalAutomationContextmember and owesClause-②: yes (widening)and a contract review.The measurement (before the fix, at
origin/main47a8b588)The new pins were run against the unchanged source.
flow-run-posture.test.ts: 5 red, 2 green.buildAutomationContextgiven an execution context withposture: 'PLATFORM_ADMIN'returned a context with noposturekey. Both trigger routes handed the engineposture: undefined, and so diddispatchFlowAction. The flat-body cell was red because the door forwarded no rung at all. The two absence cells were green, as expected.runas-user-posture-rung.integration.test.ts, real stack: the door-shaped envelope for the platform administrator (their own resolved positions and permission sets, no rung, which is what both doors built) ranqa_ledger_readand was served the organization row only. The same administrator's request is served every deployment-level row. That cell is kept as a CONTROL.main: the envelope cells for a carried rung were green before the fix. Only the cell "a forwarded rung without forwarded grants is dropped when the resolution answers none" was red, which is this PR'srun-resume.tshunk.Clause-②
One optional member is added to an exported contract interface. No existing member changes, no producer is required to set it, and a consumer that ignores it behaves as before. So this is
yes (widening). Its only readers are the run's data context (runPosture→resolveRunDataContext), which#28070already wired.Verification
Head
0c325aab(one commitbd3ac11cplus a plain merge oforigin/mainafter PR #28110 landed).flow-run-posture.test.tscovers both doors: the trigger door carries the rung beside the grants, both trigger routes hand it to the engine, the action door carries it, a request with no rung (or an anonymous one) adds no key, and a body cannot name a rung. That is 7 passed.runas-user-posture-rung.integration.test.tshas 20 passed. The envelope cells: a door-started run carries the forwardedPLATFORM_ADMINbeside the forwarded grants; a forwardedMEMBERis not raised to the resolver'sPLATFORM_ADMIN; no forwarded rung gives no rung; a rung forwarded without grants is replaced by the resolved one, or dropped when the resolution has none. The real-stack cells: the administrator's door-started run is served every deployment-level row, as their request is; a member's door-started run is served the organization row only, as their request is; plus the CONTROL above. The automation: a runAs:'user' run carries no posture rung, so a platform administrator's flow is served organization ledger rows only #28070 cells (an engine-resolved run) are unchanged and green.scripts/ablation-replace.mjs, each restored to HEAD:MEMBER: 2 red (the absence cell and the body cell).ctxBody.posturefirst: 2 red.seedRunGrants'selse deleteremoved: 1 red.seedRunGrantsseeds the resolver's rung even over carried grants (re-derivation): 5 red.seedRunGrantsseeds no resolved rung (automation: a runAs:'user' run carries no posture rung, so a platform administrator's flow is served organization ledger rows only #28070's case): 5 red. This shows the engine-resolved pins still bind.pnpm --filter PKG typecheckexits 0 for@objectstack/spec,@objectstack/runtimeand@objectstack/service-automation. The runtime one needs@objectstack/driver-turso's dist in a fresh worktree.src/contracts50 / 469, all exit 0. After the merge: runtime 488 / 8832, service-automation 230 / 2912, and the three typechecks, all exit 0.dispatch-gates --commandsderived 108 families, and the list is the same after the merge. All 108 exit 0.check:skill-examplesandcheck:dual-build-cjs-loadsfirst exited 3 (prerequisite) and passed after a whole-tree build.dispatch-gates --ran: 108 run, 0 NOT-MEASURED.pnpm lintexit 0.pnpm check:route-envelopeexit 0.check-commit-card-trailers --range origin/main..HEADexit 0.Acceptance notes
SeededRunContext.postureinservice-automation/src/runtime-identity.tsnow restates the member it inherits fromAutomationContext(same type,AuthzPosture). It is left as is because the file is outside the claimed surface. A later touch of that file can drop it.content/docs/automation/flows.mdx(the trigger route's Identity paragraph) lists the forwarded identity as "user, positions, permissions, tenant". The list is incomplete but not false. It is outside the claimed surface, so I did not edit it.grep -rn -i "posture" skills/objectstack-automationfinds no text that says which facts a door forwards, so nothing in skills changes.Human Minimum
packages/specholds no runtime logic. (One type-only interface member.)pnpm lintpasses.main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed. (None move for an interface member: the spec gates and the spec build leave the tree clean.)content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.维护者速读block is the maintainer's summary, as on the lane's other PRs.)🤖 Generated with Claude Code
https://claude.ai/code/session_016WV4czNtyUvgvgGC1mdxrU