Found while implementing #5633. Filed unassigned, observation-class (no pm:queue): nothing is blocked and the remedy in the hint is correct either way — the defect is that a deliberate distinction in the diagnostic is dead on the surface that matters.
What
packages/lint/src/lint-flow-patterns.ts branches the message on whether runAs was authored or defaulted:
const runAs = typeof flow.runAs === 'string' ? flow.runAs : 'user';
...
const declared = typeof flow.runAs === 'string' ? `\`runAs:'${runAs}'\`` : `the default \`runAs:'user'\``;
The rule is wired input: 'parsed' (authoring-rules.ts), and the flow schema supplies 'user' as runAs's default — so by the time the rule runs on the CLI, flow.runAs is the string 'user' whether the author wrote it or not. The 'the default …' arm is therefore never taken on os validate / os build / os lint.
Measured
A real example flow (examples/app-todo/src/flows/task.flow.ts, overdue_escalation) with its runAs line deleted entirely — the author declared nothing — through os validate:
x Author-time rules failed (1 issue)
- flow 'overdue_escalation' - runAs: schedule-triggered flow runs as `runAs:'user'`, but a
schedule run has no trigger user - so its data node 'update_priority' (update_record), in
loop 'loop_overdue' body, has no identity to scope to and will be REFUSED at run time.
rule: flow-runas-unscoped
runs as runAs:'user' — the explicit arm, for a flow with no runAs key. The unit tests take the other arm (the default runAs:'user') only because they pass raw, unparsed stack literals straight to lintFlowPatterns, so the test suite proves the branch works while no shipped surface can reach it.
Why it is worth recording rather than shrugging at
It reads as an accusation the author can disprove by looking at their file. Someone who wrote no runAs is told their flow "runs as runAs:'user'", which invites the reading "I never wrote that — the tool is confused", exactly when the tool is right and the fix is one line away. The author-vs-default distinction is real and useful (it separates "you wrote something incoherent" from "you inherited a default that does not fit a user-less trigger"), and it is currently invisible where it would help.
It is also a small instance of a shape this repo has paid for before: a branch that is exercised only by tests whose input shape differs from production's. Cf. #4984 (fixtures spelling rejected aliases kept tests green while the rule was dead) — same mechanism, much lower stakes.
Possible directions (a maintainer's call — not proposed here)
- Read the authored value. Have the rule consult the pre-parse stack for
runAs presence, so the distinction survives. Costs a second input to a rule that is otherwise cleanly parsed.
- Drop the distinction. Delete the dead arm and always print
runAs:'user', keeping the message honest about what it can actually know. Cheapest; loses a genuinely useful nuance.
- Re-word so it is true either way — e.g. "runs under
runAs:'user' (the default when none is declared)" — one sentence, no new input, no dead branch.
(3) looks like the best value for the size of the problem, but this is a diagnostic-wording contract and the person who wrote the distinction may have wanted (1).
Not caused by #5633
Pre-existing: #5633 only widened where this rule looks for its evidence node and left the declared branch untouched. The behaviour above reproduces identically on origin/main at 414395b9a. Confirmed with real os validate output in both directions.
Related
Found while implementing #5633. Filed unassigned, observation-class (no
pm:queue): nothing is blocked and the remedy in thehintis correct either way — the defect is that a deliberate distinction in the diagnostic is dead on the surface that matters.What
packages/lint/src/lint-flow-patterns.tsbranches the message on whetherrunAswas authored or defaulted:The rule is wired
input: 'parsed'(authoring-rules.ts), and the flow schema supplies'user'asrunAs's default — so by the time the rule runs on the CLI,flow.runAsis the string'user'whether the author wrote it or not. The'the default …'arm is therefore never taken onos validate/os build/os lint.Measured
A real example flow (
examples/app-todo/src/flows/task.flow.ts,overdue_escalation) with itsrunAsline deleted entirely — the author declared nothing — throughos validate:runs asrunAs:'user'— the explicit arm, for a flow with norunAskey. The unit tests take the other arm (the default runAs:'user') only because they pass raw, unparsed stack literals straight tolintFlowPatterns, so the test suite proves the branch works while no shipped surface can reach it.Why it is worth recording rather than shrugging at
It reads as an accusation the author can disprove by looking at their file. Someone who wrote no
runAsis told their flow "runs asrunAs:'user'", which invites the reading "I never wrote that — the tool is confused", exactly when the tool is right and the fix is one line away. The author-vs-default distinction is real and useful (it separates "you wrote something incoherent" from "you inherited a default that does not fit a user-less trigger"), and it is currently invisible where it would help.It is also a small instance of a shape this repo has paid for before: a branch that is exercised only by tests whose input shape differs from production's. Cf. #4984 (fixtures spelling rejected aliases kept tests green while the rule was dead) — same mechanism, much lower stakes.
Possible directions (a maintainer's call — not proposed here)
runAspresence, so the distinction survives. Costs a second input to a rule that is otherwise cleanlyparsed.runAs:'user', keeping the message honest about what it can actually know. Cheapest; loses a genuinely useful nuance.runAs:'user'(the default when none is declared)" — one sentence, no new input, no dead branch.(3) looks like the best value for the size of the problem, but this is a diagnostic-wording contract and the person who wrote the distinction may have wanted (1).
Not caused by #5633
Pre-existing: #5633 only widened where this rule looks for its evidence node and left the
declaredbranch untouched. The behaviour above reproduces identically onorigin/mainat414395b9a. Confirmed with realos validateoutput in both directions.Related
flow-runas-unscoped(severity error) still searches only top-level nodes — a scheduled flow whose data ops all live in aloopbody passes the build and is refused at run time #5633 — where this surfaced.error.