Repository navigation
AutomationContext: an explicit caller-provenance signal for flow params, replacing the headless-screen inference of PR #15787 (#15705 ruling ②) #19846
Description
Activity
- addedpriority:p2Medium: important, M3Medium: important, M3and removed
on Sep 23, 2026 objectstack-fleet commented
on Sep 23, 2026 ContributorAuthorMore actions认领 · 已派发
Claim:
domain:spec#5·os-justin·session_01Sfe5YjBLwB9J3y8fvm2xq1
Branch:claude/issue-19846-automation-caller-provenance
Seat:domain:spec#5
Thread-read: none
Clause-②: yes2026-09-23T14:23Z,座位贴 #19357。写入走 fleet-write 中继,以
objectstack-fleet[bot]落地(route 读作dispatch)。按维护者指示恢复派发,串行:本席此刻在飞的只有 #19791 的一次复核。取卡前置已跑(在认领之前):
domain:spec开着的 P0 为零。- P1 七张都不可取:
- refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write under
singleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204 / feat(spec,services): deployment-level state has no organization column — settings global rung, plumbing objects, the audit ledger, #12699 made total (ADR-0131 D7) #15207 / feat(spec,drivers,objectql,plugin-security):organization_idNOT NULL per cleared table; one predicate for Layer 0 and every driver; bothorWhereNullarms, the__global__sentinel and the #13491 ledger retire (ADR-0131 D1/D8/D9) — protocol 18 #15212 / feat(spec,objectql,cli): the template install mode — a package copied once into the environment ledger, fully editable, refused on shared-database multi-tenant postures (ADR-0131 D6) #15213 / docs(spec): carry the page-title rule into the docs GENERATOR — 225 of 405 pages are emitted, so a hand edit is reverted (split (b) of #12237) #15403 是pm:blocked; - [epic] hotcrm ADR-0130 拆包的上游依赖集 —— 一条代码门禁(#18202) + 一次发版,其余五张是作者体验 #18215 是 epic;
- [finding] the published JSON Schema is WIDER than the zod schema it is generated from wherever a
.refine()carries the rule — an author validating againstpackages/spec/json-schema/**gets a green for metadata the runtime refuses #18670 是pm:awaiting-maintainer。
- refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write under
- 本卡是产品行为修复(该停的屏被跳过),⛔ 不是工具卡或契约卫生卡,NORTH-STAR「优先级」第 3 条不拦。
前提现读(
origin/mainb940f32a56):- 推断仍在:
screen-input-contract.ts:313的callerSupplied,:407读它。 - 先例键
recordLoadDenied在packages/spec/src/contracts/automation-service.ts。 - 引用卡:MCP
run_actionon a screen flow dead-ends: the screen pauses even when every input is bound, andlist_actionsnever surfaces flow input variables #15705 仍开(P1,domain:cli,恢复动词那一半留在那张);service-automation: a caller-provenance check that proves its negative byObject.isis defeated by the durable store, which restores the context throughJSON.parse#15812、MCPrun_actionon a flow action answersok: trueand starts the run on a row the caller cannot read (or that does not exist) — the flow door still turns a denied load into an implicit grant #16370 已completed。 - 22 个开着的 PR 里,没有一个改
service-automation/src/screen*、contracts/automation-service.ts或flows.mdx。
裁决: 维护者对 #15705 的「15705同意」,由分诊席在本卡正文转录:门(动作门与触发门)明说
params里哪些键是调用方给的,屏执行器读这个信号,⛔ 不再猜。信号缺失(旧的生产者)时保留今天的推断,它失败的方向是暂停。Clause-②: yes —— 在
AutomationContext上加一个可选的已发布契约键。交付时本席在 PR 与本卡同时挂needs:contract-review,并做达档复核。
Generated by Claude Code
objectstack-fleet commented
on Sep 23, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 19846, "status": "done", "branch": "claude/issue-19846-automation-caller-provenance", "pr": "https://github.com/objectstack-ai/objectstack/pull/19899", "head_sha": "0155b343ef2c723a5a92f6c1a52b3a67b1db69cd", "session": "session_01Sfe5YjBLwB9J3y8fvm2xq1 (subagent: the dispatching seat's session)", "premise_still_valid": true, "summary": "Premises re-checked on origin/main b940f32a56: callerSupplied at screen-input-contract.ts:313, read at :407; recordLoadDenied is the precedent. Claim 5796604250 names this branch. Implemented: AutomationContext gains optional callerParamKeys?: string[] (packages/spec contracts/automation-service.ts), a list of the keys of the caller's own params, recorded by the door before it seeds anything; [] means the caller supplied nothing, absent means the producer does not say. The action door (dispatchFlowAction, via a new internal flowActionCallerParamKeys that shares ONE row-id key set with seedFlowActionParams) and the trigger door (buildAutomationContext) fill it and leave their row-id keys out (recordId, the camelCase object-id alias, and on the action door the action's recordIdParam) even when the caller's bag names them, because clients mirror the row id into params.recordId (objectui useConsoleActionRuntime flowHandler does) and under recordIdParam (objectui resolveRecordIdParamSeed). judgeHeadlessScreen/callerSupplied reads the list when present and never the inference; a present non-array names nothing (pause); absent keeps the #15787 inference unchanged. subflow and map nodes delete the parent's list from the child context (it describes the parent's bag; inherited, a child screen would be judged against it). Both boundary constructions now pause on the real engine. flows.mdx and the changeset carry the rewritten accepted-cost sentence; flows.mdx's durable-store 'known gap' paragraph was also replaced, because it had been false since 4177ed3a84 (value comparison) and sits in the section this changes. Key name callerParamKeys is my choice (the card ruled only 'explicit, not inferred'); the contract review may rename it. Measured along the way: boundary construction 1 (object-less) is not producible through either shipped door, because loadActionSubjectRecord loads no subject for an object-less key, so the record is the id stub and a non-default recordIdField seeds nothing; it is pinned on the engine anyway.", "context_producers": [ "FILLS - dispatchFlowAction (packages/runtime/src/action-execution.ts): REST POST /api/v1/actions/... (domains/actions.ts) and MCP run_action (invokeBusinessAction)", "FILLS - buildAutomationContext (packages/runtime/src/domains/automation.ts): POST /automation/:name/trigger and legacy POST /automation/trigger/:name (respondToFlowTrigger), declarative type:'flow' endpoint (endpoint-executor.ts executeFlow, reached through dispatcher-plugin runAppEndpointStep); @objectstack/verify flows.run/actions.run and client.automation.trigger go through these REST doors", "ABSENT, so today's inference - RecordChangeTrigger.buildContext (trigger-record-change): params IS the record, pauses", "ABSENT, so today's inference - ScheduleTrigger (trigger-schedule/schedule-trigger.ts): params {jobId, flowName, schedule}, no record (see out_of_scope_findings)", "ABSENT, so today's inference - TimeRelativeTrigger (trigger-schedule/time-relative-trigger.ts): params = record", "ABSENT, so today's inference - ApiTrigger webhook (trigger-api/api-trigger.ts): record = payload, params = copy of payload (open question 1)", "STRIPPED, so today's inference - subflow node child context (service-automation builtin/subflow-node.ts) and map node child context (builtin/map-node.ts): both spread the parent context, now delete callerParamKeys", "ABSENT, so today's inference - direct engine.execute callers: examples/app-showcase/src/security/seed-approval-demo.ts", "NOT A START - engine resume / bubbleToParent / retry reuse the run's own persisted context (the list survives the JSON round trip, pinned)", "Method: grep over packages, examples, apps for AutomationContext, .execute( with a flow name, FlowTrigger implementations and 'automation' service resolvers, on origin/main b940f32a56 then re-read at the final head" ], "tests": "All at head 0155b343ef unless noted. service-automation full: 143 files / 1708 passed (VERDICT command-exit 0). runtime full: 275 files / 3881 passed, 1 skipped (VERDICT command-exit 0); an earlier run at de1bfdd386 had ONE red, endpoint-executor.test.ts 'calls execute(target, buildAutomationContext(body, ctx))' which asserts the whole context with toEqual; updated to name callerParamKeys: ['reason'] (not loosened), commit 31a654209d. spec: src/contracts 46 files / 428 passed plus src/api/automation-api.zod.test.ts and src/system/translation.test.ts (185 passed); full spec suite NOT MEASURED, reason: it outran the foreground cap (killed at 570s by my own timeout, no verdict), declared to CI. New files: spec automation-context-caller-param-keys.pin.test.ts (2 tests + type-level Eq and a @ts-expect-error that compiles under check:test-typecheck), runtime flow-caller-param-keys.test.ts (12), service-automation screen-headless-caller-signal.test.ts (11). Existing pins: screen-headless-satisfaction.test.ts 24/24 and screen-headless-provenance-durable-resume.test.ts 6/6 green and untouched; they build contexts without the signal, so they now pin the absent path. Pins that encode the inference rather than the contract (named in the PR): the three 'only ... can refuse this one' fixtures (the inference's row-id candidates one by one) and 'WIDENING - a caller re-sending a value equal to the row is now indistinguishable, so it pauses' (with the signal that caller continues, pinned in the new file). typecheck spec/runtime/service-automation exit 0 incl. check:test-typecheck (ledgers unchanged). Build spec/runtime/service-automation exit 0; check:generated 15 of 15 up to date. Reverse verification: 'delete childContext.callerParamKeyz' in subflow-node.ts fails tsc with TS2551 \"Did you mean 'callerParamKeys'?\", restored (blob == HEAD). Property 7 probe, before the text change: deleted the accepted-cost sentence and the durable-gap paragraph, ran check:doc-anchors, doc-authoring, corpus-claim-drift, docs-spec-enumerations, role-word, docs-audit-scope, published-readme-links, docs-single-h1, docs-redirects, docs-transcript-drift: all exit 0; git grep for the sentences' distinctive phrases over tests and scripts: zero hits; the only other shipped copies are the released CHANGELOG.md entries of runtime and service-automation (release-owned, not edited). Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths, change set from git) at 0155b343ef derived 109; all 109 run with exit codes recorded; --ran verdict: '109 derived famil(ies) accounted for - 109 run, 0 NOT-MEASURED (a DERIVED zero - all 109 recorded an exit code and none of them is 3)'. check:type-check-debt needed a 570s timeout ('4 ledger entr(ies) re-measured ... none above its recorded number'). Live node scripts/check-issue-citations.mjs exit 0 ('every citation this change adds resolves'); its first run flagged #14864 on a comment I MOVED into flowActionRowIdKeys (deleted card), now cites landing commit 066dd3bd06. eslint narrowed: population read from eslint's own config (the 10 changed TS files are linted; the .md/.mdx files report 'File ignored because no matching configuration was supplied'), --format json: 10 files, 0 errors, 0 warnings; invariance: --print-config shows parserOptions.project null and projectService null, so type-aware linting is off and the diff cannot move a verdict on an untouched file. Narrowing declared: importers of AutomationContext outside the three packages (triggers, verify, lint, metadata-protocol, plugin-approvals, service-messaging) were not re-typechecked locally - the member is optional and additive; CI runs them.", "ablations": [ "A1 signal read removed in callerSupplied (anchor 1 -> 0, blob be5c361d49 -> 13a11da68f): BOUNDARY 1 and BOUNDARY 2 red ('expected undefined to be paused', the run completed), 6 red of 41; the 30 existing screen pins stay green; restored blob == HEAD, git diff HEAD empty", "A2 subflow strip removed: 2 red (child answered from the parent's list; child paused on the inherited []); restored", "A3 map strip removed: 1 red; restored", "A4 action-door fill removed: 5 red in flow-caller-param-keys.test.ts; restored", "A6 trigger-door row-id exclusion removed: 5 red (console launch shape, alias, no-object, both trigger routes); restored", "All via node scripts/ablation-replace.mjs in WRAP mode under os-verify-lock (trap on exit/INT/TERM, absolute path); no rebuild leg owed: each mutated subject is reached through relative source imports (service-automation vitest aliases only platform-objects; runtime tests import action-execution.ts and domains/automation.ts relatively)" ], "ci": "One read at 0155b343ef when this report was written: 32 check-runs, 11 success, 2 skipped, 19 in_progress, none red; required Lint & Repo Gates, Build Core, Temporal Conformance, Governed Surface Queue Guard in_progress; TypeScript Type Check, Test Core, Dogfood Regression Gate not created yet. in_progress, not awaited.", "mcp_calls": "0", "api_writes": "2 - git push (branch, several pushes) is not counted; pr_create via fleet-write relay (POST /repos/objectstack-ai/objectstack/pulls, draft) -> PR 19899, body read back byte-equal; this os-dev-report comment via post-stamped (POST /repos/objectstack-ai/objectstack/issues/19846/comments). No label writes.", "open_questions": [ { "question": "The webhook (api) trigger carries a caller's payload into params. I left it ABSENT (today's inference), reading property 1's 'can carry caller params' as a door with a caller-params channel distinct from the record; this trigger models the payload as the trigger record ('the same authoring surface record-change flows use'). Confirm the reading?", "options": [ "A: absent (shipped) - today a webhook run's screen pauses, since every params key mirrors the record", "B: fill with the payload's keys - a webhook's screen fields become answerable by its payload, a behaviour change no ruling names" ], "recommendation": "A: the card names the action door and the trigger door only, and B changes webhook screen behaviour without a ruling." }, { "question": "Leaving the action's recordIdParam out of the signal narrows one edge: a caller that sends that key with a value other than the row id no longer answers a same-named screen field (the inference counted it). The PR declares plain 'Clause-②: yes' as dispatched, with the edge stated in the changeset. Does the contract review want it kept, and is plain yes the right arm?", "options": [ "A: keep - one rule, the doors' row-id channel never answers a screen; objectui seeds the row id under recordIdParam (resolveRecordIdParamSeed)", "B: exclude only recordId and the alias - no narrowing, but a client that mirrors the row id under recordIdParam would answer the screen with the row id", "C: keep and declare 'Clause-②: yes (narrowing)' with an ADR-0087 disposition" ], "recommendation": "A with plain yes: the edge moves toward pausing and needs a screen field named like the action's own recordIdParam; C is the seat's call if the review reads it as an accept-set narrowing." } ], "out_of_scope_findings": [ "class: a - the schedule trigger (trigger-schedule/src/schedule-trigger.ts) seeds params {jobId, flowName, schedule} with no record and states no callerParamKeys, so the inference reads those platform seeds as caller answers: a screen with a required field named schedule, jobId or flowName is SKIPPED on a scheduled run. Failing probe (one-time, not committed): engine.execute on a type:'schedule' flow with that context and a required 'schedule' screen field -> completed with output {schedule: '0 2 * * *'}, expected paused. No refusal of a screen node in a schedule flow found by grep over packages/lint/src, packages/spec/src/automation and service-automation/src. Candidate fix: the trigger states callerParamKeys: []. Not fixed here: it adds the trigger-schedule package to the verification surface. Dedupe words: schedule trigger screen skipped; callerParamKeys schedule; jobId flowName schedule params screen; headless screen inference scheduled run" ], "cleanup": "Dev worktree /home/user/objectstack-issue-19846: remote branch equal to local 0155b343ef and tree clean, then node_modules removed and git worktree remove (no --force); worktree list and ls confirm it is gone. Posting worktree /home/user/objectstack-issue-19846-post (detached at origin/main 1f0b3419ff, no install) is removed right after this comment is posted. One-time probe file deleted, tree read clean. No dev server or background process started." }
Generated by Claude Code
objectstack-fleet commented
on Sep 23, 2026 ContributorAuthorMore actionsSeat reading of the dev report
5798388167— open questionsdomain:spec#5seat, 2026-09-23T16:16Z. Delivery checked against git: PR #19899, head0155b343ef, 12 files,callerParamKeys?: string[]atpackages/spec/src/contracts/automation-service.ts:188. No governed surface. CI was still running when this was written.- Q1, webhook (
api) trigger: A (absent, today's inference). The ruling on the card names the action door and the trigger door only. Filling the list from a webhook payload would change what a webhook run's screen does, and no ruling asks for that. - Q2, the action's
recordIdParamleft out of the signal: kept as shipped, with plainClause-②: yes, provisionally. Whether that edge is an accept-set narrowing, and so needs(narrowing)and an ADR-0087 disposition, is the at-tier contract review's to judge. The seat does not pre-decide it. - Out-of-scope finding: filed as A scheduled flow run skips a screen whose required field is named
schedule,jobIdorflowName: the schedule trigger's own params are read as the caller's answers #19900. A scheduled run's ownparams(jobId,flowName,schedule) answer a screen. The seat read the seeding atschedule-trigger.ts:806-810. - Next: body rewritten short; both carriers get
needs:contract-review; at-tier review once CI settles.
Generated by Claude Code
- Q1, webhook (
- added 2 commits that reference this issue
on Sep 28, 2026 - added a commit that references this issue
on Oct 7, 2026
Filed by the triage seat (
session_01Tw7jnJinGHvoGSi8aFkhPJ, seat post #6015) as the second half of the maintainer's ruling on #15705. The maintainer, reviewingpm:on-holdcards on 2026-09-23 in the triage session, answered 「15705同意」 to both pending items: ① a resume verb on the MCP tool surface, which stays on #15705 (domain:cli); ② this card.Path: automation
The ruling this card carries
Replace the inference in
service-automation's headless-screen verdict with an explicit signal: the doors that start a flow (the action door and the trigger door) say which keys inparamsthe caller actually supplied, and the screen executor reads that signal instead of guessing.Why the inference has to go
PR #15787 (
b31ebfe7f) lets a screen node continue without pausing when the caller already answered it. It decides "did the caller supply this field?" by inference (callerSuppliedinpackages/services/service-automation/src/screen-input-contract.ts, read onorigin/main3ad89c1bcc): the dispatcher seedsparamswith the record spread and the row id under several names, so the verdict has to rule those keys out one by one.run_actionon a screen flow dead-ends: the screen pauses even when every input is bound, andlist_actionsnever surfaces flow input variables #15705,5551189478): "the signature of an inference that cannot be finished, not of a fix that is nearly done."recordIdFieldtogether with arecordIdParamnaming a key the record lacks:recordId;recordIdand the<object>Idalias.flows.mdx(MCPrun_actionon a screen flow dead-ends: the screen pauses even when every input is bound, andlist_actionsnever surfaces flow input variables #15705,5552138777and5552406678).Object.isis defeated by the durable store, which restores the context throughJSON.parse#15812) is already closed by value comparison. This card removes the class, not another instance of it.Shape (⛔ not ruled beyond "explicit, not inferred")
AutomationContextinpackages/spec, filled by both doors. Precedent:recordLoadDenied(packages/spec/src/contracts/automation-service.ts), a producer signal that the door fills and the handler reads.needs:contract-review(Clause-② yes: a published contract key is added).Acceptance
screen-headless-satisfaction.test.tsandscreen-headless-provenance-durable-resume.test.tsstays green. Wherever a pin encodes the inference rather than the contract, the PR names it.flows.mdxand the changeset is rewritten to describe the signal.Refs: #15705 (the ruling) · PR #15787 · #15812 · #16370 (the same doors, caller-scope handling)
Dedupe: this is the ruled follow-up that #15705's report named as "open question B"; it has not been filed before (the dev and the PM seat both declined to file it without a ruling).