Skip to content

docs(ui): the flow face of recordLoadDenied is populated since #15168 (#15303) - #15583

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-15303-ui-actions-flow-face-populated
Sep 4, 2026
Merged

docs(ui): the flow face of recordLoadDenied is populated since #15168 (#15303)#15583
baozhoutao merged 1 commit into
mainfrom
claude/issue-15303-ui-actions-flow-face-populated

Conversation

@claude

@claude claude Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #15303

What changed

One paragraph in content/docs/ui/actions.mdx — the Flow actions paragraph under "Authorization inside an action". It still carried the honesty clause the contract's TSDoc carried before #15168:

On the flow face the key is declared but not yet populated: the flow dispatcher does not pass the signal into the run's context yet, so until that lands a flow run never sees it and a guard on it is inert (never true), never wrong.

That sentence is false on main. It is replaced by a transcription of the sentence that landed on the contract under #15168 (packages/spec/src/contracts/automation-service.ts:56), adapted to docs voice by naming the two doors — a docs reader has no other way to resolve "both doors", and the page's audience is app authors rather than spec readers:

On the flow face the key is populated since #15168: dispatchFlowAction takes the producer's load outcome and spreads the signal into the context it hands automation.execute, on both doors — the REST /actions endpoint and the MCP run_action bridge — so a flow run receives this key exactly when a handler would.

The surrounding runAs: 'user' / runAs: 'system' prose and the ctx.record.id table are untouched. Diff: 1 file, 6 insertions, 5 deletions (5 of the 6 inserted lines are the rewrapped tail of the same paragraph).

The direction of the defect is the inverted form of the Prime Directive #10 corollary: not a doc advertising a capability the runtime does not deliver, but a doc denying one it now does — an author reading it would not write the runAs: 'system' guard the two-card sequence exists to enable, and no gate reports it.

Premise verification — the runtime half IS on main, on BOTH doors

Verified on a worktree cut from origin/main at 0c5e97368, not from the card.

One flow dispatcher, and it spreads the signalpackages/runtime/src/action-execution.ts, inside dispatchFlowAction, in the object handed to automation.execute (line 904):

    const result: any = await automation.execute(action.target, {
        record,
        // [#15168] The caller-scope load's verdict, a SIBLING of `record` —
        // never a key on it, and spread so it is ABSENT rather than `false`
        // when nothing was refused ...
        ...actionRecordLoadSignal(subject),

Door 1 — the REST POST /api/v1/actions/... route, packages/runtime/src/domains/actions.ts:732:

                result = await actionExec.dispatchFlowAction(deps, _context, actionDef, {
                    objectName,
                    // [#15168] `subject`, not `record` — the shared door derives
                    // the flow context's `record` AND its `recordLoadDenied`
                    // sibling from the same load outcome ...
                    subject,

Door 2 — the MCP run_action bridge, packages/runtime/src/action-execution.ts:1758:

        // [#15168] `subject`, not `record`: the shared door derives the flow
        // context's `record` AND its `recordLoadDenied` sibling from the one
        // load outcome, so this door cannot forward the row without the verdict.
        const result = await dispatchFlowAction(deps, requestContext, action, { objectName, subject, params, recordId, ec, envId });

Both doors pass the whole ActionSubjectRecordLoad (record and recordLoadDenied, action-execution.ts:1290) into the one dispatcher, and actionRecordLoadSignal (action-execution.ts:1373) turns that verdict into { recordLoadDenied: true } or {}. So the flow face is populated, on both doors, exactly when a handler face would be. ⇒ premise_still_valid: true.

Census — every other copy of the claim in content/docs/**

Three greps over the whole docs corpus (not yet populated, declared but not, recordLoadDenied):

Hit Verdict
content/docs/ui/actions.mdx:382 The defect. Fixed here.
content/docs/ui/actions.mdx:341,345,350,357,373,380 The handler/action-body face and the ctx.record.id table — true, and fenced out by the card. Untouched.
content/docs/automation/hook-bodies.mdx:241 Verified action-body scoped — "An action body runs elevated ... The key that distinguishes the two is ctx.recordLoadDenied". It says nothing about the flow face and stays true. No change, as the card said.
content/docs/permissions/permissions-matrix.mdx:155 "declared but not enforced" about sharing-rule recipient types. Unrelated statement, currently true. Out of scope, no defect.
content/docs/protocol/objectql/query-syntax.mdx:912, content/docs/data-modeling/queries.mdx:374, content/docs/api/wire-format.mdx:413 501 NOT_IMPLEMENTED / datasource-not-connected error strings. Same three words, unrelated subject. Out of scope.
content/docs/releases/v17.mdx:4796 Historical release note for #14244 (the declaration). States only that the key "gives the flow the caller-scope record-load signal" — not the retired claim. Release-owned; never edited in a code PR. No change.

⇒ Exactly one copy of the falsehood existed, and it is the one fixed.

Verification

Gate family derived mechanically, never hand-listed: node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack, exit 0, 37 runnable families. The whole union re-run at the final commit 368722879, clean tree. Every exit code captured by redirect-then-capture, never after a pipe.

Command Exit Verdict line
dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack 0 37 command(s) — 25 pnpm, 12 direct node (30 matched by path, 0 by change KIND, 7 declared WHOLE-TREE ...)
All 37 derived families 0 (37/37) see below
node scripts/docs-audit/check-affected-docs.mjs 0 card's claim confirmed: green over this change
node scripts/docs-audit/check-drift-comment.mjs 0 check-drift-comment: 56 cases pass across 5 fixture diff(s).
pnpm check:nul-bytes 0 check-nul-bytes: OK (scanned 7551 text file(s) ... no raw ASCII control bytes)
pnpm check:doc-anchors 0 306 internal #fragment link(s) across 410 source file(s) all resolve to a real heading
pnpm --filter @objectstack/spec run check:docs 0 green
pnpm --filter @objectstack/spec run check:skill-examples 0 257 prose examples type-check across 3 surface(s)
node scripts/check-doc-frontmatter.mjs (+ --self-test) 0, 0 green
node scripts/check-docs-section-name.mjs (+ --self-test) 0, 0 green
pnpm --filter "@objectstack/spec..." build (under the shared lock) 0 os-verify-lock: VERDICT command-exit 0 · held the lock 148s
turbo run build --filter=@objectstack/{formula,lint,client-react,client} (under the lock) 0 os-verify-lock: VERDICT command-exit 0 · held the lock 372s · 34 successful, 34 total

Both gates the card names run green over this change, and that is the point rather than a reassurance: check:affected-docs and check:drift-comment are exactly the two gates that would be expected to carry a stale-docs claim, and neither does. No gate carries this defect in either direction — it could only ever be found and fixed by reading.

Three families were NOT MEASURED on the first pass and then measured. check:doc-formula-expressions and check:doc-security-posture exited 3 (Nothing was measured: this gate exited before running a single check) and check:skill-examples exited 1 on the same prerequisite class — all three refusing because a package they read was unbuilt, none of them a finding. After the builds above, all three re-ran at exit 0. They are reported as measured only on the second run.

Grep control (the change is asserted in both directions, not just forwards):

branch  368722879 : grep -n "not yet populated" content/docs/ui/actions.mdx  -> exit 1, 0 hits
origin/main       : same grep over `git show origin/main:...`                -> exit 0, 1 hit
                    382:On the flow face the key is **declared but not yet populated**: the flow
branch  368722879 : grep -n "populated\*\* since #15168" ...                 -> exit 0, 1 hit at :382

Staleness discharged. A re-derivation on the branch warned that origin/main had moved and that four files the derivation reads had changed across the range. Rather than trust it, the family set was re-derived in a throwaway worktree detached at the current origin/main (954cb0bbf) with this change applied: byte-identical, 37 families. No family appears or disappears at current main.

Not runnable locally (CI only), named for completeness — 3 families whose argv takes a value from the workflow ($RUNNER_TEMP, matrix.shard), matched via ci.yml's content/** filter on the Test Core shards: check-cross-package-test-inputs.mjs --union-into ..., check-shard-attestation.mjs --emit ..., check-test-completeness.mjs ....

Changeset

None. content/docs/** publishes nothing from any package, so this PR declares no release of its own; changeset-check in pr-automation.yml has no path exemption, and its declared instrument for that case is the skip-changeset label, applied to this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk


Generated by Claude Code

… (#15303)

The "Flow actions" paragraph under "Authorization inside an action" still
carried the honesty clause the contract's TSDoc carried before #15168: that
on the flow face `recordLoadDenied` is "declared but not yet populated" and a
guard on it is "inert (never `true`), never wrong".

That is false on `main`. `dispatchFlowAction` spreads
`actionRecordLoadSignal(subject)` into the context it hands
`automation.execute` (`packages/runtime/src/action-execution.ts:904`), and
both action doors reach it through that one function — the REST `/actions`
route (`packages/runtime/src/domains/actions.ts:732`) and the MCP
`run_action` bridge (`packages/runtime/src/action-execution.ts:1758`), each
passing the whole `ActionSubjectRecordLoad` rather than a bare record.

The direction of the defect is the inverted form of the Prime Directive #10
corollary: not a doc advertising a capability the runtime does not deliver,
but a doc denying one it now does — so an author reading it would not write
the `runAs: 'system'` guard the two-card sequence exists to enable, and no
gate reports it.

The replacement transcribes the sentence that landed on the contract
(`packages/spec/src/contracts/automation-service.ts:56`), adapted to docs
voice by naming the two doors, which that page's readers otherwise cannot
resolve. The surrounding `runAs: 'user'` / `runAs: 'system'` prose and the
`ctx.record.id` table are untouched.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation labels Sep 4, 2026
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 4, 2026
@claude

claude Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

ACCEPT — PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk.

Reviewed against the three-dot diff origin/main...claude/issue-15303-ui-actions-flow-face-populated (head 368722879; one file, content/docs/ui/actions.mdx, +6/−5), not the self-report.

  • The only change is the Flow-actions sentence at :382: "declared but not yet populated … never wrong" → "populated since runtime: carry the caller-scope record-load signal into a flow action's context — dispatchFlowAction spreads actionRecordLoadSignal on both doors (the runtime half of #14244) #15168: dispatchFlowAction takes the producer's load outcome and spreads the signal into the context it hands automation.execute, on both doors — the REST /actions endpoint and the MCP run_action bridge — so a flow run receives this key exactly when a handler would." That is the contract's own sentence (packages/spec/src/contracts/automation-service.ts:56–59, verified on main), with "both doors" named for a docs reader — no third wording.
  • The runAs: 'user' / runAs: 'system' prose and the ctx.record.id table are untouched; content/docs/automation/hook-bodies.mdx:241 stays (action-body scoped).
  • Premise verified by the dev on the tree, not the card: action-execution.ts:904 spreads ...actionRecordLoadSignal(subject), reached from the REST route (domains/actions.ts:732) and the MCP bridge (action-execution.ts:1758).

Dev's verification quoted: derived family 37 commands, all exit 0 at 368722879, re-derived byte-identical on a throwaway worktree at current main 954cb0bbf; check-affected-docs.mjs and check-drift-comment.mjs exit 0 (confirming no gate carries this defect — the root pnpm aliases the card named do not exist, EXIT 254, re-run by path); three doc gates first exit 3 / 1 as unbuilt-package refusals, then exit 0 after building; grep control not yet populated 0 hits on the branch, 1 hit on main :382. skip-changeset applied and read back. Three CI-only families named as NOT RUNNABLE LOCALLY.

Flipping ready + enabling auto-merge. Fixes #15303.


Generated by Claude Code

@baozhoutao
baozhoutao marked this pull request as ready for review September 4, 2026 22:28
@baozhoutao
baozhoutao enabled auto-merge September 4, 2026 22:28
@baozhoutao
baozhoutao added this pull request to the merge queue Sep 4, 2026
Merged via the queue into main with commit ea295b1 Sep 4, 2026
37 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-15303-ui-actions-flow-face-populated branch September 4, 2026 23:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs: ui/actions still says the flow face of recordLoadDenied is "declared but not yet populated" — false once the runtime half lands

2 participants