Skip to content

feat(spec,runtime,service-automation): the flow doors state callerParamKeys, and the headless screen verdict reads it - #19899

Merged
os-justin merged 7 commits into
mainfrom
claude/issue-19846-automation-caller-provenance
Sep 23, 2026
Merged

os-justin merged 7 commits into
mainfrom
claude/issue-19846-automation-caller-provenance

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #19846

Clause-②: yes

Rewritten short by the domain:spec#5 seat (2026-09-23T16:17Z). The dev report is on #19846; the earlier long body is in the edit history.

AutomationContext gains an optional callerParamKeys?: string[] (packages/spec): the keys of the caller's own params. When it is present, the screen node's headless verdict reads it and never infers. When it is absent (an older producer), the inference from #15787 runs unchanged.

What changed

  • Doors that fill it:

    • the action door, dispatchFlowAction (REST POST /api/v1/actions/..., MCP run_action);
    • the trigger door, buildAutomationContext (POST /automation/:name/trigger, the legacy route, a declarative type: 'flow' endpoint).

    Each door records the caller's keys before seeding anything. It leaves out its row-id keys: recordId, the camelCase object-id alias, and on the action door the action's recordIdParam.

  • Producers that leave it absent (today's inference): the record-change, schedule, time-relative and webhook triggers, and direct engine.execute callers.

  • Child runs: subflow and map drop the parent's list from the child run's context.

  • Behaviour edge: a caller that sends the action's recordIdParam key with a value other than the row id no longer answers a screen field of that name. The changeset says so.

  • Docs:

    • flows.mdx and the changeset describe the signal;
    • the flows.mdx "known gap" paragraph on durable resume is replaced, because it has been false since 4177ed3a84.

Pins

  • Both boundary constructions from the card pause on the real engine (screen-headless-caller-signal.test.ts). Removing the signal read turns both red.
  • screen-headless-satisfaction.test.ts and screen-headless-provenance-durable-resume.test.ts are untouched and green; they now pin the absent path. The dev report names the fixtures in them that encode the inference rather than the contract.
  • One existing pin was edited: endpoint-executor.test.ts compares the whole context with toEqual, so it now includes callerParamKeys: ['reason'].

Not in this PR

🤖 Generated with Claude Code

https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1

…ams keys the caller supplied

AutomationContext gains an optional callerParamKeys. The action door
(dispatchFlowAction) and the trigger door (buildAutomationContext) fill it
from the caller's own bag before seeding, leaving their row-id keys out.
The screen node's headless verdict reads it instead of inferring
provenance; an absent signal keeps the inference. subflow and map child
runs drop the parent's list.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
…tract, the two doors and the screen verdict

The two boundary constructions (non-default recordIdField with a
recordIdParam naming a key the record lacks, object-less and object-bound)
pause on the real engine; the doors hand the engine exactly those contexts.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
…ovenance signal; changeset

The accepted-cost sentence now describes the doors' callerParamKeys and the
row-id keys they leave out. The durable-store gap paragraph is replaced:
it has not held since the record leg compared by value (4177ed3), and
the fallback inference for producers without the signal is described
instead.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
…ies callerParamKeys

The endpoint executor sends buildAutomationContext's output, which now
states the caller's keys; the pin asserts the whole context with toEqual,
so it names the new key rather than loosening to a partial match.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
…ing commit

The comment moved into flowActionRowIdKeys with the code it explains; the
card number it carried no longer resolves (live citation gate), so it now
names the commit that landed the single predicate.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Sep 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/runtime, @objectstack/service-automation, @objectstack/spec, touching 17 documentable anchor(s).

23 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 1f0b3419ff818c5b97c2d0c00dc917410a67e8b2.

⛔ 4 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 141 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 1f0b3419ff818c5b97c2d0c00dc917410a67e8b2 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from b513eabdc4b92f077956a4f127a73e2682265505 — the merge of head 0155b343ef2c723a5a92f6c1a52b3a67b1db69cd into base 1f0b3419ff818c5b97c2d0c00dc917410a67e8b2, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin b513eabdc4b92f077956a4f127a73e2682265505 && git checkout b513eabdc4b92f077956a4f127a73e2682265505
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1f0b3419ff818c5b97c2d0c00dc917410a67e8b2 0155b343ef2c723a5a92f6c1a52b3a67b1db69cd && git checkout -B drift-repro 1f0b3419ff818c5b97c2d0c00dc917410a67e8b2 && git merge --no-ff 0155b343ef2c723a5a92f6c1a52b3a67b1db69cd

node scripts/docs-audit/affected-docs.mjs --json 1f0b3419ff818c5b97c2d0c00dc917410a67e8b2

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 1f0b3419ff818c5b97c2d0c00dc917410a67e8b2 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 0155b343ef2c723a5a92f6c1a52b3a67b1db69cd

Reviewed and posted 2026-09-23T16:55Z by the at-tier review subagent the domain:spec#5 seat spawned — inputs: card #19846 and its 3 comments, #15705 (9 comments), #19900, PR #19899 (merge-base diff dabf8d795e..0155b343ef, 12 files, 7 commits, 46 check-runs), AGENTS.md, contract-review.md, execution-duties.md; objectui read at the pin 62597c5880. Read: the whole diff and every producer named below. Ran: the three screen suites, the two runtime pin files and the spec pin at head in a detached worktree; four ablations (three re-measured from the dev's set, one my own) and one edge probe on the real engine. NOT MEASURED: the map-strip ablation (A3), the dev's claim that boundary construction 1 is unreachable through a shipped door, and the derived gate family (read from CI, per the rule).

① Derived judgments

Producers of an AutomationContext that start or resume a run (my census: every .execute( into an automation engine/service over packages, examples, apps, non-test, plus the engine's trigger start callback at engine.ts:3607; the service registered as automation is the engine itself, plugin.ts:617, and resolveRunContext spreads ...context, engine.ts:5038, so the key reaches screen-nodes.ts:236 unfiltered):

producer signal judged
trigger door domains/automation.ts:1305 via buildAutomationContext — POST /:name/trigger, legacy POST /trigger/:name filled right; both routes pinned, 12/12 measured
declarative type:'flow' endpoint endpoint-executor.ts:537, same builder filled right; callerParamKeys: ['reason'] pin measured
action door action-execution.ts:947 dispatchFlowAction — REST /actions (domains/actions.ts:753, params: reqParams = raw body.params) and MCP run_action (domains/mcp.ts:727 → invokeBusinessAction:2261, params = raw input.params) filled right; nothing is merged into the caller's bag upstream on either path (the {...reqParams, recordId, objectName} merge at actions.ts:741 is the script/sandbox branch)
schedule (schedule-trigger.ts:806), time-relative (:726), record-change (:499), api/webhook (api-trigger.ts:143) — through engine.ts:3607 absent → inference right per the ruling's scope; schedule's non-record seeds are #19900
subflow-node.ts:114, map-node.ts:193 child runs stripped → inference right: these are the only two engine.execute sites in builtin/; parallel/loop/try_catch run regions of the same run against the same bag, so the parent's list is the right list there
examples/app-showcase/src/security/seed-approval-demo.ts:308 direct engine.execute absent right
engine.resume (run.context, engine.ts:6783), retryExecution (same context), bubbleToParent (parent's own persisted context) not a start no foreign list is possible: ResumeSignal (automation-service.ts:503) carries no context; both stores JSON-clone on save and load (suspended-run-store.ts:145,178,183), and a string[] survives — the durable pin went red under A1

Can a caller make a filled list name a key it did not supply, or omit one it did? No and yes-by-design: both lists are Object.keys of the caller's own bag minus the door's row-id keys, nothing else; the omission is the row-id set only.

Row-id keys. Trigger door leaves out recordId and the object-id alias: right against shipped clients — objectui useConsoleActionRuntime.flowHandler at the pin mirrors the row id into params.recordId (line 578) before posting to that door; RecordDetailView.flowHandler, the SDK automation.trigger and verify's flows.run (handle.ts:369) reach the same door. Action door additionally leaves out the action's recordIdParam: on main, flows.mdx already says the id seeded "under an action's declared recordIdParam" is not "the caller supplying a screen value" (diff context lines 44-46), while the inference honoured that only by value. Measured edge (my probe on the real engine, required field named like recordIdParam, caller value ≠ row id): no signal → run continues with the caller's value; door-shaped signal → paused. Not an accept-set narrowing: no request key is refused, the value still lands in the bag ({...seeded, ...params}), no authorable key or export is removed — execution-duties.md:65 asks whether the accept set widens or the public surface grows; AGENTS.md's (narrowing) arm is for a removal or rename an author can write. Plain Clause-②: yes is the right arm; no ADR-0087 disposition owed. The changeset states the edge (its last bullet).

Pins. Head: service-automation 41/41 (11 new + 24 + 6 untouched), runtime 62/62, spec 2/2. Ablations (each mutated file hash-checked, restored with checkout HEAD --, blob == HEAD, tree clean): A1 signal read removed → 6 red of 41, BOUNDARY 1 and BOUNDARY 2 both red plus the durable-store pin; A2 subflow strip removed → 2 red of 11; A6 trigger-door exclusion removed → 5 red of 12; my own action-door exclusion removed → 3 red of 12.

Sentences that ship. Docblock, changeset, flows.mdx, PR body: the door list, the absent-producer list and the strip list equal my census; the inference paragraph in flows.mdx matches the four legs of callerSupplied at head; the replaced "known gap" paragraph was false since 4177ed3a84 (value comparison, #16392) as claimed; "no security middleware keys on it" — the twelve files naming the key are the PR's own; "one existing pin edited" — the diff's only modified test is endpoint-executor.test.ts. No false sentence found. Two incompletions, not falsehoods: flows.mdx names the trigger door without the legacy route the docblock and changeset name; the changeset's "two constructions still skipped" is the engine-level verdict (the dev's own report says construction 1 is not producible through a shipped door — NOT MEASURED by me).

Dev-report claim that is misattributed (not in shipped text): the action door's recordIdParam exclusion is justified by objectui resolveRecordIdParamSeed, but at the pin that call sits in the console's api handler (useConsoleActionRuntime.tsx:415-426; RecordDetailView.tsx:685 names it the apiHandler mirror), which never reaches dispatchFlowAction; console flow actions use the trigger door. The exclusion stands on the published flows.mdx sentence, not on that client.

Governed surfaces: none in the file list. Model identifiers: swept the diff, all seven commit messages and the PR body for every spelling — none.

② Semver level

  • @objectstack/spec minor — a new optional member on the published AutomationContext (./contracts export): correct, and it is what makes Clause-②: yes true.
  • @objectstack/runtime minor — no new entry-point export (buildAutomationContext/dispatchFlowAction are not on index.ts); the doors emit a new signal: at least right.
  • @objectstack/service-automation patch — HeadlessScreenContext is new but screen-input-contract.ts is not re-exported by the package entry (exports map is . only), so no public surface moves; the behaviour is a skip→pause fix: defensible (minor would also be).
  • Arm: plain yes, consistent with the changeset (no (narrowing); nothing removed). Check Changeset success.

CI at the head, 46 runs de-duplicated by name keeping the latest started_at: 35 checks, 31 success, 4 skipped by design, 0 red — Auto Label and Check PR Size (latest run is the seat's body-edit edited event, excluded by pr-automation.yml's github.event.action != 'edited'; their earlier push-event runs succeeded), Packed-tarball smoke (opt-in) (label needs:pack-smoke absent), Console Pin Gate (needs.filter.outputs.console false: no console path touched). All seven required contexts success.

③ Boundary flags

  • Q1 (webhook trigger absent): answered — A. The ruling names the action and trigger doors; api-trigger.ts:143 seeds params as a copy of the payload it also sets as the record, so today's inference pauses; filling from the payload would move webhook screen behaviour without a ruling.
  • Q2 (recordIdParam left out; arm): answered — keep, plain yes, no ADR-0087 disposition (① above, with the measured edge).
  • Key name callerParamKeys: accepted — camelCase TS config key, reads as what it is.
  • Locally unmeasured (dev): full spec suite and the out-of-package importers of AutomationContext — covered by Test Core (6/6 shards) and Type Check · workspace, both success.
  • A scheduled flow run skips a screen whose required field is named schedule, jobId or flowName: the schedule trigger's own params are read as the caller's answers #19900 (schedule seeds answer a screen): correctly filed as class a, pm:blocked with Blocked-by: #19846; the only trigger with non-record seeds, so the class ends there.

Blocking: none.
Non-blocking: (1) the dev report's resolveRecordIdParamSeed justification is misattributed (api handler, not the flow door) — the shipped text does not carry it; (2) flows.mdx omits the legacy trigger route from its door list; (3) service-automation could equally be minor; (4) A3 and the boundary-1 reachability claim were not re-measured here.

Implemented-by: claude/issue-19846-automation-caller-provenance
Reviewed-by: session_01Sfe5YjBLwB9J3y8fvm2xq1 — at-tier review subagent spawned by the domain:spec#5 seat

VERDICT: PASS

@os-justin
os-justin marked this pull request as ready for review September 23, 2026 16:58
@os-justin
os-justin added this pull request to the merge queue Sep 23, 2026
Merged via the queue into main with commit 90ff10a Sep 23, 2026
51 checks passed
@os-justin
os-justin deleted the claude/issue-19846-automation-caller-provenance branch September 23, 2026 17:19
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…a screen field (objectstack-ai#19900) (objectstack-ai#19982)

Fixes objectstack-ai#19900
Clause-②: no

## What this changes

The schedule trigger now sets `callerParamKeys: []` on every run context
it builds. A tick has no caller, so none of its `params` seeds (`jobId`,
`flowName`, `schedule`) is a caller's answer. The `screen` node's
headless verdict reads the list when it is present (PR objectstack-ai#19899) and no
longer infers. The card's property: "A scheduled run's platform seeds
never answer a screen field". A screen in a scheduled flow now pauses,
whatever its fields are named. The three seeds stay in `params`, so
flows that read them are unaffected.

## Premise, re-measured on `origin/main` `2c1011b01b`

- `packages/triggers/trigger-schedule/src/schedule-trigger.ts:806-810`
builds `params: { jobId, flowName: binding.flowName, schedule }` and
sets no `callerParamKeys`. It is the only producer of an `event:
'schedule'` context under `packages/` (non-test `git grep`).
- The reader treats `[]` as "the caller supplied nothing", not as
absent. `callerSupplied` in `screen-input-contract.ts:357` reads `if
(declared !== undefined) return Array.isArray(declared) &&
declared.includes(name);`. The consumer side already has a pin for this:
`screen-headless-caller-signal.test.ts:160` ("an EMPTY list is an
answer: it pauses a screen the inference would skip"). The reader is
unchanged here.
- Pre-fix red: I ran the new test file against the unfixed trigger. With
a required screen field `schedule`, the run completed instead of
pausing: `success: true`, `output: { schedule: { type: 'cron',
expression: '0 2 * * *' } }`, and node `screen_1` had status `success`.
`jobId` and `flowName` failed the same way.

## Files

1. `packages/triggers/trigger-schedule/src/schedule-trigger.ts`: adds
`callerParamKeys: []` (the fix).
2.
`packages/triggers/trigger-schedule/src/schedule-caller-param-keys-e2e.test.ts`
(new): the real `ScheduleTrigger` is bound through the real
`AutomationEngine` with builtin nodes. A `type: 'schedule'` flow runs
`start`, then `screen_1`, then `end`. The job is fired by hand. The test
reads the context the trigger passed to `engine.execute` and the result
the engine returned to the trigger's callback. It pins:
- the context carries `callerParamKeys: []`, and the seeds are still in
`params`;
- a required field named `schedule`, `jobId` or `flowName` pauses at
`screen_1` (`it.each`), and `listSuspendedRuns()` names the run;
   - an all-optional screen with all three names pauses too;
- control: on the same flow and engine, a context that names `schedule`
as a caller key continues. The screen is not paused just because the
flow is a schedule flow.
3. Four texts this change would have made false. These are outside the
expected landing:
- `packages/spec/src/contracts/automation-service.ts`, the
`callerParamKeys` TSDoc. It now names the schedule trigger as a producer
that states `[]`, and drops it from the "Absent means" list. TSDoc only;
no type or key change.
-
`packages/spec/src/contracts/automation-context-caller-param-keys.pin.test.ts`,
a comment ("an absent key is what every producer other than the two
doors sends").
- `packages/services/service-automation/src/screen-input-contract.ts`,
the docblock's "Absent" paragraph. Comment only; the inference is
unchanged.
- `content/docs/automation/flows.mdx`, the "A run started any other way"
paragraph.
4. `.changeset/19900-schedule-caller-param-keys.md`:
`@objectstack/trigger-schedule` patch and `@objectstack/spec` patch. The
spec TSDoc ships: after the build, the new sentence is in
`packages/spec/dist/contracts/index.d.ts` and `index.d.mts`, one hit
each. The `service-automation` docblock does not ship: `grep -rl` over
its `dist/` finds 0 files, while the positive control
`judgeHeadlessScreen` is found in `index.js` and `index.cjs`. So
`service-automation` has no entry.

## The pending `callerParamKeys` release note (objectstack-ai#19846) is left as it is

That note says "Record-change, schedule, time-relative and webhook
triggers ... leave it absent". Once this lands, that is false for the
schedule trigger. I edited it once, and `check-empty-changeset.mjs
--base origin/main` refused the edit with exit 1: "Correcting a pending
release note is a decision about a release". I restored it
byte-identical to the merge base (blob `c5cbff2689`, both sides). This
PR's own changeset now states the superseded sentence instead. If that
note should be corrected as well, a person has to confirm it; the gate
stays red on that edit by design.

## Time-relative trigger (dispatch item 3): measured, no defect, not
changed

This was a one-time probe, not committed. It fired the real
`TimeRelativeTrigger` and passed its callback to the real engine. The
run context had keys `record, object, event, params`, with `params ===
record` (the same object) and no `callerParamKeys`. A screen with
required fields named after swept columns (`name`, `status`) paused at
`screen_1`. An all-optional screen with the same names also paused after
a `structuredClone` round trip. The inference's record leg already
treats every key as a seed, because `params` is the record. So there is
no defect and no change.

## Other producers (dispatch item 4): no finding

The same probe ran two hand-built shapes on the real engine, each copied
from its producer's code. The record-change shape (`params` is the same
object as `record`, from `record-change-trigger.ts:499`) paused. The
webhook shape (`record: payload, params: { ...payload }`, from
`api-trigger.ts:142-143`) paused. Neither skips a screen on its own
seeds, so I filed nothing.

## Tests (HEAD `eeed06a5b`)

- `pnpm --filter @objectstack/trigger-schedule test`: 7 files, 150 tests
passed. `typecheck` (`tsc --noEmit`) passed with exit 0, and
`--listFiles` includes the new test file (7 test files in the program).
- `@objectstack/service-automation`: `vitest run`, 144 files, 1725 tests
passed. `typecheck` exit 0.
- `@objectstack/spec`: `vitest run --project local`, 530 files, 15617
passed and 1 todo. `typecheck` exit 0 (`tsc --noEmit`,
`check:scripts-typecheck`, `check:test-typecheck`).

## Ablation of the pin

I committed the fix first, at `afde207`. Then `node
scripts/ablation-replace.mjs --file
packages/triggers/trigger-schedule/src/schedule-trigger.ts --anchor
'callerParamKeys: [],' --delete -- (the test run)`:

- Mutation landed: anchor count 1 to 0; blob `5d67f8394deb` to
`85d2e4c9054f`.
- The test went red: 5 failed and 1 passed (the control). Failure
output: `expected undefined to deeply equal []`, and four times
`expected { success: true, …(4) } to match object { status: 'paused',
…(1) }`.
- Restored: the blob after restore is `5d67f8394deb`, the same as at
HEAD, and `git diff HEAD` is empty.
- The subject is imported as `./schedule-trigger.js` (source), not
through a package export, so no `dist/` preflight applies.

## Gates

`node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` derived 107 commands from the change set, with no paths
passed. All 107 ran at `eeed06a5b`, each with its exit code recorded.
The `--ran` reconciliation reads: 107 derived, 105 run, 2 NOT-MEASURED,
0 UNRUN.

- NOT MEASURED: `check:dual-build-cjs-loads` and
`check:type-check-debt`. Each exited 3 (PREREQUISITE NOT MET): both need
every package built, and a turbo dry run shows 77 of 78 build tasks as
cache misses. CI's lint job builds that closure before running them.
- `check-plugin-teardown-shape --self-test` first exited 3, because its
pinned fixture commit was missing from the shallow clone. I fetched that
one commit, and the self-test passed on re-run (48 cases).
- `check:skill-examples` first exited 3 because client-react was not
built. I built `@objectstack/client` and `@objectstack/client-react`,
and it passed on re-run (259 blocks).
- ESLint, narrowed to the touched files: `eslint --no-inline-config
--format json` over the 5 touched `.ts` files gave 5 files, 0 errors, 0
warnings. Population: the config's `files` globs are
`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` and the `packages/**` /
`**/*.{ts,…}` blocks. The other two changed paths (`.md`, `.mdx`) match
no `files` glob. Invariance: all 7 `parserOptions` blocks are `{
ecmaVersion, sourceType }` and none sets `project`, so there is no
type-aware linting. The config reads only
`scripts/slot-lookup-baseline.json` and
`scripts/query-options-erasure-baseline.json` from disk, and this diff
touches neither. So the diff cannot change the verdict on any untouched
file.

## Acceptance notes

- The webhook trigger passes its whole payload as both `record` and
`params`. A screen in a webhook-started flow can therefore never be
answered by that payload, because the inference reads every key as a
record seed. That leans toward pausing (a lost skip, never a skipped
screen). This is an observation, not filed.
- The docblock in
`packages/metadata-protocol/src/runtime-authoring-gate.ts:106-108`
abbreviates the schedule context as `{ event: 'schedule', params: {
jobId, flowName, schedule } }`. Its subject is `tenantId`, and it
already omits the conditional `tenantId`. Left as it is.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01Evb5jFDZGKQE9KG4jbMfMF)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
… schedule trigger as leaving it absent (objectstack-ai#19900) (objectstack-ai#19991)

Part of objectstack-ai#19900
Clause-②: no

## The pending release note this corrects

This PR edits one sentence of
`.changeset/19846-automation-caller-param-keys.md`, the pending note for
`AutomationContext.callerParamKeys` (PR objectstack-ai#19899). The note's frontmatter
covers `@objectstack/spec`, `@objectstack/runtime` and
`@objectstack/service-automation`. Nothing else in the note changes,
frontmatter included (1 line changed: +1 / −1).

**Before:**

> Record-change, schedule, time-relative and webhook triggers, and code
calling `execute` directly, leave it absent.

**After:**

> Record-change, time-relative and webhook triggers, and code calling
`execute` directly, leave it absent; the schedule trigger, whose run has
no caller, states an empty list (objectstack-ai#19900).

**What made it false:** PR objectstack-ai#19982, landed as `ae7a35a63b`. Since that
commit, `ScheduleTrigger` sets `callerParamKeys: []` on every run
context it builds, so the schedule trigger no longer leaves the key
absent. PR objectstack-ai#19982's own changeset
(`.changeset/19900-schedule-caller-param-keys.md`) reaches only the
`@objectstack/trigger-schedule` and `@objectstack/spec` CHANGELOGs. The
`@objectstack/runtime` and `@objectstack/service-automation` CHANGELOGs
would publish this sentence unchanged.

## The gate is expected to stay red

`node scripts/check-empty-changeset.mjs --base origin/main` exits 1 on
this head. This is a DELIBERATE CORRECTION of a foreign pending note,
and the gate stays red on it by design until a person confirms the
rewritten sentence (the seat's ruled path: ruling D on objectstack-ai#17712, and the
landing rule added in objectstack-ai#19970). Its output begins:

```text
Diffing HEAD from ae7a35a (merge base with origin/main).
✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).
This PR changes a changeset it did not add:

   .changeset/19846-automation-caller-param-keys.md
     present on the merge base and CHANGED by this PR -- this is somebody else's release note
```

The DELIBERATE CORRECTION remedy it prints is: "do NOT restore it -- say
so on the PR and get it confirmed". This section is that statement.

## `skip-changeset`: not applied

Two readings, and they agree. (1) This diff changes release-note text
that `changeset version` will compile into three published CHANGELOGs,
so it is not a diff that publishes nothing. (2) The standing rule for a
PR that edits an existing `.changeset/*.md` is never to apply
`skip-changeset`: the red gate is correct, and the confirmation goes
through the PR text.

## Gates (head `465a0f9342`)

`node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` derived 19 commands from the change set (merge base
`ae7a35a63`). All 19 ran with their exit codes recorded. 18 exited 0,
and `check-empty-changeset.mjs --base origin/main` exited 1, as expected
above. `pnpm check:changeset-gate-self-tests` exited 0. The `--ran`
reconciliation reads: 19 derived, 19 run, 0 NOT-MEASURED.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01Evb5jFDZGKQE9KG4jbMfMF)_

Co-authored-by: Claude <noreply@anthropic.com>
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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AutomationContext: an explicit caller-provenance signal for flow params, replacing the headless-screen inference of PR #15787 (#15705 ruling ②)

2 participants