Skip to content

docs corpus teaches a flow the build REJECTS: flows.mdx contract_expiration_check is a schedule flow with a top-level get_record and no runAs: 'system' #5692

Description

@os-zhuang

Found while implementing #5633 (widening flow-runas-unscoped to nested regions). Filed unassigned for triage, deliberately out of scope for that PR — the finding is pre-existing and unchanged by it.

What

content/docs/automation/flows.mdx teaches, as its worked "Scheduled flow" example:

export const contractExpirationCheck: Flow = {
  name: 'contract_expiration_check',
  label: 'Contract Expiration Check',
  type: 'schedule',
  status: 'active',
  nodes: [
    { id: 'start', type: 'start', label: 'Start', config: {
        triggerType: 'schedule',
        schedule: { type: 'cron', expression: '0 0 * * *', timezone: 'UTC' },
    } },
    { id: 'find_expiring', type: 'get_record', label: 'Find Expiring Contracts' },
    { id: 'notify_owners', type: 'notify', label: 'Notify Owners' },
    { id: 'end', type: 'end', label: 'End' },
  ],
  ...
};

No runAs. A schedule run resolves no trigger user, so the effective runAs is the spec default 'user' and the get_record has no identity to scope to — which flow-runas-unscoped reports at severity: 'error', i.e. an author who copies this snippet gets a failed build, and if they force it past, a run the engine refuses (#3760).

Measured

Run against the rule as-is on origin/main (414395b9a), the snippet transcribed verbatim:

1 x flow-runas-unscoped  <-  flows.mdx contract_expiration_check
      error: schedule-triggered flow runs as the default `runAs:'user'`, but a schedule run has
      no trigger user - so its data node 'find_expiring' (get_record) has no identity to scope
      to and will be REFUSED at run time.

The same probe over the neighbouring examples returns 0 — they are correct:

0 x flow-runas-unscoped  <-  flows.mdx renewal_reminder (runAs: 'system')
0 x flow-runas-unscoped  <-  SKILL.md scheduled update (runAs: 'system')

Why it matters

Two independent reasons, and the second is the sharper one:

  1. The corpus contradicts itself in the same file. ~35 lines below the broken snippet, the time-relative example gets it right and even carries the rationale as a comment: runAs: 'system', // a sweep has no trigger user - elevate explicitly. And the property table in the same document (flows.mdx, the runAs row) already states the rule in prose: "declare 'system' for schedule / time-relative / API triggers". So the document explains the constraint twice and then violates it once, in the example most likely to be copied wholesale.
  2. This is the corpus AI authors from. check-doc-authoring.mjs's own header states the case for treating content/docs/ as corpus: a bad sample there is copied into app code by the next agent that reads it. Nothing type-checks or lints fenced blocks against the authoring rules, so a snippet can teach a shape the gate rejects indefinitely — this one has.

Fix

One line: add runAs: 'system' to contract_expiration_check, ideally with the same one-line rationale the neighbouring example carries. Docs-only.

Worth considering separately (not proposed here, and larger): nothing extracts fenced flow snippets and runs the authoring rules over them, which is the general mechanism that let this survive. check-doc-authoring.mjs scans for the bare-literal anti-pattern and check:skill-examples type-checks; neither evaluates a snippet's semantics. If that gate is wanted it should be its own issue.

Not caused by #5633

The evidence node here is find_expiring, a top-level get_record, so the rule flagged this snippet before #5633 and flags it identically after. The A/B over the docs/skills flow fixtures was run in both directions while implementing #5633 and is byte-identical — 1 hit on this snippet on both sides, 0 everywhere else. It is a corpus defect, not a regression, and it is not in #5633's scope.

Related

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions