Skip to content

fix(service-automation): the run row's highlight set carries the first filter's qualifier, unmeasured_count - #22632

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-22590-run-summary-acted
Oct 10, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-22590-run-summary-acted

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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 [finding] service-automation: FlowRunSummary.failed has no queryable column on sys_automation_run, so "which runs lost rows?" is a summary_json scan #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

…t filter's qualifier, unmeasured_count

The broken-sweep first filter is `selected_count > 0 AND acted_count = 0
AND unmeasured_count = 0`, and the object's own comment says it has to be
visible on the run row. Only two of its three operands were in
`highlightFields`. On a served stack a notify step's delivery is enqueued,
so a notify sweep that did its job reads `acted 0, unmeasured 1`, and the
run row showed it as `selected 1, acted 0`.

- highlightFields gains `unmeasured_count` right after `acted_count`
- the comment above it names the three operands
- `unmeasured_count`'s description names a notification queued for delivery
- one pin holds the highlight set to the three operands

Claude-Session: https://claude.ai/code/session_013j5gkUCpqQiti4GgPqqmnt
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Oct 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-automation, touching 9 documentable anchor(s).

19 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json e8c666687030083c0ffff9c172bc273b9ada6f7b.

⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e8c666687030083c0ffff9c172bc273b9ada6f7b → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 56ee4e5a51efb94df06ce030d26577bc3f7c3df9 — the merge of head 29cfa41a1bfaa3e909bb3360085c8e8dc39e906b into base e8c666687030083c0ffff9c172bc273b9ada6f7b, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 56ee4e5a51efb94df06ce030d26577bc3f7c3df9 && git checkout 56ee4e5a51efb94df06ce030d26577bc3f7c3df9
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e8c666687030083c0ffff9c172bc273b9ada6f7b 29cfa41a1bfaa3e909bb3360085c8e8dc39e906b && git checkout -B drift-repro e8c666687030083c0ffff9c172bc273b9ada6f7b && git merge --no-ff 29cfa41a1bfaa3e909bb3360085c8e8dc39e906b

node scripts/docs-audit/affected-docs.mjs --json e8c666687030083c0ffff9c172bc273b9ada6f7b

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs e8c666687030083c0ffff9c172bc273b9ada6f7b → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 10, 2026 07:17
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 10, 2026
Merged via the queue into main with commit 1b99388 Oct 10, 2026
36 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-22590-run-summary-acted branch October 10, 2026 07:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants