Uh oh!
There was an error while loading. Please reload this page.
docs(spec): the broken-sweep predicate in FlowFunctionEffectSchema is a first FILTER, not the detector - #13068
Conversation
… a first FILTER `packages/spec/src/automation/flow-function.zod.ts` stated `selected > 0 AND acted = 0 AND unmeasured = 0` as *the* broken-sweep query, unqualified -- the third `packages/spec` surface carrying the claim, outside the fence of the change that corrected `automation/execution.zod.ts` and `integration/connector.zod.ts`. #12685 measured the A/B on one graph pair through the real engine: a healthy idempotent sweep -- re-select the same records, gate each one on "already handled" -- and a dead gate BOTH report `selected > 0, acted 0, unmeasured 0`. "Over N consecutive runs" does not rescue it: the healthy steady state is persistent for as long as the outstanding work stands, so it trips on every run; consecutiveness filters flapping, which is a different failure. Reworded to the shape the sibling surfaces now agree on: the predicate is the FIRST FILTER, not a verdict, and the per-node fold (`FlowRunSummary.nodes[]` / `gates[]`) is the discriminator. Each clause keeps its own true point -- a declared-`writes` function makes `acted` INCOMPLETE rather than zero, and an under-reported `selected` can still only make the filter quieter, never wrong. This file's `@module` block is emitted verbatim as the intro of `content/docs/references/automation/flow-function.mdx`, so the artifact is regenerated here with `gen:schema && gen:docs` -- never hand-edited. TSDoc and generated-docs prose only -- no behaviour, no schema, no accept-set change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 49f8025c725e6e5dbb2cc8e4390e94403a92146b && git checkout 49f8025c725e6e5dbb2cc8e4390e94403a92146b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6c6157af05b25b28be0b3a7e92ce03ef85a98653 4d5d782bd8e4e39b1e5ad3789b963b493a630f2d && git checkout -B drift-repro 6c6157af05b25b28be0b3a7e92ce03ef85a98653 && git merge --no-ff 4d5d782bd8e4e39b1e5ad3789b963b493a630f2d
node scripts/docs-audit/affected-docs.mjs --json 6c6157af05b25b28be0b3a7e92ce03ef85a98653 |
Uh oh!
There was an error while loading. Please reload this page.
…rst FILTER, and the misspelled-effect sentences state the measured direction #12685 measured that `selected > 0 AND acted = 0 AND unmeasured = 0` cannot separate a healthy idempotent sweep from a dead gate. #12721, #12722, #12900 and #13068 each rewrote the surfaces that stated it as a detector, and each was fenced to the doc blocks its dispatch named -- so three mentions inside `packages/spec/src/automation` were left standing rather than corrected in passing. One of them ships in `.d.ts` and is what a platform author reads. Reworded to the shape the sibling surfaces now agree on: the predicate is the FIRST FILTER and not a verdict, the per-node fold (`FlowRunSummary.nodes[]` / `gates[]`) is the discriminator, and each clause keeps its own true point. 1. `execution.zod.ts` -- the `unmeasuredEffect` rationale no longer says an understated `0` "fires the broken-sweep alert on a healthy run until operators learn to ignore it". That muting is not peculiar to an understated `0`: after #12685 the filter selects every healthy idempotent sweep. The block now states what a fabricated count really costs -- an understated `0` puts a run that DID act inside the filter, an overstated `1` keeps a run that acted on nothing outside it, and a faked `acted` is a fact the per-node fold can only repeat rather than settle. 2. `execution.test.ts` -- the comment no longer says "the broken-sweep query has to be able to tell". The assertion under it is unchanged and still correct: it pins that `unmeasured` is carried distinctly from `acted`. 3. `flow-function.test.ts` -- the comment mirroring the source sentence #13068 reworded now matches it again. CAUSAL DIRECTION, measured rather than ruled. The card's second half asked whether the "stays silent on the one flow that needed it" sentences state their direction backwards. They do. Read forward: a lost `effect: 'writes'` declaration means the `script` executor reports no `unmeasuredEffect` (`screen-nodes.ts`: `const unmeasured = registration.effect === 'writes'`), so the run folds to `selected > 0, acted 0, unmeasured 0` -- which SATISFIES the three-clause filter. The run lands INSIDE the candidate set reading exactly like a dead sweep, rather than escaping it. The same file's `@module` block already said so ("which reads exactly like the broken sweep #4354 exists to detect"), and `packages/qa/dogfood/test/flow-function-effect.dogfood.test.ts` asserts both legs end to end: declared writer -> `unmeasured: 1` (filter does not match), pure -> `unmeasured: 0` with the filter "free to fire on this run". The card's alternative reading -- a flow whose only work is inside the function, so `selected` stays 0 -- is disposed of by the same measurement: there the filter is quiet with OR without the declaration, which makes the sentence vacuous rather than correct. Bounded in-place: `flow-function.zod.ts` is one file outside the three-mention surface, carrying the two source sentences that mention 3 mirrors. Fixing the mirror while leaving its source backwards would manufacture exactly the disagreement this card exists to remove, so both are corrected here -- the `FlowFunctionDeclarationSchema` TSDoc and the author-facing `history` string in its unknown-key message. Prose and one error-message string only -- no behaviour, no schema, no accept-set change. `check:generated` reports all 14 artifacts up to date, so nothing regenerated. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
Fixes#12900
The third
packages/specsurface carrying the unqualified broken-sweep claim. #12685 measured the A/B on one graph pair through the real engine (pinned inrun-summary.test.ts): a healthy idempotent sweep — re-select the same records, gate each one on "already handled" — and a dead gate BOTH reportselected > 0, acted 0, unmeasured 0. "Over N consecutive runs" does not rescue it: the healthy steady state is persistent for as long as the outstanding work stands, so it trips on every run; consecutiveness filters flapping, which is a different failure.TSDoc and generated-docs prose only — no behaviour, no schema, no accept-set change. No ablation is owed here: nothing executable changed, so there is no guard whose removal could be shown to turn a check red. The measurement that backs the prose already has its executable twin in
run-summary.test.ts.Per-mention triage
Five mentions in
packages/spec/src/automation/flow-function.zod.ts, judged individually rather than swept.9e8adf3f6)@moduleblock, ~:38-42 — emitted verbatim on the docs pageselected > 0 AND acted = 0 AND unmeasured = 0) … That predicate is a first filter and not a verdict (#12685: a healthy idempotent sweep … matches it on every run too, and what discriminates is the per-node fold inFlowRunSummary.nodes[]/gates[])"FlowFunctionEffectSchemadoc block, ~:62-67 — the card's primaryunmeasuredtally keeps the broken-sweep query (selected > 0 AND acted = 0 AND unmeasured = 0) off it"actedcount, not a zero one." plus a new paragraph: "A filter, not a verdict (#12685) … What separates them is the per-node fold onFlowRunSummary(nodes[]/gates[]) … Declaring'writes'decides which runs enter that first filter; it does not make the filter a detector."selectedcan only make the broken-sweep alert quieter, never wrong"unmeasured_count's descriptioneffectkey is untouchedhistorystring, ~:155strictObjecterror-message prose (it is in no generated artifact; grep over the tree finds it in no.json/.mdx/.mdoutsideCHANGELOG.md), and no test pins its content —flow-function.test.tsasserts on the surface name and the`efect` → `effect`prescription, both untouchedLeft standing, deliberately: ~:23 — "which reads exactly like the broken sweep #4354 exists to detect". This claims nothing about the three-clause predicate. It says an under-reporting run resembles a broken sweep, which is true before and after #12685, and is in fact the premise the rest of the module rests on. Rewording it would be a change for uniformity's sake with no defect behind it.
Note on mention 1, which the card's file surface did not enumerate
The card measured that this file's
@moduledoc comment is emitted verbatim as the intro ofcontent/docs/references/automation/flow-function.mdx, and required the regeneration on that basis. Re-measuring on the base showed the enumerated four mentions are not the ones that reach that page: theFlowFunctionEffectSchemadoc block contributes only its.describe()string to the generated page (under theFlowFunctionEffectheading), and mentions 3-5 do not appear there at all. The prose that reaches the page is the@moduleblock — and it carries a fifth mention of the same claim, in the same defect class, at ~:38-42. "the broken-sweep signal" is the exact phrase #12721 replaced onsys_automation_run.acted_count.So mention 1 is both the sentence an operator actually reads on the docs page and the only reason the generated artifact moves in this PR. Rewording the enumerated four alone would have produced a PR whose
check:generatedis green trivially — the #12722 reading — rather than green with the regenerated artifact in the diff, which is what this card was dispatched to demonstrate. It is inside the card's declared file, the same defect class, mechanically corrected by evidence already landed, and no other open PR claims the file; it is named here and quoted above rather than folded in silently.Cross-surface agreement
These sentences now say the same thing as their siblings:
sys_automation_run.acted_count's description ("is the FIRST FILTER for a broken sweep, not a verdict", fix(service-automation): the broken-sweep predicate is a first FILTER, not the detector #12721) ·FlowRunSummarySchema's block inautomation/execution.zod.ts("is the FIRST FILTER for a broken sweep, not a verdict (The documented broken-sweep predicateselected > 0 AND acted = 0 AND unmeasured = 0fires on a HEALTHY idempotent sweep — measured A/B, and "over consecutive runs" does not rescue it #12685)", The corrected broken-sweep claim is still stated unqualified inpackages/specTSDoc — same false predicate, a surface #12685 was fenced away from #12722) ·integration/connector.zod.ts("a filter, not a verdict (The documented broken-sweep predicateselected > 0 AND acted = 0 AND unmeasured = 0fires on a HEALTHY idempotent sweep — measured A/B, and "over consecutive runs" does not rescue it #12685)", The corrected broken-sweep claim is still stated unqualified inpackages/specTSDoc — same false predicate, a surface #12685 was fenced away from #12722) ·content/docs/automation/flows.mdx("The filter is not the detector", fix(service-automation): the broken-sweep predicate is a first FILTER, not the detector #12721) — and now mentions 1 and 2 here.nodes[]/gates[], orsummary_json's fold; mentions 1 and 2 now nameFlowRunSummary.nodes[]/gates[]in the same words.unmeasured_count's description andexecution.zod.ts'sunmeasuredblock both say an uncountable run has an INCOMPLETEactedcount, not a zero one; mention 2 now says it in those words, which is the point the card asked to have survive.flows.mdx("stops matching that flow") andconnector.zod.ts("puts every healthy connector sweep into that candidate set").Generated artifact
content/docs/references/automation/flow-function.mdxis regenerated, never hand-edited:The only artifact that moved is
flow-function.mdx, mirroring mention 1 exactly (12 lines replacing 5).authorable-surface.base.jsonwas not rewritten by this run;gen:schemaprinted the expected anchor note ("expected, and not an error: the anchor is a snapshot of an upstream commit, proved AUTHENTIC rather than current") andcheck:authorable-surfaceis green.Evidence
All readings taken on the final commit —
git rev-parse --short HEAD=4d5d782bd, the pushed head, working tree clean. Exit codes captured before any pipe (each gate run redirected to its own log first, thenEXIT=$?).The card-distinguishing reading —
check:generatedgreen with the regenerated artifact in the diff:The first run of that gate exited 1 on a PREREQUISITE, not a finding: "packages/spec/dist holds no .d.ts declarations — the package is not built".
pnpm --filter @objectstack/spec build(exit 0, "check-dts-emitted: @objectstack/spec - 34/34 declared declaration file(s) present.") and the re-run above is the measured one. The build is also the proof these blocks ship:packages/spec/dist/flow-function.zod-DoP4q83A.d.tscarries "A filter, not a verdict (#12685)" — read out of adistrebuilt after the edit, not a cached one.Type checking, with coverage proven rather than assumed:
(
typecheckhere istsc --noEmit && check:scripts-typecheck && check:test-typecheck; the test-layer program is thetsconfig.test.jsonsibling and it is green too.)Tests — the package's own suite, and the file that pins the
strictObjectmessages the editedhistorystring feeds:Gate family — derived from the real change set rather than recalled:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, which reported "change set derived from git — 3 path(s) vs merge base 9e8adf3" and 49 matched families. All 49 were run; 47 green, 2 NOT MEASURED for container reasons (both declared below). Among the green ones, beyond the families the dispatch named:check:docs,check:authorable-surface,check:doc-anchors,check:doc-authoring,check:doc-formula-expressions,check:doc-security-posture,check:docs-audit-scope,check:docs-redirects,check:docs-single-h1,check:empty-state,check:liveness,check:merge-driver,check:skill-examples,check:skill-refs,check:spec-parsed-alias,check:strictness-ledger,check:variant-docs,check:quick-reference-counts,check:cross-package-test-inputs,check:test-source-alias,check:type-source-resolution,check:published-files,check:keyed-text-bounds,check:doc-frontmatter,check:docs-section-name,check:section-landing-index,check:changeset-gate-self-tests,check:empty-changeset,check:changeset-no-major,check:adr-0087-registration,check:objectui-changeset,check:pm-half-states, and the docs-drift pair.Three gates exited non-zero on a PREREQUISITE the first time and were re-run after the prerequisite was met, all green:
check:generated(above),check:doc-formula-expressions/check:doc-security-posture("Nothing was measured: this gate exited before running a single check" — needed@objectstack/formulaand@objectstack/lintbuilt) andcheck:skill-examples(needed@objectstack/client-reactbuilt).Two gates are NOT MEASURED — declared as such, not reported as green and not reported as red. Both are container facts and neither can be moved by this diff:
node scripts/check-dev-prereqs.mjsexits 1 in this fresh worktree with "34 of 67 workspace packages declare an entry point under dist/ that is not on disk … Fix: pnpm build". Its population, read from its own output, is declared package entry points versus the filesystem — a build-state fact about the container, not about file contents. This diff contains three files (git diff --name-only), none of them apackage.json, and it adds no package, noexportsand nomain; prose inside a.tsdoc comment and a regenerated.mdxcannot move that verdict in either direction. Satisfying it locally means building all 67 packages, which is CI's run — and CI builds before lint, so it reports the real reading there.node scripts/pm/check-half-states.mjs(thehalf-state-patrol.ymlboard sweep, as distinct from the lint.yml familypnpm check:pm-half-states= its--self-test, which ran green: "check-half-states self-test: 1551 cases pass.") exits 3 with its own "PREREQUISITE NOT MET — the token in the environment is not a valid GitHub credential … Nothing was swept: no issue was listed, no predicate (H1-H16) ran … It is not a clean board and it is not a dirty one — it is no reading at all." It sweeps the issue board, not the tree, so no file in this diff is in its population.Byte discipline:
Repo-wide lint, not narrowed:
Out of scope, filed
#13063 — three further broken-sweep mentions in
packages/spec/src/automationthat sit outside the fences of #12722 and of this card:execution.zod.ts~:57-61 (a.d.ts-shipping block in the same file #12722 corrected elsewhere, still "fires the broken-sweep alert" / "makes the alert never fire"), and test comments inexecution.test.ts~:210 andflow-function.test.ts~:192-194. That card also records a separate question the reading raised — whether the "stays silent on the one flow that needed it" sentences state their causal direction backwards — which is not the predicate-as-detector defect and is deliberately left unresolved here: the minimal rewords in mentions 4 and 5 leave each sentence's own claim exactly as it was, whichever reading turns out to be intended. That card is open and is not addressed by this PR.Generated by Claude Code