Skip to content

Commit 8a483b3

Browse files
Trumpclaude
andauthored
docs(spec): the residual broken-sweep mentions in automation are a first FILTER, and the misspelled-effect sentences state the measured direction (#13096)
#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. Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4d94823 commit 8a483b3

5 files changed

Lines changed: 68 additions & 10 deletions

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
docs(spec): the last three broken-sweep mentions in `automation` become the first FILTER, and the misspelled-`effect` sentences state the direction the code measures (#13063)
6+
7+
#12685 measured that `selected > 0 AND acted = 0 AND unmeasured = 0` cannot separate a
8+
healthy idempotent sweep from a dead gate, and #12721 / #12722 / #12900 / #13068 rewrote
9+
the surfaces that stated it as a detector. Each of those changes was fenced to the doc
10+
blocks its dispatch named, so three mentions inside `packages/spec/src/automation`
11+
survived — including one that ships in `.d.ts` and is what a platform author reads.
12+
13+
Reworded to the shape the sibling surfaces now agree on: the predicate is the FIRST
14+
FILTER and not a verdict, the per-node fold (`FlowRunSummary.nodes[]` / `gates[]`) is the
15+
discriminator, and each clause keeps its own true point.
16+
17+
- `ExecutionStepMetricsSchema`'s `unmeasuredEffect` rationale no longer says an
18+
understated `0` "fires the broken-sweep alert on a healthy run until operators learn to
19+
ignore it". That muting is not peculiar to an understated `0` — after #12685 the filter
20+
selects every healthy idempotent sweep — so the block now states what a fabricated
21+
count actually costs: an understated `0` puts a run that DID act inside the filter, an
22+
overstated `1` keeps a run that acted on nothing outside it, and a faked `acted` is a
23+
fact the per-node fold can only repeat rather than settle.
24+
25+
Separately, and measured rather than ruled: the two sentences describing a misspelled
26+
`effect` key stated their consequence backwards. Read forward, a lost `effect: 'writes'`
27+
declaration means the `script` step reports no `unmeasuredEffect`, so the run reports
28+
`selected > 0, acted 0, unmeasured 0` — which SATISFIES the filter. The run therefore
29+
lands INSIDE the candidate set reading exactly like a dead sweep, rather than escaping
30+
it; the declaration is what would have kept it out. That is how the same file's `@module`
31+
block already stated it, and what `flow-function-effect.dogfood.test.ts` asserts end to
32+
end. The `FlowFunctionDeclarationSchema` TSDoc and the author-facing `history` string in
33+
its unknown-key message now say so.
34+
35+
TSDoc and one error-message string only — no behaviour, no schema, no accept-set change.

packages/spec/src/automation/execution.test.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,13 @@ describe('FlowRunSummarySchema', () => {
208208
});
209209

210210
it('carries an uncountable-effect tally distinct from acted', () => {
211-
// A connector-driven run: `acted: 0` is INCOMPLETE, not zero, and the
212-
// broken-sweep query has to be able to tell.
211+
// A connector-driven run: `acted: 0` is INCOMPLETE, not zero. The third
212+
// clause of the broken-sweep FIRST FILTER
213+
// (`selected > 0 AND acted = 0 AND unmeasured = 0`) is what reads that
214+
// distinction, which is why the tally is carried separately instead of
215+
// folded into `acted`. The filter only selects candidates — a healthy
216+
// idempotent sweep satisfies it too, and the per-node fold discriminates
217+
// (#12685) — but merging the two would corrupt even the selection.
213218
const summary = FlowRunSummarySchema.parse({
214219
selected: 9, acted: 0, skipped: 0, unmeasured: 3,
215220
nodes: [{ nodeId: 'push', nodeType: 'connector_action', status: 'success' as const, runs: 3, failures: 0, skipped: 0, unmeasured: 3 }],

packages/spec/src/automation/execution.zod.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,19 @@ export type ExecutionStatus = z.input<typeof ExecutionStatus>;
5555
* And `unmeasuredEffect` is the third answer, which a two-counter model would
5656
* have had to fake: a `connector_action` reaches an external system, and when
5757
* the action declares nothing about whether it reads or writes, `0` understates
58-
* a write and `1` overstates a read. Both are worse than saying so — an
59-
* understated `0` fires the broken-sweep alert on a healthy run until operators
60-
* learn to ignore it, and an overstated `1` makes the alert never fire at all,
61-
* which is the original bug back again.
58+
* a write and `1` overstates a read. Both are worse than saying so, and in
59+
* opposite directions — an understated `0` puts a run that DID act inside the
60+
* broken-sweep FIRST FILTER (`selected > 0 AND acted = 0 AND unmeasured = 0`),
61+
* and an overstated `1` keeps a run that acted on nothing outside it, which is
62+
* the original bug back again.
63+
*
64+
* Being inside that filter is not the accusation an alarm reading makes of it:
65+
* it is a first filter and not a verdict (#12685) — a healthy idempotent sweep
66+
* that re-selects the same records and gates each one on "already handled"
67+
* satisfies it on every run too, and what separates the two is the per-node
68+
* fold spelled out on `FlowRunSummarySchema` below. That is also what makes a
69+
* fabricated count the expensive kind of wrong: the fold is the step that would
70+
* have settled it, and a faked `acted` is a fact the fold can only repeat.
6271
*
6372
* [#4395] "Declares nothing" is now the fallback rather than the only case:
6473
* `ConnectorActionSchema.effect` lets an action say `read` or `write`, and a

packages/spec/src/automation/flow-function.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,11 @@ describe('defineStack({ functions }) — the authoring surface (#4396)', () => {
191191
// ignores everything else by construction, so a misspelled `effect` was
192192
// dropped at the schema and then not looked for — and the failure is the quiet
193193
// direction: the function registers, runs, and its writes are counted as none,
194-
// keeping #4354's broken-sweep query silent on the one run that needed it.
194+
// so the run reports `selected > 0, acted 0, unmeasured 0` — which SATISFIES
195+
// #4354's broken-sweep FIRST FILTER. The run lands in the candidate set
196+
// reading exactly like a dead sweep, on a flow that did its work; the `effect`
197+
// declaration is what would have kept it out (`unmeasured > 0`), and the
198+
// misspelling is what dropped it.
195199
describe('unknown keys are rejected, not stripped (#4001 batch 11)', () => {
196200
const base = {
197201
manifest: { id: 'com.example.demo', name: 'demo', version: '1.0.0', type: 'app' as const },

packages/spec/src/automation/flow-function.zod.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,12 @@ export const DEFAULT_FLOW_FUNCTION_EFFECT: FlowFunctionEffect = 'pure';
141141
* construction, so before this change a misspelled `effect` was dropped at the
142142
* schema and then *not looked for* by the reader — and the failure is the quiet
143143
* direction: the function is registered, it runs, and its writes are counted as
144-
* none, so #4354's broken-sweep filter stays silent on the one flow that needed
145-
* it. (A misspelled `effect` VALUE — `'write'` — already fails loudly-ish:
144+
* none, so the run reports `selected > 0, acted 0, unmeasured 0` and lands
145+
* INSIDE #4354's broken-sweep first filter, reading exactly like the broken
146+
* sweep that filter exists to detect — on the one flow that was doing its work
147+
* through the function. Declaring `effect: 'writes'` is what would have kept it
148+
* out (`unmeasured > 0`); the misspelling is what dropped the declaration.
149+
* (A misspelled `effect` VALUE — `'write'` — already fails loudly-ish:
146150
* `normalizeFlowFunctionEntry` degrades it to `'writes'` and surfaces the raw
147151
* string. A misspelled KEY had no such backstop.)
148152
*/
@@ -173,7 +177,8 @@ export const FlowFunctionDeclarationSchema = lazySchema(() => strictObject({
173177
'Until this shape was closed, these were dropped silently — and `normalizeFlowFunctionEntry` reads only ' +
174178
'`handler`/`effect` by construction, so a misspelled `effect` was discarded twice over: ' +
175179
'the function still registered, still ran, and its writes were still counted as none, ' +
176-
'which is what keeps the broken-sweep filter quiet on the run that needed it.',
180+
'which is what drops the run INTO the broken-sweep first filter ' +
181+
'(`selected > 0 AND acted = 0 AND unmeasured = 0`) on the very flow whose work the declaration would have accounted for.',
177182
}, {
178183
handler: z.function().describe('The function invoked by name (a `script` node, a string-named Hook/Action handler)'),
179184
effect: FlowFunctionEffectSchema.default(DEFAULT_FLOW_FUNCTION_EFFECT)

0 commit comments

Comments
 (0)