Repository navigation
Commit 1b99388
fix(service-automation): the run row's highlight set carries the first filter's qualifier, unmeasured_count (#22632)
Part of #22590
Clause-②: no
Item 2 of #22590 (a flow run's summary reports `acted: 0` while the run
sent notifications), under the amended claim (seat review `6094568255`,
claim amendment `6094572037`). Item 3, the activity pre-scan, is not
addressed here, and #22590 remains open.
## What changes
`packages/services/service-automation/src/sys-automation-run.object.ts`,
metadata only:
1. `highlightFields` gains `unmeasured_count`, right after
`acted_count`. The run row now shows all three operands of the
broken-sweep first filter: `selected_count > 0 AND acted_count = 0 AND
unmeasured_count = 0`.
2. The comment above `highlightFields` names those three operands, and
says why the qualifier has to be visible.
3. `unmeasured_count`'s description lists a notification queued for
delivery among the uncountable effects, beside the `connector_action`
and the mutating `http` call it already named. The rest of the sentence
is unchanged.
Plus one pin, `src/sys-automation-run-first-filter-highlight.test.ts`,
and a `patch` changeset. No column is added (ADR-0103 engine-owned
object). No counter moves, and nothing in `notify-node.ts`,
`service-messaging` or `packages/spec` changes.
## The premise measurement: no producer under-reports
The dispatched premise was this: a notify-only run triggered at `POST
/api/v1/automation/FLOW/trigger` returns `acted: 0` AND `unmeasured: 0`
while the inbox rows are already written. **It is false.**
How it was measured:
- A real `bootStack` stack (`@objectstack/verify`), sqlite-wasm, with an
org-bound admin session.
- The probe was a scratch file and was never committed.
- `origin/main` was `514bf3c101`.
- The flow is `start → notify (recipients: [admin], channels: [inbox]) →
end`, run once as `autolaunched` and once as `screen`. Both answered
identically.
| composition | door `summary` (selected / acted / unmeasured) |
`sys_automation_run` row | `emit()` answer (delivered / enqueued /
failed) | notify node `metrics` | `sys_inbox_message` at response |
after settle |
|---|---|---|---|---|---|---|
| **served default**: `requires: ['messaging']`, so
`MessagingServicePlugin` defaults, reliable delivery on, as `serve`
composes it | 1 / 0 / 1 | 1 / 0 / 1 | 0 / 1 / 0 | `selected 1,
unmeasuredEffect true` | **0 rows** (response at 100 ms and 39 ms) | 1
row (at 154 ms and 92 ms) |
| inline P0: `reliableDelivery: false` | 1 / **1** / 0 | 1 / 1 / 0 | 1 /
0 / 0 | `selected 1, acted 1` | 1 row | 1 row |
| control, served: audience `role:no_such_role_22590` resolves to no
user | 1 / 0 / 0 | 1 / 0 / 0 | 0 / 0 / 0 | `selected 1, acted 0` | 0 | 0
(5 s deadline) |
| control, inline | 1 / 0 / 0 | 1 / 0 / 0 | 0 / 0 / 0 | `selected 1,
acted 0` | 0 | 0 |
On a served stack, the in-app write really is asynchronous: the outbox
dispatcher writes the inbox row about 50 ms after the response. The
contract's answer for that is `unmeasuredEffect` ("an effect I cannot
count"), never a fabricated `acted`, and it already reaches both the
door's summary and the run row. The delivering run (`1 / 0 / 1`) sits
outside the first filter. The nothing-to-do control (`1 / 0 / 0`) sits
inside it, as designed. `summarizeRun` folds `step.metrics` from every
step whatever its `nodeType`, so the count already reads from each
node's outcome; there is no list of record-writing kinds.
**Release parity.**
- The `@objectstack/service-automation@17.7.0` tag is an ancestor of
HEAD (`merge-base --is-ancestor`, exit 0).
- These are byte-identical at the 17.7.0 tags and at HEAD (`diff`, exit
0):
- the notify node's metrics block;
- `MessagingService.emit`'s outbox and inline tail.
- `run-summary.ts` and `messaging-service-plugin.ts` have no diffstat
since the tag.
- `reliableDelivery: true` is the default at the tag.
- The trigger door relays the engine result, `summary` included, at
`@objectstack/runtime@17.7.0`.
- So the card's source, measured on 17.7.0, saw `acted 0` beside an
`unmeasured 1` it did not read.
## Why the fix is the highlight set, not a counter
At the API, the delivering run is already distinguishable. The misread
survives on the Runs surface. `highlightFields` carried `selected_count`
and `acted_count` but not their qualifier, although the object's own
comment says the first filter "has to be visible on the run row itself …
a signal you must click to find is a signal nobody sees". So a served
stack's delivering notify sweep showed as `selected 1, acted 0` on the
run row, which is the card's misread made on another surface.
Counting the enqueue as `acted` would bring back the #7747 defect: a
summary asserting a delivery that `sys_notification_delivery` could
still mark `dead`. Folding it into anything other than `unmeasured`
would break the contract in `execution.zod.ts`. The fix that matches the
declared design is to show the qualifier the design already has.
## Node-kind census (no edits)
These are the effect-bearing node kinds and the counter each reports on
`main`:
- `create_record`, `update_record`, `delete_record`: `acted` as a row
count.
- `get_record`: `selected`.
- `http`: `acted 0` for a read, `unmeasuredEffect` for a mutating call.
- `connector_action`: `acted 1` for an accepted declared write, `acted
0` for a declared read, `unmeasuredEffect` for an undeclared action or a
failed write dispatch.
- `script`: `unmeasuredEffect` for a function declared `writes`, nothing
for a pure one.
- `notify`: `acted` equal to `delivered`; `unmeasuredEffect` while a
delivery is enqueued; `acted 0` plus `selected` for a zero dispatch.
- `subflow`, `map`: the child run rolls up.
The kinds without an effect report nothing, which is correct:
`decision`, `assignment`, `loop`, `parallel`, `try_catch`, `wait`,
`screen`.
The one effect-bearing kind with no counter is plugin-approvals'
`approval` / `approval_revise` node. See Acceptance notes.
## The pin and its ablation
`sys-automation-run-first-filter-highlight.test.ts`:
- each first-filter operand is a declared `number` column, a positive
control so that the containment check cannot pass on a typo;
- `highlightFields` contains all three operands.
Ablation, run on the committed tree with `node
scripts/ablation-replace.mjs` in WRAP mode under the verify lock, which
proves the write landed on disk and restores it under a trap.
- Attempt 1 used `--replacement "'acted_count', "`. **The tool refused
it before any test ran.** The replacement is a substring of the anchor,
so its count could not rise, and the tool reads that as a mutation that
did not land. The file was restored to the HEAD blob. Recorded as a
no-op.
- Attempt 2 used `--anchor " 'unmeasured_count'," --delete`:
- the anchor count went 1 → 0, and the blob went `5a0f59cde967` →
`ec9f7ec995f1`;
- **pin red**: `Tests 1 failed | 1 passed (2)`, `first-filter operands
missing from highlightFields: unmeasured_count: expected [
'unmeasured_count' ] to deeply equal []`;
- the column-type test stayed green, as predicted;
- restore: the blob is back to `5a0f59cde967`, equal to HEAD, `git diff
HEAD` is empty, and `git status --porcelain` is empty.
- The pin imports the object by relative path (source), so no `dist/`
sits between the mutation and the run.
## Verification
Every run below is at HEAD `29cfa41a1b` (the branch merged with
`origin/main` `83b8b80728`). Heavy runs went through
`scripts/pm/os-verify-lock.sh` (slot `issue-22590`, 3 GB heap, turbo
`--concurrency=1`, vitest `--maxWorkers=2`).
- **Build**: `turbo run build
--filter='@objectstack/service-automation...'` gave `VERDICT
command-exit 0`, `Tasks 30 successful, 30 total`. `packages/spec` moved
on `main`'s side of the merge, so `pnpm --filter @objectstack/spec
check:generated` also ran: `All 15 generated artifacts are up to date`.
- **`service-automation` test**: `vitest run --maxWorkers=2` gave `Test
Files 182 passed (182)`, `Tests 2287 passed (2287)`. The new pin is
included.
- **`service-automation` typecheck**: `tsc --noEmit && pnpm
check:test-typecheck` gave `VERDICT command-exit 0` and
`check:test-typecheck: OK`. The new test file is in tsc's program (`tsc
--listFiles` count 1).
- **Derived gates**: `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` derived 63 commands from the real
diff (3 paths). All were run, each exit captured before any pipe. The
reconcile with `--ran`, given exit codes, answered `63 derived, 62 run,
1 NOT-MEASURED, 0 UNRUN`.
- 62 exited 0, each printing its own pass verdict.
- **NOT MEASURED: `check:dual-build-cjs-loads`**, reason: `PREREQUISITE
NOT MET`, exit 3. It reads the built output of every publishable
package, and 37 have no `dist/` in this worktree, which built only
`service-automation`'s closure. This is a declared narrowing: CI's `Lint
& Repo Gates` runs it on a full build. The diff changes a string array
and a description string in one source file, and does not touch any
package's emit format or exports.
- **i18n** (not derived, run because a description changed): `pnpm
check:i18n` gave `OK (9 package(s) — all bundles in sync)`, and `pnpm
check:i18n-coverage` gave `OK (13 config(s) … none new)`. No translation
bundle carries `sys_automation_run`, so nothing was regenerated.
- **Scope**: `packages/cli` and `objectql` are not touched; no
integration layer is owed.
## Acceptance notes
- **The approval-node census finding was dropped by the seat** (seat
review `6094568255`). `approval` / `approval_revise`
(`packages/plugins/plugin-approvals/src/approval-node.ts`,
`approval-revise-node.ts`) open an approval request and suspend with no
`metrics`. No door gives a wrong summary, and an approval-only run reads
`selected 0`, outside the filter regardless. This was a source reading
only, and it is not filed.
- For flow operators (the card's source included): read the triple
(`selected`, `acted`, `unmeasured`), never `acted` alone. On a served
stack, a notify-only sweep that delivered reads `acted 0` with
`unmeasured 1`.
- `failed_count` stays absent (the #15606 verdict). The sibling pin
`sys-automation-run-failed-count-verdict.test.ts` still holds
`failed_count` out of `highlightFields`, and still requires
`acted_count` in it.
- The measurement probe (scratch, never committed) and its logs live in
the dev's scratch directory, not in the tree.
---
_Generated by [Claude
Code](https://claude.ai/code/session_013j5gkUCpqQiti4GgPqqmnt)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent f368b7e commit 1b99388
3 files changed
Lines changed: 63 additions & 5 deletions
File tree
- .changeset
- packages/services/service-automation/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
Lines changed: 42 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
Lines changed: 12 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
77 | 77 | | |
78 | 78 | | |
79 | 79 | | |
80 | | - | |
81 | | - | |
82 | | - | |
83 | | - | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
84 | 91 | | |
85 | 92 | | |
86 | 93 | | |
| |||
459 | 466 | | |
460 | 467 | | |
461 | 468 | | |
462 | | - | |
| 469 | + | |
463 | 470 | | |
464 | 471 | | |
465 | 472 | | |
| |||
0 commit comments