Observation-class finding, filed unassigned with no pm:queue — for triage to grade and route. Named residual of #7900 (PR pending), deliberately not decided inside that card because closing it needs a different mechanism from the one #7900's maintainer ruling names.
What #7900 closed, and what it left
The 2026-08-12 ruling on #7900 converged the /automation run-state reads on the sys_automation_run object-read grant. GET /:name/runs/:runId and GET /:name/runs now require it. The ruling also asked for the rest of the domain to be audited against the same rule, with a stated reason for anything that stays authenticated-only.
GET /:name/runs/:runId/screen is the one route the audit left authenticated-only, and the reason is on the route in packages/runtime/src/domains/automation.ts:
The residual, measured
The route is not inert with respect to record data. AutomationEngine.getSuspendedScreen(runId) returns (await this.loadSuspendedRun(runId))?.screen — the ScreenSpec persisted with the suspended run — and that spec is built with values interpolated against the live flow variables at suspend time (packages/services/service-automation/src/builtin/screen-nodes.ts: interpolate(cfg.defaults, variables, context) and, per field, interpolate(f.defaultValue, variables, context)).
So a flow whose screen prefills from the triggering record (defaultValue: '{record.email}', a defaults map over {record.*}) writes those values into the spec, and the route answers them to any authenticated caller who knows the run id — there is no check anywhere on this path that the run belongs to, or was paused for, the caller. Same shape as the #7900 exposure, one route over and a narrower payload: interpolated screen defaults rather than the whole variable map.
Not claimed: that this is a regression (it is true of origin/main before #7900 as well), or that it exposes any credential class (the #7900 analysis of connector credentials applies here unchanged).
What closing it would take
Not the sys_automation_run grant — that is the mechanism this route was audited out of, for the reasons above. The shape that fits is the one the resume path already has: a per-run authority read gate, i.e. "may this caller see the screen this run is paused on?", answered from the suspension's own resumeAuthority / assignee state rather than from an object permission. That is a design call about the screen-flow runtime (ADR-0019 territory), not an application of the #7900 ruling, which is why it is a separate card.
Possible cheaper interim, if triage wants one before that design lands: scope the route to the run's own trigger identity (ExecutionLogEntry.trigger.userId) with the sys_automation_run grant as the operator override — narrow, decidable, and it does not refuse the end user. Recorded as an option, not a recommendation; the authority model is the real answer.
Evidence
Measured 2026-08-12 while implementing #7900, on origin/main base 4b5702a, reading packages/runtime/src/domains/automation.ts, packages/services/service-automation/src/engine.ts (getSuspendedScreen) and packages/services/service-automation/src/builtin/screen-nodes.ts.
Source
Split out of #7900 — the implementing dev's named residual, recorded in the route's own comment and in that card's PR audit table.
Observation-class finding, filed unassigned with no
pm:queue— for triage to grade and route. Named residual of #7900 (PR pending), deliberately not decided inside that card because closing it needs a different mechanism from the one #7900's maintainer ruling names.What #7900 closed, and what it left
The 2026-08-12 ruling on #7900 converged the
/automationrun-state reads on thesys_automation_runobject-read grant.GET /:name/runs/:runIdandGET /:name/runsnow require it. The ruling also asked for the rest of the domain to be audited against the same rule, with a stated reason for anything that stays authenticated-only.GET /:name/runs/:runId/screenis the one route the audit left authenticated-only, and the reason is on the route inpackages/runtime/src/domains/automation.ts:screeninline, and the route exists so a browser refresh does not lose it. Requiring an operator grant here would refuse the screen to the very person the flow paused for — a breakage, not the narrowing the ruling prices in ("operator tooling … now needs the grant").resumeis gated in the engine by the suspension's declaredresumeAuthority(automation: the generic run-resume route needs an authorization gate keyed on the suspended node #3801 / automation:resumeAuthoritydefaults to'any', so every future pausing node ships fail-open — ADR-0044 says this is "tracked separately" and nothing tracks it #5561) — a per-run authority model, not an object grant. Read and write on the same pause answering to two unrelated permissions would be the incoherence finding: the whole /automation read domain is gated only by "authenticated" — run-detail returns the triggering record's fields without that record's own FLS #7900 exists to remove.The residual, measured
The route is not inert with respect to record data.
AutomationEngine.getSuspendedScreen(runId)returns(await this.loadSuspendedRun(runId))?.screen— theScreenSpecpersisted with the suspended run — and that spec is built with values interpolated against the live flow variables at suspend time (packages/services/service-automation/src/builtin/screen-nodes.ts:interpolate(cfg.defaults, variables, context)and, per field,interpolate(f.defaultValue, variables, context)).So a flow whose screen prefills from the triggering record (
defaultValue: '{record.email}', adefaultsmap over{record.*}) writes those values into the spec, and the route answers them to any authenticated caller who knows the run id — there is no check anywhere on this path that the run belongs to, or was paused for, the caller. Same shape as the #7900 exposure, one route over and a narrower payload: interpolated screen defaults rather than the whole variable map.Not claimed: that this is a regression (it is true of
origin/mainbefore #7900 as well), or that it exposes any credential class (the #7900 analysis of connector credentials applies here unchanged).What closing it would take
Not the
sys_automation_rungrant — that is the mechanism this route was audited out of, for the reasons above. The shape that fits is the one the resume path already has: a per-run authority read gate, i.e. "may this caller see the screen this run is paused on?", answered from the suspension's ownresumeAuthority/ assignee state rather than from an object permission. That is a design call about the screen-flow runtime (ADR-0019 territory), not an application of the #7900 ruling, which is why it is a separate card.Possible cheaper interim, if triage wants one before that design lands: scope the route to the run's own trigger identity (
ExecutionLogEntry.trigger.userId) with thesys_automation_rungrant as the operator override — narrow, decidable, and it does not refuse the end user. Recorded as an option, not a recommendation; the authority model is the real answer.Evidence
Measured 2026-08-12 while implementing #7900, on
origin/mainbase4b5702a, readingpackages/runtime/src/domains/automation.ts,packages/services/service-automation/src/engine.ts(getSuspendedScreen) andpackages/services/service-automation/src/builtin/screen-nodes.ts.Source
Split out of #7900 — the implementing dev's named residual, recorded in the route's own comment and in that card's PR audit table.