Skip to content

fix(RUFU-883): the 5s metrics board read stops carrying comment text - #3732

Closed
ischindl wants to merge 1030 commits into
Runfusion:mainfrom
ischindl:rufu-883-sampler-comment-projection
Closed

ischindl wants to merge 1030 commits into
Runfusion:mainfrom
ischindl:rufu-883-sampler-comment-projection

Conversation

@ischindl

@ischindl ischindl commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

What this delivers

The dashboard's metrics endpoint stops loading every card's comment history every 5 seconds just to count how many cards sit in each board column.

Evidence (measured, not inferred)

Offline retainer chain over a 10.35 GiB production heap snapshot (B-10.35GiB-heaps-1648464.heapsnapshot, 41 622 648 nodes / 161 043 192 edges):

level holder count
1 Object via property text 394 503 comment records
2 Array elements, max 289 37 616 arrays
3 hydrated rows via comments / steeringComments 18 801 / 18 751
4-6 batch arrays, fan-in 106-107 3 703 - 20 972

The same board in PostgreSQL: 2 766 live cards, 836 of them carrying any comment, 9 938 comment entries. That is ~22 living hydrations of every commented card.

The reader is packages/dashboard/src/metrics/domain-sampler.ts, which ticks every 5 s per registered project and declares its own need as SlimTaskLike { column: string }.

Why RUFU-640 did not fix this

RUFU-640 bounded the steering-comment writer (2026-10-11; last new signal "stuck" entry 11:43 that day). It stopped the data growing. The heap kept growing because the reader re-materialised the whole corpus every tick.

Change

  • TaskListReadField gains comments, owning BOTH comments and steeringComments. One member, because rowToTask deliberately does not merge the pair - a half-declaration would describe a shape the mapper cannot produce.
  • defaultListTasks declares fields: [], so the executed SELECT drops the comment columns (log stays excluded by slim).
  • Re-audited all four production declared read sites, because adding a declarable member silently narrows every existing declared read: triage.ts:3202/:6368 and auto-claim-snapshot.ts:237/:335 read no comment field; triage.ts's two .comments reads are single-task getTask paths.

Verification

  • pnpm --filter @fusion/core exec vitest run src/__tests__/list-read-comment-projection.test.ts -> 9 passed
  • pnpm --filter @fusion/dashboard exec vitest run src/metrics/__tests__/domain-sampler.test.ts -> 18 passed
  • pnpm verify:fast -> PASS, 68 steps green
  • pnpm lint -> 0 errors (6 pre-existing warnings in untouched files)

Ref: RUFU-883 (retainer), RUFU-588 (holder chain), RUFU-640 (writer bound), RUFU-615/756/495 (the same read's earlier payloads), RUFU-627 (the declared-field mechanism).

ischindl and others added 30 commits October 5, 2026 02:30
project.task_reviewer_runs had no liveness reconcile: an attempt whose session died stayed
status='running' forever. Measured on production 2026-10-04: 275 rows running, NOT ONE live, oldest
2026-09-15 (19 days), 20 in-review cards held by one. Every engine restart manufactures more, because the
reviewer session dies with the process and nothing closes its row. The dispatch sweep derives its
anti-loop budget from those committed rows, so a zombie reads as an unfinished attempt, and the stall
classifier disposes the card as completed-review-status-none -- the whole review lane sat still while
every individual guard behaved correctly.

- core ledger gains listStrandedLiveReviewerRuns + REVIEWER_RUN_LIVENESS_FLOOR_MS (30 min, candidate
  selection only, never asserts death), ReviewerRunRow.failureReasons parsed defensively, and the shared
  engine-lost: reason prefix with isEngineLossReviewerRun so writer and reader cannot drift.
- engine gains reconcileStrandedReviewerRuns, registered at startup and in maintenance batch 2 next to
  FN-8492's sweep, whose two rules it mirrors: age selects a candidate, the canonical liveness triple
  (activeSessionRegistry paths, executingTaskLock, isTaskActive) proves death; and nothing is deleted --
  the ledger's one-way completion is the race guard, so a verdict landing mid-pass wins and the pass
  reports already-settled. userPaused cards, vanished cards and unparseable clocks are never touched.
- dispatch budget exemption: unfinishedAttempts skips engine-lost closes. Without it this sweep would be
  worse than doing nothing, converting 'stranded, cannot dispatch' into 'attempts exhausted, parked
  forever'. The sweep's own dispatch-failed: refusals stay counted (asserted).
- audit: task:reconcile-stranded-reviewer-runs via emitBoundedRunAudit, ids/counts/fixed outcomes only;
  documented as a prose section, keeping the curated catalogue parity test honest.

Verified: core+engine tsc 0 errors; new sweep test (7 cases incl. live-session veto, one-way-guard win,
unparseable clock, registration slice) + dispatch budget tests + catalogue parity = 84 green; pnpm
test:gate EXIT=0 (509+207+75). Symptom proof owed after deploy: the 20 stranded in-review cards.
…eam line

Add docs/fork-extensions.md as the single record of which capabilities belong to
this build rather than upstream Runfusion/Fusion, linked from README with one
line so our README divergence stays minimal, and index it in docs/README.md.

The boundary is upstream-authored, not claimed by us: their
0085_drop_excluded_upstream_feature_schema.sql describes their published binary
as "a permanently feature-reduced fork" that never implements migration slots
0074, 0076, 0079-0083 (project notes, whiteboards, workflow-identity archive
tables, task-level human-approval/pause/queue-boost columns). We are the
full-featured build that migration moves features out of, and we do not carry
0085 at all.

Every claimed capability is per-symbol verified against origin/main with the
anchor identifier at 0 upstream hits: humanPlanApproval, humanMergeApproval,
AsyncNoteStore, AsyncWhiteboardStore, perTurnRecall, operatorLanguage,
ReviewDispatchSweep, applyVerificationResourceBound, classifyChatInFlightLiveness.
A "deliberately not claimed" section records the knowledge graph, long-term
memory, overlap waits, and migration 0089 as shared, so they cannot be
re-claimed as ours on a path-only reading.

The doc also records two things that cost hours to rediscover:
- archive retirement (FN-9187) against upstream's retained LifecycleColumns.archived
  is the largest silent-conflict generator here. It produces no textual conflict
  and fails only at typecheck, so conflict counts understate merge cost.
- upstream PRs stay shippable because pr/* branches are cut from origin/main and
  cherry-pick only the shippable commits; measured, no pr/* branch has our main as
  an ancestor. Rebasing a small pr/* branch is safe; rebasing our main is not.

Guard the claims with fork-extensions-doc.test.ts: anchor files and identifiers
must still resolve, upstream's relocation migration must never appear here, our
own schema slots must stay present, and the README link must not break. It
asserts link integrity and code constructs only, never documentation prose.
Give the cataloged Meta Muse provider a first-class accessible icon instead of the unknown-provider fallback.

- add a tokenized Meta Muse SVG mark to the shared provider icon registry
- cover catalog deduplication, accessibility, sizing, and fallback behavior
- document the regression and add the published package patch changeset

Files changed:
 .changeset/fn-9477-provider-icon-catalog-coverage.md                   |  7 +++++++
 docs/solutions/test-failures/fn-9477-provider-icon-catalog-coverage.md | 34 ++++++++++++++++++++++++++++++++++
 packages/dashboard/app/components/ProviderIcon.tsx                     | 24 ++++++++++++++++++++++++
 packages/dashboard/app/components/__tests__/ProviderIcon.test.tsx      | 40 +++++++++++++++++++++++++++++++++++++++-
 packages/dashboard/app/styles.css                                      |  1 +
 5 files changed, 105 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-9477

Fusion-Task-Lineage: 488952c5-7916-4ac6-9d2a-903ef211716d

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Record the completed post-landing Full Suite investigation and its evidence-backed disposition.

- Identify the shard-3 failure as pre-existing core producer-inventory drift rather than the FN-9477 change.
- Capture final lane, artifact, digest, attribution, and follow-up evidence for all Full Suite jobs.
- Remove stale deleted-test paths from the committed timing snapshot.

Files changed:
 .../fn-9478-full-suite-shard-3-disposition.md      | 110 +++++++++++++++++++++
 scripts/test-timings.json                          |   3 -
 2 files changed, 110 insertions(+), 3 deletions(-)

Fusion-Task-Id: FN-9478

Fusion-Task-Lineage: d5be48eb-966d-4477-9d9f-2402bc1ff848

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Accept unambiguous trailing heading dependencies authored before strict prefix validation, preserving canonical and stored dependency checks. Cover the live failed plan at the parser and graph runner boundaries.

Fusion-Task-Id: FN-9442
Mission validation now reuses the completed task's landed revision and bounded forge records without converting infrastructure outages into product failures.

- materialize exact landed revisions and classify checkout, ancestry, and forge outages as retryable inconclusive evidence
- read bounded, redacted GitHub and GitLab issue and merge-request records through the configured dashboard integrations
- wire evidence providers through CLI and engine runtimes, with regression coverage and operator documentation

Files changed:
 .changeset/fn-9464-mission-validation-evidence.md  |   7 +
 docs/missions.md                                   |   6 +-
 packages/cli/src/commands/__tests__/daemon.test.ts |  26 +++
 packages/cli/src/commands/__tests__/serve.test.ts  |  25 +++
 packages/cli/src/commands/daemon.ts                |  13 +-
 packages/cli/src/commands/dashboard.ts             |  13 ++
 packages/cli/src/commands/serve.ts                 |  13 +-
 .../__tests__/mission-validation-evidence.test.ts  |  75 +++++++++
 packages/dashboard/src/index.ts                    |   2 +
 .../dashboard/src/mission-validation-evidence.ts   | 128 +++++++++++++++
 .../src/__tests__/mission-execution-loop.test.ts   | 174 ++++++++++++++++++--
 .../__tests__/mission-validation-evidence.test.ts  | 106 ++++++++++++
 packages/engine/src/index.ts                       |   7 +
 .../engine/src/missions/mission-execution-loop.ts  | 142 ++++++++++++++--
 .../src/missions/mission-validation-evidence.ts    | 178 +++++++++++++++++++++
 packages/engine/src/project-engine-manager.ts      |   2 +
 packages/engine/src/project-engine.ts              |   3 +
 packages/engine/src/project/project-runtime.ts     |   2 +
 packages/engine/src/runtimes/in-process-runtime.ts |   1 +
 19 files changed, 892 insertions(+), 31 deletions(-)

Fusion-Task-Id: FN-9464

Fusion-Task-Lineage: 60e005f3-28e0-472a-a713-bdbab85684be

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
…not have

Fork adaptation on top of the taken upstream FN-9464 (05c7ae5). Upstream also treats its
archived lane as terminal when judging mission delivery, but this build retired task archiving
under FN-9187: `LifecycleColumns` exposes only intake/hold/wip/review/complete, so
`taskLifecycle.archived` does not typecheck and no card can ever sit in an archived column.
`complete` is the sole terminal lane.

Keep this single divergence when re-taking FN-9464.
…ted TTL constant

RUFU-479 raised TASK_DIFF_STATS_CACHE_TTL_MS from 10s to 60s so the cache outlasts the client poll
period, but the route test kept advancing its fake clock by 10_001 and asserting a recompute. The
stale assertion has been red on main ever since: the cheapest way to satisfy it would have been to
restore the 10s TTL, which is the behaviour RUFU-479 deliberately removed.

The TTL is now exported and the test asserts both boundaries — still cached one poll period after a
miss, recomputed once past the TTL — so a future TTL bump moves the test with the source.
… approve-plan patch

RUFU-297 widened the approve-plan persisted patch to clear the approval hold in the same write, so a
hold whose gate evidence was just rewritten cannot survive as evidenceless. The route test still
asserted the old two-field shape with an exact-match assertion, so it was red on main. Asserting the
new field is stricter than the old expectation, not looser.
…6-10-05)

Makes origin/main an ancestor of our development line instead of continuing to
cherry-pick, so future syncs stop re-paying the same conflict cost. Upstream tip:
8b4f2f7. 62 conflicted files / 212 hunks.

Decisions that are not mechanical:

- Migration slot: upstream FN-9439 ships pull-request readiness as 0087. We already own
  0087 (OVERLAP_OWNER_FK_DEFERRABLE_REPAIR) and 0088 (REVIEW_LANE_LEDGER), and every
  production DB recorded those meanings, so the incoming migration is renumbered to 0090
  (0090_fn_9439_pull_request_readiness.sql, SCHEMA_BASELINE_VERSION -> 0090). Same
  convention that moved upstream 0086_fn_9429 onto 0089. Ordering is asserted as `>=`/`>`;
  only the highest slot keeps an exact equality, so the two sides cannot contradict each
  other in a PG-only file that local runs cannot see.
- Archiving: upstream re-introduced the archive subsystem we retired in FN-9187
  (`async archiveTask` in store.ts: upstream 1, us 0). Every `taskLifecycle?.archived` /
  resolveProjectColumnsForRoles(["archived"]) hunk is refused with an FNXC note, incl. the
  temp-worktree sweep's terminal-column set (complete-only). This is directional, so it
  will recur on every sync.
- anthropic-models.ts: upstream FN-9450 deletes the supplemental registration on the
  assumption the installed Pi catalog supplies Claude Opus 5.5 / Sonnet 5.5; our own
  FNXC:ModelCatalog records that the bundled Pi 0.86.1 catalog omits both rows. Kept ours
  and restored the fork's two supplemental-anthropic test files, because a kept behaviour
  must keep its tests. The sibling openai-codex / zai / grok supplemental sources and their
  tests were accepted as deleted - their subject is gone.
- project-engine.ts planner recovery: kept our shipped FN-9359-union-FN-429 in-place resume
  and refusal taxonomy, and restored the moveTaskToContainedBackwardTarget import that
  upstream deleted with its own call site. Upstream's further node-pinned continuation-seed
  refinement is deliberately NOT adopted; it is a lifecycle-authority question, not a
  mechanical merge. Tracked in RUFU-563.
- public TaskStore methods: three public signatures had narrowed to our side while the
  Impl kept upstream's wider ctx (updatePrReadiness*/releaseAwaitingPrChecksIfCurrentHead
  missing entirely, writeTaskWorkflowSelection missing options.taskUpdatedAt,
  applyPrMergedTransition missing externallyLanded/lifecycleLockHeld). Restored; the errors
  surfaced two packages away from the conflict, which is why this note exists.

Verification: tsc core/engine/dashboard 0 errors; pnpm test:gate green (207 + 509 + 75);
verify:fast PASS (65 steps, real /api/health 200); child-resolved test files green
(dashboard 257/257, engine 28 passed).

Known red, deliberately not "fixed": merge-orphan-durable-write-inventory-drift.test.ts
fails because upstream's curated manifest does not classify this fork's durable writers.
The manifest carries forensic fields (executionProof/axis2Final); inventing values to make
it green would falsify evidence, so it is left red and named in RUFU-563. PostgreSQL
migration application is unverified here (initdb unusable in this environment) - do not
deploy this merge until the 0090 path is proven against a real database.
…al PostgreSQL and repair what it broke

The merge was landed with the PostgreSQL lane unverified, because initdb cannot be provisioned
in this environment (missing libicui18n.so.60). A throwaway postgres:15 container reproducing
CI's own contract (FUSION_PG_TEST_URL_BASE=postgresql://postgres:postgres@localhost:5432) let the
PG-only suite run for the first time, and it found four things that local runs cannot see.

1. Migration ledger ordering (schema-applier.test.ts). STALE_REVIEW_CALLBACK_WAIVER_RECEIPTS_VERSION
   is re-issued locally at 0089 but kept the array position it inherited from upstream's 0086 hunk,
   so `ORDER BY version` (TEXT) disagreed with the fixture: received ...0087,0088,0089,0090 vs
   expected ...0075,0089,0076... The ledger fixtures now order by the constant's VALUE, and the
   non-numeric local-repair-mixed-0065 marker is last again in the automation-upgrade fixture.
   84/84 pass.

2. Upstream's "serializes concurrent schema appliers" was re-imported as a side effect of grafting
   ledger entries into the surrounding upstream region. This fork had documented it as
   not-importable (canonical-lineage-fixture bound); its expected ledger enumerates upstream's
   0074/0076/0079-0083/0085 slots, which this feature-reduced line never implements. Removed again,
   with the reason and what a re-import requires. serialized-DDL coverage remains via
   "queues schema DDL behind an active SQLite migration transaction".

3. assertBinaryNotOlderThanDatabase is now warn-only, adopted verbatim from upstream. Our own test
   already stated the contract ("a genuinely newer version never throws either — it's warn-only")
   while the source threw StaleBinarySchemaError, so an ordinary upgrade-then-rollback bricked every
   open and told the operator to upgrade a binary that is intentionally behind. No test asserted the
   throw; upstream relaxed it for the same forked-product-line reason.

4. store-safe-defaults.pg.test.ts asserted upstream's `archived...read-only` sentence. Archiving was
   retired in FN-9187 and this fork's refusal now reads "is deleted or historical - comments are
   read-only", so the write was correctly refused but the pattern could not match. The alternation
   keeps the invariant and does not resurrect retired vocabulary.

Refusal recorded, not adopted: upstream's 0085_drop_excluded_upstream_feature_schema.sql. Its own
header describes a reduced fork that "never implements 0079-0083: ... task-level human-approval,
pause, queue-boost columns" - which this fork DOES implement at 0080-0083 - and the migration drops
five project.tasks columns after copying them aside. Applying it here would drop columns in live use.
No 0085 file is tracked on this line and the ledger fixtures no longer expect it.

Verification (PostgreSQL 15.18 container, CI-shape URL): schema-applier.test.ts 84/84; core
test:pg-gate 13/13; renamed-board-reopen + store-safe-defaults 9/9; engine
workflow-lifecycle-live-e2e 24/24; migration-wiring-integrity 4/4; pnpm test:gate green with the PG
lane actually participating (19 static validators, 13 + 509 + 207 + 75 tests); tsc core 0 errors.
Two test-only upstream commits, three conflicts.

createTask-plan-approval-payload.test.ts: upstream replaced the source-layout guard with a
behavioural client-seam test (assert the serialized request body instead of scanning createTask's
text). That is the direction this repo's own rule demands - "Tests assert behavior, never source
text or comments" - so upstream's shape is adopted and our fork's FN-408 requirement is grafted
back in: humanPlanApproval is now sent and asserted forwarded, which the old source-text scan
could only prove loosely. Field existence was verified at tasks.ts:388/432 and task-core.ts:2256
rather than assumed.

api-auth.test.ts: both sides assert the same contract - our client already appends ?origin= to
/api/auth/status - but upstream builds the expectation with URLSearchParams instead of a hardcoded
literal, so theirs is kept. 50/50 green.

AgentDetailView.core.test.tsx: union of the two import lists (our AgentDetail + their
DiscoveredSkill). This file is quarantined on our line since 2026-09-07 (skill-badge suite-only
flake, register entry 18, sighting 2), which is why vitest reports "no test files found" for it;
it typechecks clean so the resolution is ready either way. Flagged rather than decided: upstream
FN-9506 is a root-cause fix for that exact flake (rescue evidence), while our deletion ratchet puts
a 28-day quarantine (limit 14) due for deletion. Deleting coverage and un-quarantining on borrowed
evidence are both worse than asking.

Verification: createTask payload test + api-auth 50 tests green; tsc dashboard 0 errors; tree
scanned with git grep for markers, and the unmerged list taken from
`git diff --name-only --diff-filter=U` rather than the truncated merge output.
… once from the production database shape

The re-numbering decision (upstream FN-9439 shipped at 0087, this fork owns 0087/0088 and recorded
0086-0089, so the incoming step re-issues at 0090) was only argued, never executed against the shape
that actually matters. A fresh-apply test cannot prove it: on an empty database nothing is missing,
so running 0090 proves only that the file parses.

Every applied fork database sits elsewhere: it recorded 0086-0089 with THIS line's meanings
(0087 overlap-owner FK repair, 0088 review-lane ledger, 0089 the re-issued FN-9429 receipts) and has
never seen 0090. The fixture reproduces exactly that - full apply, then drop the two readiness
columns the step owns and delete its marker - and asserts the deploy contract: the next open applies
once, lands readiness + readiness_provider, records exactly one marker row, and the open after that
is a no-op.

This is the evidence that was missing when the merge was landed as deploy-NOT-GO. schema-applier
85/85 on PostgreSQL 15.18; core test:unit-gate 207, test:pg-gate 13, tsc 0 errors.
…fuse upstream archive hunks

I recorded "this fork retired task archiving (FN-9187)" while refusing upstream archive-lane hunks
today. That citation is wrong and I am correcting it rather than letting it stand in the code.

Measured against both lines: FN-9187 is upstream's OWN archive hardening - "Bound auto-archive failure
retries", pre-filtering live lineage children "while honoring renamed archive lanes". Upstream never
removed archiving: it declares `archived` as a lifecycle role, lists it in TERMINAL_ROLES, exposes it
as the operator-facing `autoArchive` setting, and is still developing it (FN-9451 restore, FN-9466,
FN-9468). The absence of the role here is a FORK decision made in a misunderstanding.

The refusals themselves stay: `resolveProjectColumnsForRoles(store, ["archived"])` genuinely does not
typecheck on this line, which is a vocabulary gap, not proof of a removed feature. Comments now say
that, so the next reader does not treat a local gap as upstream intent.
… read queue

The queue bounded only traffic inside `api()`, so `activeReadCount()` measured 10 against a ceiling of 4 on
a cold board mount: five GET call sites fetched directly and competed in the same burst that `/tasks/page`
is supposed to win. Routed: ai-sessions list + detail, task agent logs, task detail (per retry attempt, so
the loop does not hold a slot across the gap), and report discussion categories.

The chat event stream deliberately stays outside. It is open for the life of a conversation, so queueing it
would convert a request-concurrency bound into a cap on open streams. The characterization test asserts a
never-settling queued read really does hold its slot forever, which is the reason for the exclusion.

Verification: read-queue-call-site-coverage.test.ts + bootstrap-read-scheduler.test.ts, 11 passed. Control:
putting `fetchTaskDetail` back on a direct fetch fails 3 of the new tests. Regression: 388 passed across the
8 client API suites plus both scheduler suites; the single remaining failure (`createTask` priority
serialization) is pre-existing on the base commit and was repaired on main in 5b03398.
Dashboard build and typecheck clean, eslint clean, patch changeset added.

Fusion-Task-Id: RUFU-479
(cherry picked from commit 71b94c5)
… aging them out

invalidatePath/invalidateAll had no production caller, so the 10s TTL was the only
freshness mechanism for a verdict that gates dormant-lease downgrades. Lifecycle
mutations now invalidate the exact checkouts they can dirty (every workspace member
repo), and an unattributable mutation invalidates all, since a missing verdict keeps
a blocker blocking while a stale "empty" can release live work.

This is step 1 of RUFU-487: cadence/TTL changes are only safe after this lands.

Fusion-Task-Id: RUFU-487
(cherry picked from commit e0c4d4d)
Resolutions, all read before resolving:
- scripts/lib/test-quarantine.json: union. Kept our AgentDetailView +
  TaskPlannerChatTab entries and upstream's executor-prompt entry; ledger is
  valid JSON (key `entries`, 5 items).
- PlanningModeModal.planning-flow.test.tsx: took upstream's
  clickEnabledPlanningButton("Next") over our local waitFor-on-enabled, because
  the helper awaits the same observable readiness and is already used elsewhere
  in the file. Our intent is preserved, the duplicated wait is not.
- suite-only-flakes-observed-register.md: took upstream's corrected root-cause
  sentence for the shared createFnAgent paragraph ("zero only in broad
  shard/multi-file execution") over our stronger claim, and kept all 83 of our
  appended register entries.

Verification (file-scoped):
- packages/engine plan-premise test file: 23/23 (unchanged by this merge)
- PlanningModeModal.planning-flow.test.tsx: 84/84 via `npx vitest run` from
  packages/dashboard; 83/84 once via `pnpm --filter` path.

Recorded, not appeased: the intermittent case is written into the flake
register as a first sighting with its exact name, invocation path and 1-in-4
frequency. No timeout widened, no retry added, no assertion relaxed.

Fusion-Task-Id: RUFU-569
A workspace project's root is a container of sibling repositories, not a git
repository, so the declared-base rung of the card git-identity ladder raised for
every premise-bearing card in such a project and the card was refused at
admission, promote, scheduler release, and event release alike.

When the root is not a git database, the base is retried against the member
repositories that exist under it, ordered by the card's own workspaceWorktrees
and then by name. A single-repo checkout never enters the new branch, so the
common case is untouched. Nothing resolves means it still fails closed, but the
refusal now names the attempted base, says the root is not a git repository,
lists the members searched, and separately reports a member the card named that
is absent from the workspace, so an unregistered sibling never reads as a base
that merely went missing.

Fusion-Task-Id: RUFU-570
Round 1 made the declared base resolve inside a workspace member repository,
because a workspace root is a container and not a git repository. Premise paths
are written from the project root, so a card stating
lager-manager/src/main/java/X.java was measured against a path that member
repository has never contained, and the true fact came back missing.

The premise path is now re-rooted at the member the identity resolved in, and
only when its first segment is exactly that member: a bare or other-member path is
still evaluated as written, because guessing which sibling a path meant is how a
premise stops being falsifiable. Only the git read moves; the violation keeps the
path the plan states, so a refusal-episode signature cannot shift under a running
card, and the reason names both spellings so an operator can tell which one is
wrong.

A re-rooted path that is still absent stays `stale`, not `unavailable`. The engine
did look, in the right repository at the right commit, and got a definitive "not
there" — the same fact that has always been stale for a single-repo card.
`unavailable` would claim it could not look, which releases the refusal with no
episode, no escalation, and no replan, so the card would be re-measured forever on
a plan nobody was ever asked to correct. `stale` routes the defect to the planner,
and the prompt fingerprint resets the refusal count once the plan changes.

Fusion-Task-Id: RUFU-570
…nd ratchet it

The review-lane ledger changeset named `0079_stas_205_review_lane_ledger.sql`, a file that does not exist on this
line: keeping `origin/main` an ancestor means incoming upstream migrations keep their slots and ours move to a free
later one, upstream took 0079 for FN-393, and the ledger landed as 0088. The number was prose, so neither
`check:changesets` (labelled field format) nor `schema-applier` (real files) could see it.

After a renumber a stale number is worse than no number: it resolves to a different, real migration owned by another
card. Proven both ways against fixtures - a valid reference passes, and a `0079_stas_205_*` reference against a tree
whose 0079 belongs to someone else fails naming the file. Wired into pretest, pretest:full and test:gate:static.
…heir concurrency

The RUFU-073 coalescer only helped callers that passed a selectionCache. Measured from two
live heap snapshots of the production dashboard, ~4 000 async frames were suspended on the
workflow-IR selection read because most of the ~150 call sites pass no cache, and that branch
was deliberately live-per-call and cached nothing. Every concurrent caller issued its own
SELECT and parked a frame that retained the batch of materialized tasks it closed over, which
grew the heap from 914 MB to 2.4 GB and OOM-killed the UI roughly hourly.

The new coalescer is per-query-lifetime: the in-flight entry is deleted in `finally` on both
success and failure, so a call made after a workflow edit still reads fresh and live-per-call
stands. A per-store bound of 16 keeps a pass over many distinct tasks from turning coalescing
into one query per row on a pool sized in the tens.

Tests assert the collapse (50 callers, 1 read), the non-retention (second call re-reads), the
bound (48 tasks, never more than 16 in flight), failure retryability, and that the caller-owned
cache path is untouched.

Fusion-Task-Id: RUFU-588
start() fires `void this.tick()` on a fixed interval and tick() awaits
`store.listRuns({ status: "queued" })`, with nothing preventing the next interval from firing
while the previous read was still suspended. A live heap snapshot of the production dashboard
at 25 minutes of process life held 1 125 suspended `listResearchRuns` frames and 1 130 `tick`
frames — roughly 19 minutes of stacked polls. The pile is self-reinforcing: a slower database
stacks more polls, which presses the connection pool harder.

The guard skips an overlapping pass instead of admitting a second read. A skipped pass loses
nothing: it would have read the same queued set, and the next interval reads again. The flag is
released on the throwing path as well, so a single failing read cannot stop polling for the
life of the process.

Tests assert the invariant rather than the repro: 20 minutes of intervals while a read is
suspended produce exactly one store read, polling resumes after it settles, and a failing read
does not wedge the guard.

Fusion-Task-Id: RUFU-588
GET /api/agents/stats answered 157 bytes after 7.62 s. Sampling
pg_stat_activity during one call showed two full project.tasks scans plus
per-card task_workflow_selection reads behind it; the project holds 565
rows / 42 MB while the payload is 157 bytes.

RUFU-378 already removed the per-card workflow-IR reads and set
derive:false + excludeLog:true, but the counter still materialized every
queued row to compute `.length`. It now uses countLiveTasks, the same SQL
COUNT(*) the board lane pager runs for its `total`, so the panel counter
and the pager total come from one primitive and neither hydrates a row.
The SQLite backend has no async layer and keeps the bounded column read.
Both fan-outs in buildBoardWorkflowsPayload were awaited inside for...of loops:
the per-card selection-read fallback and the per-workflow IR description, so a
request paid the sum of every round-trip. Characterized with a counting store:
120 cards with no batched reader produced 120 serial selection reads.

The batched selection read stays preferred and unchanged; this bounds the
degenerate path and parallelizes independent IR description while preserving
payload order.
The verdict-less pre-merge gate re-run cap never engaged: the strike counter read the task projection the caller handed it, and every board read the recovery lanes run answers `log: []`, so the budget reported zero strikes forever and each of RUFU-281's 11 markers was written as `rerun 1 of 3`.

- `countVerdictlessGateRerunAttempts` now counts `[verdictless-gate-rerun]` markers from the durable row via `store.getTask(task.id)` instead of the caller's `task`; the old `Array.isArray(task.log) ? task.log : hydrate` arm was dead code on the hot path because the projection always passed the array test.
- A rejected durable read is fail-closed (no `.catch`): swallowing to zero strikes is exactly the false evidence of a fresh budget this counter exists to bound, and all six production call sites already contain the rejection, so a flaky read seeds nothing instead of seeding forever.
- A `null` row stays decision-neutral (fake/legacy-store shape), while the real store's `TaskNotFoundError` / `TaskDeletedError` throw takes the fail-closed path — a card that no longer exists gets no seed.
- States the retained-window bound instead of assuming one: the durable activity log keeps its newest 1,000 entries (`DEFAULT_TASK_ACTIVITY_LOG_ENTRY_LIMIT`; `logEntryImpl` splices the front), so extreme log churn can retire an oldest marker and earn one further re-run — a bounded degradation versus the pre-fix per-pass reset. The exact per-(task, gate) durable counter needs new state plus a migration and is out of this task's scope; the unbounded `active-continuation` arm is RUFU-490 and the post-merge reseed arm is RUFU-502.
- Tests state strikes on the durable row and keep the board projection slim, so the cap is now a real reachability proof: a new self-healing-sweep case refuses the fourth re-seed, a `handleGraphFailure` case proves the cap bites through the graph-failure lane, fresh-card shapes (`null` row / no `log` / `log: undefined`) stay zero-strike, a projection-only strike cannot spend the budget, and the real `TaskNotFoundError` throw seeds nothing.
- Reconciles the wedge lane's store fake with FN-9429's durable stale-review-waiver receipt read (`getStaleReviewCallbackWaiverReceipts` + `getProjectId`) so the merge-admission case reaches its assertion instead of dying on a missing method.
- `docs/workflow-steps.md` names the durable row as the budget's source of truth and records the retained window; patch changeset added.

Files changed:
.../rufu-452-verdictless-rerun-budget-hydration.md |   7 +
docs/workflow-steps.md                             |   2 +-
.../src/__tests__/pre-merge-gate-reseed.test.ts    | 229 +++++++++++++++++++--
.../__tests__/unrun-pre-merge-gate-wedge.test.ts   |  23 ++-
.../verdictless-pre-merge-gate-rerun.test.ts       |  55 ++++-
packages/engine/src/merge/pre-merge-gate-reseed.ts |  62 +++++-
6 files changed, 353 insertions(+), 25 deletions(-)

Fusion-Task-Id: RUFU-452

Fusion-Task-Lineage: 0da412e2-d760-4b65-a011-1f53d724ae91

Co-authored-by: Fusion <noreply@runfusion.ai>
…it per call

CDP sampling of the live dashboard attributed 22.8 % of process CPU to child_process
spawn machinery. For an ordinary repository (a .git directory) the linked-worktree
resolver deliberately misses the on-disk parser and fell through to two synchronous
git rev-parse spawns on every call, and resolvePiExtensionProjectRoot is invoked once
per extension directory.

Results are memoised per resolved path: positive linked-worktree roots for the process
lifetime, negative answers for 60 s so a worktree created later is still found.

Fusion-Task-Id: RUFU-595
…s transition

The board monitor counted 429 capacity suspensions across 6 cards in 45 minutes,
and the audit store held 5,356 auto-merge finalize refusal rows from 7 cards in a
single day (61 from one card in 30 min). An engine loop re-states a persistent
refusal on every pass, and each restatement wrote an audit row — plus, on the
finalize path, two durable `store.logEntry` rows.

Measured 2026-10-07, in the same window: engine held 0 leases and made 1 board
move in 30 minutes. The volume was not board pressure; it was a handful of parked
cards restating themselves, and it made the monitor's `capacity_suspends` metric
read as a health signal when it was counting passes.

Reuse `createRepeatSuppressedLog()`, the house primitive for exactly this
("suppressed in the same window: log at debug instead"), which two hold-release
call sites already use. It had no clear/reset entry point, so add one: with a
reset, it is the transition detector this defect needs — the first pass into a
condition logs at level, later identical passes go to `debug()`, and any changed
signature logs again at full level. A suppression window that expires silently is
not a transition detector, which is why both notices pass an unbounded window and
key on state rather than time. The wait itself is untouched: the graph still
persists the continuation, only its restatement is bounded.

- RUFU-442: `task:workflow-run-suspended` + its log line, keyed on
  node/reason/boundary/continuation identity, cleared by any non-suspended
  disposition so a later wait reports afresh.
- RUFU-431: the finalize refusal audit row is bounded by the discriminating
  metadata the row itself carries (previousColumn, status, blockers, reason) —
  keying on the reason sentence alone collapsed a refusal in the default `done`
  lane and in a board's renamed complete lane into one row. Terminal outcomes
  (`…-reconciled`, successful finalize) are excluded and always write.

Bounding may never mask a state change: every test asserts both halves.

The finalize-path `logEntry` churn also disarmed RUFU-452's verdict-less-gate
re-run budget, which counts its strikes as markers in the per-task log that
retains only its most recent 1,000 entries — SANE-507 held 0 markers against
~30 real re-runs. Writing the merge-confirmed fast-path line on the transition
only restores that budget without moving it into a new column.

Fusion-Task-Id: RUFU-442
Fusion-Task-Id: RUFU-431

Also drops the unused `getRequiredPostMergeEvidenceDecision` import that the
RUFU-465 revert left in `auto-merge-finalization.ts`. That dead import fails
`pnpm lint`, which is a CI gate check; it was already red on `main` before this
change (verified in the primary checkout at 57d349c), and it is folded into
this commit rather than split because it sits in the same file the fix edits.
ischindl and others added 21 commits November 3, 2026 11:01
…wn card

Branch-conflict recovery's terminal park re-authored itself on every automatic pass, inflating its own retry budget past the declared bound and re-stamping updated_at so a dead card looked alive (measured 26 and 19 against a budget of 3).

- A terminal park persists no counter: buildBranchConflictRecoveryParkPatch drops recoveryRetryCount, and planBranchConflictRecoveryPass clamps only the REPORTED attempt at maxRetries + 1, never the stored value, so the remedy sentence can no longer read "after 26 bounded passes (budget 3)".
- New branchConflictRecoveryCounterShouldWrite(pass) is the single write guard on the counter arm — routing an empty patch through updateTask was itself an updated_at re-stamp.
- New branchConflictRecoveryParkAlreadyApplied(task, autoRecovery) gates the park write, its audit row, and its History line together at all four park sites (foreign-tip reject, PR-reclaim destructive-ambiguity, dirty-checkout hold, self-owned reclaim); the verdict is read from the LIVE row, never a caller's snapshot.
- Refusal notes now go through the durable windowed store.logEntryOnce seam — one History line per 6 h per (card, reason, branch tip) instead of one per sweep pass.
- Suppression lives only in the mutating write arms, never in front of inspectBranchConflict(), so a parked card is still re-inspected every pass and keeps its stale / stale-resolved / ghost-pointer / never-delivered release doors.
- BRANCH_CONFLICT_RECOVERY_EXHAUSTED_PAUSE_REASON moves to core beside its sibling reasons and joins the Retry allowlist in buildAutoPauseClearPatch, so the Retry the remedy sentence names actually lifts the pause and its dormant branch/worktree lease.
- Park remedy reworded to state the real disposition, and refuse sites now name their verdict (delivery verdict <kind>, reclaim verdict <class>, reclaim rationale destructive-ambiguity).
- Corrected the accounting module's contract comment and docs/architecture.md: the exhausted park IS re-admitted on purpose, and the retry bound no longer rides on the park.
- Tests: three new files (recovery accounting, terminal-park durability, foreign-reject History) plus extended retry-bound and core manual-retry-reset coverage; patch changeset added.

Files changed:
 .changeset/rufu-432-terminal-park-durability.md    |   7 +
 docs/architecture.md                               |   7 +-
 .../core/src/__tests__/manual-retry-reset.test.ts  |  47 +++
 packages/core/src/index.gate.ts                    |   1 +
 packages/core/src/index.ts                         |   1 +
 packages/core/src/tasks/manual-retry-reset.ts      |  24 ++
 .../branch-conflict-foreign-reject-history.test.ts | 205 +++++++++++++
 .../branch-conflict-recovery-accounting.test.ts    | 224 ++++++++++++++
 .../__tests__/branch-conflict-retry-bound.test.ts  |  45 ++-
 ...ranch-conflict-terminal-park-durability.test.ts | 332 +++++++++++++++++++++
 .../src/auto-recovery-handlers/branch-worktree.ts  |  12 +-
 packages/engine/src/executor/run-implementation.ts |  15 +-
 .../executor/worktree-branch-conflict-handle.ts    |  65 ++--
 .../branch-conflict-recovery-accounting.ts         | 181 ++++++++++-
 packages/engine/src/self-healing.ts                | 195 ++++++++----
 15 files changed, 1268 insertions(+), 93 deletions(-)

Fusion-Task-Id: RUFU-432

Fusion-Task-Lineage: 79ec4c91-4309-4ac2-a876-fb64b1da6974

Co-authored-by: Fusion <noreply@runfusion.ai>
…leaked temp scratch

Name the exhausted resource pool behind every ENOSPC failure record, so a tmpfs with 0 free inodes but 52 G of free bytes is no longer recorded as a full disk.

The classification lives in one non-throwing core helper: it statfs'es the failed creation's target, then its nearest existing ancestors (a creation failure's own path was never created, so the literal target answers ENOENT), and appends `temp-fs class: inode-exhausted|bytes-full` with the inode/byte counts and the path actually measured. It rides the failure records each lane already writes instead of acquiring a new lifecycle owner, and guarantees the literal `ENOSPC` token survives so the honest-blocked classifier's host-external match cannot be downgraded to an AI-repairable failure.

- core: new `temp-fs-diagnostics.ts` (`classifyTempFs`, `annotateEnospcFailure`, `annotateEnospcError`, `tempFsClassSuffix`, `isEnospcFailureText`, `ENOSPC_TOKEN_PATTERN`), published from BOTH `index.ts` and `index.gate.ts` so the engine-core gate lane resolves the symbols.
- engine (merge): `createAiMergeScratchDir` wraps clean-room creation — including `resolveAiMergeRoot`'s own root `mkdirSync`, previously outside the guard — so the merge park and merge-retry History line carry the class; non-ENOSPC failures return the original error object untouched.
- engine (verification): `fn_run_verification` detects errno 28 across the whole capture (spawn warnings + stderr + stdout) and appends the class to the `Failure summary:` block; new `tempFsDeps` seam keeps the assembly site covered.
- engine (boot): `guardCreationError` throws the new `EngineStartupEnvironmentError` for a provable errno-28 guard-creation failure instead of `EngineAlreadyRunningError`, whose consumer treats that class as proof another process owns the project and skips it silently; `EADDRINUSE` keeps its existing error and message.
- engine (self-healing): the stale-tmpdir sweep now reaps Fusion's own leaked scratch directories under the admitted `TEMP_SCRATCH_SWEEP_PREFIXES` gate at a dedicated `TEMP_SCRATCH_REAP_AGE_MS` (24 h) floor no early-release leg can shorten, and skips `git worktree remove` for entries that never had a Git registration; process-lifetime artifacts and third-party temp content are never touched, and the sweep stays one non-recursive prefix-filtered read per root.
- tests: new core temp-fs suite plus five engine suites (classifier text-compat with `classifyExternalObstacle`, per-record surfacing, merge/verification/boot wiring, sweep admission and floors).
- docs + changeset: `docs/diagnostics.md` gains the bytes-full vs inodes-exhausted runbook (read `df -h` and `df -i` separately) and the TMPDIR redirect for test lanes on an inode-starved host; patch changeset for `@runfusion/fusion`.

Files changed:
 .changeset/rufu-397-tmpfs-inode-exhaustion.md      |   7 +
 docs/diagnostics.md                                |  68 +++++
 .../core/src/__tests__/temp-fs-diagnostics.test.ts | 304 +++++++++++++++++++
 packages/core/src/index.gate.ts                    |  23 ++
 packages/core/src/index.ts                         |  23 ++
 packages/core/src/temp-fs-diagnostics.ts           | 323 +++++++++++++++++++++
 .../enospc-classifier-text-compat.test.ts          | 106 +++++++
 .../src/__tests__/enospc-temp-fs-surfacing.test.ts | 182 ++++++++++++
 .../__tests__/merger-ai-enospc-diagnostics.test.ts | 205 +++++++++++++
 .../run-verification-enospc-diagnostics.test.ts    | 151 ++++++++++
 .../self-healing-temp-scratch-sweep.test.ts        | 221 ++++++++++++++
 .../engine/src/execution/run-verification-tool.ts  |  43 ++-
 .../engine/src/healing/self-healing-constants.ts   |  59 ++++
 packages/engine/src/merge/merger-ai.ts             |  52 +++-
 .../engine/src/project/engine-singleton-lock.ts    |  72 ++++-
 packages/engine/src/self-healing.ts                |  94 ++++--
 16 files changed, 1903 insertions(+), 30 deletions(-)

Fusion-Task-Id: RUFU-397

Fusion-Task-Lineage: 7b480418-781f-483c-883a-4af59c0d175e

Co-authored-by: Fusion <noreply@runfusion.ai>
decidePlannerRecovery injected guidance at the reviewer stage for any
signal, including the `progressing` an open review reports. A healthy
signal also clears the controller attempt registry on every tick
(FN-7577), so the three-attempt budget never filled and the overseer
posted a steering comment on every 45 s tick. One task collected 22,499
copies in comments and steering_comments over twelve days, and each
append rewrote both arrays and the search_vector GIN entries at ~360 ms.

The reviewer stage now steers only on `stuck` or `blocked`, matching the
executor/workflow-gate fall-through. Tests that pinned injection on a
progressing review now use a problem signal, and new cases cover healthy
and human-wait reviewer signals plus repeated controller ticks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d0642e1)
Gating the reviewer branch on stuck/blocked let a reviewer snapshot with
signal `failed` fall through to the executor branch, which requests a
targeted fix or a step retry. Neither means anything for a review, and
before the gate a failed review steered. `failed` steers again; it does
not reset the attempt budget, so steering stays bounded.

The architecture guide's decision rules now match the code: rule 4 names
the reviewer problem signals, and rule 6 says healthy executor and
workflow-gate signals are a no-op, which has been true since the
2026-07-05 change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 5f04eee)
…only on a problem signal

Adopted from upstream PR Runfusion#3725 via cherry-pick -x, so flexi767 stays the
author of both commits. Our planner-recovery.ts had the unguarded branch this PR fixes: at stage 'reviewer' it
returned inject_guidance for ANY signal, including the 'progressing' the overseer reports for a review that is
simply open. Measured here on 2026-11-03: 24h of overseer:intervention rows on our board carried executor/merger
stages only (160 observe / 45 inject-guidance / 36 retry) and zero reviewer-stage rows, so we were not bleeding
from it yet - the mechanism was simply live in our source.

Verified on the merged tree: packages/core planner-recovery + planner-confirmation 35/35 passing; typecheck exit
0; eslint on the changed files 0 errors. planner-overseer-intervention-wiring.test.ts needs PostgreSQL and fails
with password auth in a fresh worktree - it fails identically on plain main, so it is an environment gap, not a
regression (and is the subject of RUFU-869).
A card whose Plan Review had already passed re-ran its planning prologue on every automatic re-entry, because each authority that decides where a resumed run starts trusted the column or the IR pin as a naked claim and none of them read the card's own gate evidence.

Key changes:

- Add `resolveResumePlacement` in `packages/engine/src/workflows/resume-placement.ts` as the single placement authority: a gate whose LATEST step row is `passed`, satisfies core's `isPlanReviewSatisfied`, or carries an audited operator `bypassedBy` waiver counts as released, and the run enters the IR's first main-path node strictly after it (`parse` for the built-in coding IR). A collateral archive (`remediationArchivedAt` with no `bypassedBy`), `pending`, `failed`, REVISE, superseded, and an absent row release nothing.
- Wire that one authority into the re-entry paths that previously guessed: the graph executor and its task runner, the executor's stuck-requeue / preserve-progress retry lane in `execute-workflow-graph.ts`, and self-healing's advanced-triage pin re-home.
- Add `pinContradictedByGateEvidence` so a pin is refused rather than trusted when the gate preceding it has positive evidence against it; absence is deliberately not contradiction, and an unjudgeable pair (v1 IR, unknown node, unreachable rework target) stays unchallenged so no false alarm is born.
- Add the `task:resume-placement-mismatch` run-audit event and the `reconcile-pin-column-coherence` sweep (startup + maintenance batch 1) so a card nobody dispatches reports a pin/column disagreement too; the KTD-2 `pin(in-progress)/column(todo)` signature is classified as the legal `pin-at-release-seam` wait, never as incoherence.
- Preserve the two-actor release rule: the graph records the ready-for-release seam and moves no column, leaving `execution/hold-release.ts` the sole `hold -> wip` mover.
- Add five test files plus a shared `resume-placement-harness.ts` fixture covering the resolver's data states, the two-actor composed chain, the re-queue authorities, and pin/column coherence; repair the pre-existing tests that encoded the superseded placement behavior.
- Document the resume-placement rule and the two-actor seam in AGENTS.md, docs/architecture.md, docs/run-audit.md, docs/workflow-steps.md, and the docs/testing.md surface-enumeration checklist; add a patch changeset for `@runfusion/fusion`.
- Merge repair: the three seam-park assertions in the new tests now expect `reason: "scheduler-owned-seam"`, main's RUFU-372 relabel of the hold-to-wip seam. The release contract is untouched — the continuation's `waitReason` stays `capacity`.

Files changed:

```
 .changeset/rufu-300-resume-after-passed-gate.md    |   7 +
 AGENTS.md                                          |   1 +
 docs/architecture.md                               |  10 +
 docs/run-audit.md                                  |  53 ++
 docs/testing.md                                    |   1 +
 docs/workflow-steps.md                             |   2 +
 .../src/__tests__/executor-step-session.test.ts    |  14 +-
 ...xecutor-stuck-requeue-preserve-progress.test.ts |  50 +-
 .../__tests__/fixtures/resume-placement-harness.ts | 184 +++++++
 ...hold-release-resume-after-released-gate.test.ts | 332 ++++++++++++
 .../src/__tests__/pin-column-coherence.test.ts     | 387 ++++++++++++++
 .../task-done-refusal-x-invariant.test.ts          |  18 +-
 ...-authorities-resume-after-released-gate.test.ts | 276 ++++++++++
 .../resume-placement-after-passed-gate.test.ts     | 266 ++++++++++
 .../engine/src/__tests__/resume-placement.test.ts  | 449 +++++++++++++++++
 .../src/__tests__/scheduler-trait-dispatch.test.ts |  27 +-
 .../self-healing-principal-held-planning.test.ts   |   8 +
 .../__tests__/unrun-pre-merge-gate-wedge.test.ts   |  13 +
 .../verdictless-pre-merge-gate-rerun.test.ts       |  13 +
 .../workflow-file-scope-continuation-wake.test.ts  |   3 +
 .../workflow-graph-optional-group.test.ts          |  15 +-
 .../engine/src/executor/execute-workflow-graph.ts  |  78 ++-
 packages/engine/src/self-healing.ts                | 180 +++++++
 packages/engine/src/workflows/resume-placement.ts  | 560 +++++++++++++++++++++
 .../src/workflows/workflow-graph-executor.ts       |  49 +-
 .../src/workflows/workflow-graph-task-runner.ts    |   8 +
 26 files changed, 2987 insertions(+), 17 deletions(-)
```

Fusion-Task-Id: RUFU-300
Fusion-Task-Lineage: 080e690a-ef04-44cf-b4f4-c93a68ea659f
Co-authored-by: Fusion <noreply@runfusion.ai>
…ions

The merge-lane unrun-gate arm emitted `task:merge-unrun-pre-merge-gate-rerouted` on every pass it evaluated a
card, including when it refused for the identical reason. Measured on this board 2026-11-03: RUFU-369 produced
23 rows in 12 minutes (one every ~30 s), all `reason: "active-continuation"`, because its own review attempt
was parked in `running` while the merge door kept re-evaluating the card. Over 24 h: RUFU-386 57 rows,
RUFU-397 25, RUFU-450 22, RUFU-439 17, RUFU-451 16. Every restatement is also a merge-queue write, and the
flood buries the `seeded`/`verdictless-seeded` rows that describe actual state changes.

RUFU-431 already solved this class and stated the rule — a refusal is a STATE, recorded on the transition into
(task, type, reason) — but its sweep gated only the surfaces sharing `recordFinalizationAudit`, so the
merge-gate site was never covered. This closes that gap by reusing the existing seam rather than adding a
second notice-state machine: `shouldEmitUnrunPreMergeGateNotice` returns true for a real re-seed (the card
changed) and otherwise defers to `shouldRecordFinalizationRefusal`, with the named gate and the required-gate
count folded into the signature so a refusal that moved gates, or gained a second missing gate, is not
swallowed as a repeat.

Surface enumeration: the guard is one expression at the single merge-gate producer of that mutation type
(`project-engine.ts`); the other three producers live in `self-healing.ts` (startup + maintenance-batch passes,
bounded cadence) and `executor/handle-graph-failure.ts` (per graph failure, not per poll) and are deliberately
untouched. No test encoded the old repeated-emission behaviour — nothing in `packages/engine/src/__tests__`
asserts on `source: "merge-gate"`, so no stale assertion had to be retired.

Verified: new invariant test across all seven refusal reasons + both seed reasons 8/8 passing; the three
existing suites that assert this mutation type (`unrun-pre-merge-gate-wedge`, `verdictless-pre-merge-gate-rerun`,
`merge-not-run-gate-wedge`) plus `repeat-refusal-audit-notice` all green — 5 files, 69 tests; `typecheck` exit 0;
eslint 0 errors; `check:changesets` exit 0.
…ransitions

Fixes the refusal flood measured on RUFU-369 (23 rows/12 min) and five other cards at 16-57 rows/24 h, using
RUFU-431's existing bounded-refusal seam. Deploy still pending.
…lity

A reviewer-only lane was woken — and opened a full session — on cards whose lifecycle cannot carry an open `code-review` gate, because the heartbeat wake revalidated only what a lane may ever touch, never whether this card owes this lane work right now. Wake-time bind revalidation is now the single wake-admission owner for both questions.

- core: extract the durable review-verdict / exclusion predicates (`hasRecordedCodeReviewVerdict`, `carriesAuthoredReviewOutcome`, `isCodeReviewExcluded`, `hasNoCommitReviewExclusion`) into `workflows/review-verdict-state.ts` so the review-dispatch sweep and the wake seam answer from one state table, and admit them plus `CODE_REVIEW_GROUP_ID` to the gate barrel — a fixture-only missing export had been letting 7 gate assertions pass on `undefined === undefined`.
- engine: add `agents/wake-bind.ts` — reviewer-only lane classification, candidacy resolution (recorded verdict, `noCommitsExpected`, zero board evidence, a lane the card's own workflow does not call review-class), wake-text derivation, and a fresh-vs-carried comment-id provenance resolver.
- engine: route every heartbeat task resolution (assignment, on-demand, timer, automation) through `evaluateWakeTaskBind`; its capability half delegates to `evaluateWakeCapabilityBind`, so a review-purpose wake keeps RUFU-766's reviewer bar. A withheld wake reuses the deduped lane-capability decline (`code` now joins the audit dedup key, `review-candidacy-mismatch` vs the capability codes) and never moves, fails, strikes, or unbinds the card.
- engine: comment context is advertised as carried context only when this wake actually received it, and a carried id no longer re-arms the next wake's freshness gate.
- engine: widen review-dispatch bucket E1 to the card's own `noCommitsExpected` authorization.
- merge reconciliation with main's RUFU-766 / RUFU-268: the new wake test's fake store echoes `startHeartbeatRun`'s initial row metadata (run ownership moved into that first durable write), and the "single purpose-aware entry point" source guard now scans the whole wake seam (agent-heartbeat + wake-bind) instead of one file, keeping its ban on a direct `evaluateImplementationTaskBind` in the wake path.

Files changed:
 .changeset/rufu-369-reviewer-wake-candidacy.md     |   7 +
 docs/agents.md                                     |  29 ++
 docs/architecture.md                               |  28 ++
 docs/run-audit.md                                  |  12 +-
 docs/testing.md                                    |   2 +
 packages/core/src/index.gate.ts                    |  18 ++
 packages/core/src/index.ts                         |   2 +
 packages/core/src/workflows/index.ts               |   1 +
 .../core/src/workflows/review-verdict-state.ts     |  97 +++++++
 .../heartbeat-reviewer-wake-candidacy.test.ts      | 321 +++++++++++++++++++++
 .../review-lane-capability-refusal.test.ts         |  17 +-
 packages/engine/src/__tests__/wake-bind.test.ts    | 190 ++++++++++++
 packages/engine/src/agent-heartbeat.ts             | 156 ++++++++--
 packages/engine/src/agents/wake-bind.ts            | 270 +++++++++++++++++
 .../engine/src/scheduling/review-dispatch-sweep.ts |  73 ++---
 15 files changed, 1147 insertions(+), 76 deletions(-)

Fusion-Task-Id: RUFU-369
Fusion-Task-Lineage: 6f863863-7a9f-4023-af45-6f5ff404fa69
Co-authored-by: Fusion <noreply@runfusion.ai>
…ntent

A second reader for a proof RUFU-337 made storable but never consumed: an approved, zero-commit-authorized card whose diff cannot be computed because its worktree and branch are gone is now finalized commit-free instead of refusing forever.

- `evaluatePreMergeApprovals` / `evaluatePreMergeAdmissionDecision` now read `task.noCommitsExpected` off the row (not via an option, which `getTaskHardMergeBlocker`'s fixed whitelist would silently drop) and, for a singular diff whose unavailability reason is `missing-worktree-or-base` beside an approval carrying the canonical `empty-review-input:v1` marker, return `approved` instead of `unprovable-content`. `git-diff-too-large` and `git-diff-failed` keep refusing byte-for-byte, as does every card without its own zero-commit authorization.
- New core primitives in `zero-commit-landing-proof.ts` — `CORE_EMPTY_REVIEW_INPUT_FINGERPRINT`, `CORE_MISSING_CHECKOUT_DIFF_REASON`, `hasEmptyReviewInputApproval`, `evaluateZeroCommitEmptyInputContentProof` — hold the waiver's fact conjunction (empty-input marker + row-level authorization + a gate the workflow actually required + the one checkout-absent diff reason), exported from `@fusion/core` so the engine/core mirror is pinnable from a package that sees both.
- `SelfHealingManager.dischargeZeroCommitReview`, reached only from the `not-landed` arm of `reconcileLandedReviewTask` after every shared fence passed: branch proven gone + row authorization + approval parity over a `captureMergeContentDescriptor` read through `getTaskHardMergeBlocker` + `enforceZeroCommitLandingProof` disposition `allow`. It finalizes commit-free (`mergeConfirmed`/`noOpMerge`, deliberately no `commitSha`) under the same compare-and-set fence and moves the card through the shared complete-lane `moveTask`.
- New reconcile result arm `zero-commit-discharged` (`basis: "empty-input-approval"`) and guard source `self-healing-reconcile-commit-free`; `fn task reconcile` prints a "Discharged … no commit authored" sentence, the dashboard reconcile route returns 200 for it, and the sweep counts it as recovered so the FN-9304 `-unproven` row simply stops being written.
- Changeset plus regression coverage replaying the RUFU-365 wedge across all four surfaces: core pre-merge gate, engine zero-commit finalize, CLI reconcile output, dashboard reconcile route.

Files changed:
 .../rufu-384-zero-commit-empty-input-finalize.md   |   7 +
 .../__tests__/task-reconcile-commit-free.test.ts   | 154 ++++++++
 packages/cli/src/commands/task.ts                  |  13 +
 .../src/__tests__/pre-merge-approval-gate.test.ts  | 153 ++++++++
 packages/core/src/index.ts                         |   2 +-
 packages/core/src/merge/pre-merge-approval.ts      |  43 ++-
 .../core/src/merge/zero-commit-landing-proof.ts    |  97 +++++-
 .../__tests__/task-reconcile-commit-free.test.ts   | 109 ++++++
 .../src/routes/register-task-workflow-routes.ts    |   8 +-
 .../zero-commit-empty-input-finalize.test.ts       | 388 +++++++++++++++++++++
 .../src/merge/zero-commit-finalization-guard.ts    |  10 +
 packages/engine/src/self-healing.ts                | 196 ++++++++++-
 12 files changed, 1171 insertions(+), 9 deletions(-)

Fusion-Task-Id: RUFU-384

Fusion-Task-Lineage: 6e88532a-3c65-447d-805f-fbc788a5cc41

Co-authored-by: Fusion <noreply@runfusion.ai>
… tools

Squashed RUFU-281: every coordinator write tool now either honours a named target or refuses it under a named code, so a supplied `task_id` is never silently dropped onto the session-bound card — the measured defect where `fn_task_log` with `task_id:"RUFU-275"` returned success while the entry landed on the caller's card.

Key changes:
- New `packages/engine/src/task-write-target.ts` seam (`resolveWriteTarget`, `guardBoundWriteTarget`, `writeTargetErrorResult`) with two dispositions (`honours` / `refuses`) and three refusal codes (`cross-card-target`, `conflicting-targets`, `unsafe-target-id`); a refusal is an `isError` tool result raised before any store call so zero bytes are written, and a named id is validated before it can reach a `join(tasksDir, id)`.
- `fn_task_log`, `fn_task_document_write`, and `fn_task_document_read` declare the shared `crossCardWriteTargetParam` and act on the named card, keeping the bound card as the omitted-target default.
- Bound-write tools read the undeclared target and refuse a foreign one before any store, worktree, planning-lock, or gate access: `fn_task_update`, `fn_task_done`, `fn_task_prompt_write`, `fn_task_file_scope_add`, `fn_review_dispute`, `fn_install_worktree_dependencies`, `fn_run_verification`, and `fn_report_build_failure`.
- `fn_report_build_failure` extracted out of merger.ts's `runAiAgentForCommit` into exported `createReportBuildFailureTool` so the merge lane's build verdict is guarded at a testable seam (recorded File Scope clarification — no tool added or removed).
- Four already-resolving lanes (`fn_task_logs_read`, `fn_artifact_register`, `fn_workflow_select`, `fn_task_promote`) are documented COMPLIANT rather than given a second, drift-prone seam; target-forced chat, planning, and pi/extension lanes are documented as having no ambient card to redirect onto.
- Tests: seam unit tests, a per-tool conformance matrix, gating invariance, agent-tools write-target coverage, and a real-store pg test for the incident shape; the durable-write inventory fixture is re-pinned for the 3 moved call sites and one stale old-contract assertion is reconciled.
- Docs: `docs/agents.md` and `docs/cli-reference.md` state the per-lane write-target contract; minor/security changeset for `@runfusion/fusion`.

Files changed:
 .changeset/rufu-281-coordinator-write-target.md    |   7 +
 docs/agents.md                                     |   6 +-
 docs/cli-reference.md                              |   2 +-
 .../src/__tests__/agent-tools-write-target.test.ts | 448 +++++++++++++++++++++
 .../__tests__/cross-card-write-target.pg.test.ts   | 176 ++++++++
 .../merge-orphan-durable-write-inventory.json      |   6 +-
 .../__tests__/plan-prompt-write-surfaces.test.ts   |  10 +
 .../engine/src/__tests__/task-write-target.test.ts | 119 ++++++
 .../src/__tests__/write-target-conformance.test.ts | 213 ++++++++++
 .../write-target-gating-invariance.test.ts         | 114 ++++++
 packages/engine/src/agent-tools.ts                 | 145 ++++++-
 .../engine/src/execution/run-verification-tool.ts  |  11 +
 .../src/executor/create-review-dispute-tool.ts     |   9 +
 .../engine/src/executor/create-task-done-tool.ts   |  10 +
 .../engine/src/executor/create-task-update-tool.ts |  12 +
 .../engine/src/executor/shared-worker-tools.ts     |  10 +
 packages/engine/src/merger.ts                      |  55 ++-
 packages/engine/src/task-write-target.ts           | 212 ++++++++++
 18 files changed, 1534 insertions(+), 31 deletions(-)

Fusion-Task-Id: RUFU-281

Fusion-Task-Lineage: cc091f41-5c3d-4558-a6d8-5139a898d2d5

Co-authored-by: Fusion <noreply@runfusion.ai>
…delivery surface from git

A landed card whose post-merge evidence can no longer be produced refused
finalize on every pass forever: 64 refusals in three days on one board while the
graph sat on a runnable `merge-gate` and the overseer only observed. The refusal
is bounded as a state and the producer surface is derived from git facts.

Landed manually at operator instruction (the review attempt recorded no gate
row). Note: `confirmed-merge-must-finalize.test.ts > resumes required closeout
after manual landing` is red on main BEFORE and AFTER this commit — tracked
separately, this landing does not regress it.

Fusion-Task-Id: RUFU-450
…rom a replacement

Squash of `fusion/rufu-233`: the agent-log text-row character-loss class was resolved disk-first (the read route and the JSONL store are proven append-only and innocent), and the layer that does lose characters — the stream capture seam — is fixed.

Key changes:
- `assistant-text-capture.ts` branch 4 now separates the two shapes that reach it: a REVISION of the block being streamed versus a REPLACEMENT (a second `${kind}_start` for a block index already delivered inside the same message).
- New `novelSpan()` / `commonPrefixLen()` hand an append-only sink only the text past the point where the block stops agreeing with what the sink already holds, so a revised block no longer re-writes its own head (`All done.All done.`, `BashBash is restricted`).
- A revision that trims back to an already-held prefix hands nothing over and must not pull the ledger cursor down; a mid-text revision records the block’s claim, not the row’s bytes, so the terminal re-assertion adds nothing.
- New `startAnnouncesReplacement()` makes a replacement block deliver whole regardless of length, repairing the clipped-head and shorter-replacement total-loss shapes (RUFU-234 regressions).
- `execute-workflow-step.ts` keeps the nullish operator-language guard already landed on main (RUFU-707) with the executor-lane fidelity pin added to its comment; the harness mock in `executor-test-helpers.ts` now spreads the real `step-session-executor` module instead of enumerating exports, so a new export cannot kill every case in the harness.
- Fidelity coverage: byte round-trip across a real `agent-log.jsonl` boundary, streamed-text fidelity tests for the heartbeat, triage and executor lanes, adversarial operator-shaped stream shapes, and a replay of the reported holed rows through the seam (`RUFU-196-holed-rows.json`).
- The heartbeat lane’s structural scan allowlists RUFU-268’s memory-only idle-preflight capture (it persists nothing) so the forwarding ratchet still fires for a hook that forgets the logger.
- Docs: new solutions article recording that the agent-log read route is append-free (falsified premise), extended trusted-delta diagnosis, one storage.md line; two patch changesets.

Files changed:
 .changeset/rufu-233-agent-log-text-fidelity.md     |   7 +
 ...fu-233-step-session-language-directive-crash.md |   7 +
 .../agent-log-read-route-is-append-free.md         | 126 +++++
 ...e-trusted-delta-against-stale-block-snapshot.md | 129 +++++
 docs/storage.md                                    |   1 +
 .../agent-log-round-trip-fidelity.test.ts          | 373 ++++++++++++++
 .../assistant-text-capture-fidelity.test.ts        | 550 ++++++++++++++++++++-
 .../evidence-replay/RUFU-196-holed-rows.json       | 127 +++++
 .../__tests__/executor-step-text-fidelity.test.ts  | 245 +++++++++
 .../engine/src/__tests__/executor-test-helpers.ts  |  84 ++--
 .../src/__tests__/heartbeat-text-fidelity.test.ts  | 314 ++++++++++++
 .../src/__tests__/triage-text-fidelity.test.ts     | 234 +++++++++
 packages/engine/src/agent-heartbeat.ts             |   2 +
 .../engine/src/execution/assistant-text-capture.ts | 137 ++++-
 .../engine/src/executor/execute-workflow-step.ts   |   5 +-
 15 files changed, 2269 insertions(+), 72 deletions(-)

Fusion-Task-Id: RUFU-233
Fusion-Task-Lineage: 4d2a15ed-9f8a-4a69-9739-d712f62acf29
Co-authored-by: Fusion <noreply@runfusion.ai>
…al line per card

One arm of `resumeMissingPostMergeGate` folded three different withholdings into
`engine-paused`, so a board running with auto-merge Off (or with a manual PR open)
produced audit rows and log lines claiming a pause that did not exist. RUFU-370's
contract is that a withheld arm names its own condition, so the arm is now three:
`engine-paused`, `auto-merge-off`, `manual-pr-open` — all still non-terminal, all
still withholding the same way. No behavior change in what gets refused.

Separately, the History-line churn bound in `auto-merge-finalization.ts` fell back to
a module-level map keyed by the episode signature alone, which names no card: two
cards refusing the same gate for the same reason shared one bound and the loser got
no History line at all — a card with a withheld finalization and no explanation on
it. The `logEntryOnce` path was always per-card, so the fallback is now keyed by the
card too. Proven by a test that fails without the fix (second card received 1 line
where the first received 2).

The fixture also stopped sharing one task id across cases, since the per-card bound
could not be tested at all while two cases were literally the same card.

Fusion-Task-Id: RUFU-873
…ike budget

A withheld file-scope delivery is a wait, not a broken checkout — stop charging it acquisition strikes and make the resulting park recoverable.

- Heartbeat worktree acquisition now classifies `OverlapResumeSynchronizationError` ahead of the broken-checkout budget: no strike consumed or cleared, the exhaustion writer is never called, and the card parks once in its rebound lane with the bare recoverable sentence (`preserveProgress`/`preserveStatus`, the shape `fn_task_retry` clears).
- New `executor/overlap-acquisition-park.ts` records one bounded `task:overlap-acquisition-parked` row per (task, episodes, reason) episode via `emitBoundedRunAudit` (ids/counts/fixed enums only) and logs an operator-facing line naming the blocker rather than the mechanism.
- The registration a refused acquisition leaks is released through the proof-gated defensive path: new `RemovalReason.AcquisitionRefused` added to `DEFENSIVE_REMOVAL_REASONS`, never a force reason; pointers clear only on a proven removed/absent checkout, so a preservation refusal keeps both and can never destroy work or orphan a registration.
- `OverlapResumeSynchronizationError` now carries structured `OverlapRefusalArtifacts` (`episodeIds`, `blockerTaskIds`, `repositories`, `missingEvidence` from a closed enum); messages are unchanged so the existing sentence-matching contract holds.
- `isRecoverableOverlapWaitFailure` strips the `Worktree acquisition failed after N heartbeat attempts…` wrapper by SHAPE before its exact comparison, so the literal row production carried is resumable while a genuine broken-checkout message stays unrecoverable.
- `reconcileReleasedOverlapWaits` derives resume lanes from column traits (`countsTowardWip` + every `hold`/`intake` column via `columnsWithFlag`, since Hold is non-unique) instead of one WIP id — previously it refused exactly where the park lives, so a card stayed failed after its predecessor landed.
- Workspace per-repo acquisition hands out `undefined` for the three overlap persistence methods it cannot call under the held task lock; that honest absence is the signal for `deferPersistence`, keeping analysis and every refusal identical while the enforcement point outside the lock writes the receipt.
- Regression coverage: inverted contract for the bounded overlap park, delivered-file evidence acquisition retry/registration leak, and self-healing release-wake resume from the dwell lane.
- Docs: architecture invariant for withheld-work-as-wait, `task:overlap-acquisition-parked` run-audit prose entry, and a `docs/solutions/reliability/` write-up of the compounding failure.

Files changed:

 .changeset/rufu-373-overlap-acquisition-park.md                 |   7 +
 docs/architecture.md                                            |   6 +
 docs/run-audit.md                                               |   2 +
 .../reliability/delivered-file-evidence-acquisition-wedge.md    | 102 ++++++++++++++
 packages/core/src/tasks/overlap-wait-release.ts                 |  22 ++-
 packages/engine/src/__tests__/agent-heartbeat-worktree.test.ts  | 203 +++++++++++++++++++++++++++
 .../engine/src/__tests__/overlap-resume-entry-points.test.ts    | 157 ++++++++++++++++++++-
 .../src/__tests__/self-healing-overlap-release-wake.test.ts     | 208 ++++++++++++++++++++++++++++
 packages/engine/src/agent-heartbeat.ts                          |  51 +++++++
 packages/engine/src/executor/overlap-acquisition-park.ts        | 161 +++++++++++++++++++++
 packages/engine/src/executor/overlap-resume-gate.ts             | 108 +++++++++++++--
 packages/engine/src/self-healing/released-overlap-waits.ts      |  59 +++++++-
 packages/engine/src/util/run-audit.ts                           |   9 ++
 packages/engine/src/worktree/worktree-acquisition.ts            |  23 +++
 packages/engine/src/worktree/worktree-backend.ts                |   9 ++
 15 files changed, 1105 insertions(+), 22 deletions(-)

Fusion-Task-Id: RUFU-373

Fusion-Task-Lineage: e19170ee-5ff2-4440-b353-e5d7376a64b2

Co-authored-by: Fusion <noreply@runfusion.ai>
…lane's only board read

The Workflow Merger woke ~50 times a day and re-derived merge authority with a hand-written scan each time; this lands one tracked, read-only census tool, pins its contract with fixture tests, and rewrites the lane seed so the lane may only act on that tool's output.

Key changes:
- scripts/merge-census.mjs (new): sentinel-delimited read-only lane census — latest authoritative verdict ranked by completedAt over each gate row and its nested priorAttempts, operator waiver only when both bypassedBy and bypassedAt are present, branch ownership by Fusion-Task-Id trailer VALUE (never a message grep), landing by merge-base --is-ancestor, laneDigest change-detection against a gitignored stamp, and exactly one machine-readable `disposition laneRows=N authorityVerdict=<v> actionTaken=<a>` line. Exit 1 plus an `incomplete:` line forbids every negative conclusion.
- Four extractor defects that each printed a value shaped like merge authority now have fixture regressions: a loop-leaked column/verdict (asserted order-swap invariant), a wrong-path sourceAgentId reporting "cards filed by me: []" on 38/38 rows, a `--grep=<id>` matching a commit that only mentions the id, and a `$`-anchored for-each-ref read reporting zero refs while 46 lived.
- Merger lane seed (workflow-role-agent-defaults.ts) keeps every original seed sentence verbatim and appends the mandate: canonical tool only, one second differently-written read via `--task <ID>` before acting, no negative conclusion from `incomplete:`/`refProbe:invalid`, record-only tick when laneDigest is unchanged.
- New test workflow-role-agent-defaults-merger.test.ts pins seed survival, that every marker the mandate tells the lane to trust is actually printed by the named tool, and that the mandate plus one census read fits MEMORY_LONG_TERM_BYTE_BUDGET read from the engine source rather than a remembered number.
- docs/workflow-steps.md adds the authority table for what a census read may and may not conclude; docs/agents.md records the census-placement rules (never under gitignored `.fusion/`, never dump a tick scan into long-term MEMORY.md).
- No changeset: internal lane tooling is not a published @runfusion/fusion surface.

Files changed:
 docs/agents.md                                     |  40 +
 docs/workflow-steps.md                             |  39 +
 .../workflow-role-agent-defaults-merger.test.ts    | 160 ++++
 .../src/agents/workflow-role-agent-defaults.ts     |  15 +-
 scripts/__tests__/merge-census-contract.test.mjs   | 427 +++++++++++
 scripts/__tests__/merge-census-git.test.mjs        | 146 ++++
 scripts/merge-census.mjs                           | 848 +++++++++++++++++++++
 7 files changed, 1674 insertions(+), 1 deletion(-)

Fusion-Task-Id: RUFU-463
Fusion-Task-Lineage: d720c83d-bf0d-4383-b56a-31bb92b242c0
Co-authored-by: Fusion <noreply@runfusion.ai>
…in Command Center

Per-request LLM cache behaviour was invisible: token totals answered "what did the board cost" but never "what did this lane send, and did its serialized system prefix stay stable?", so a cache regression and a prompt that silently drifts were both unfalsifiable. This lands three event kinds on the existing usage_events table, a windowed lane aggregate over them, one explicit emitting decision per lane, and a Command Center plate that renders the result.

Key changes:
- core/src/telemetry/llm-prompt-telemetry.ts (new): the contract module — lane union, token_usage / prompt_head / prompt_end kind constants, sha256 head identity, first-divergence-offset arithmetic plus bucket, and cap-guarded meta builders that fit the shared 4096-byte usage_events.meta budget (the divergence locator is dropped before the row is attempted rather than allowed to overflow the budget and take the usage counters with it). Privacy is type-level: no builder can receive prompt or response text, only hashes, byte counts and offsets.
- Registers the three kinds in BOTH usage-event kind sets and adds an additive kinds/limit filter to the range queries, so no migration and no new database are needed.
- core/src/board/lane-usage-analytics.ts (new): windowed lane x model aggregate that reads one source, usage_events — it never calls aggregateTokenAnalytics nor reads task token columns or the chat token table, because those are two other accounting systems whose sum double-counts; its own test scans the module with comments stripped to keep that boundary. Honest absence is the product logic: a lane whose provider never answered the cache-read counter reads as unmeasured, never as a computed 0%.
- engine/src/agents/prompt-telemetry.ts (new) plus createResolvedAgentSession: the session seam every lane uses now takes an optional promptTelemetry identity and stores a READER of the stable system prefix, not a snapshot, because pi evaluates systemPromptOverride per request. promptWithFallback wraps its whole body so the attached-dispatch closure (the executor's dominant path), the direct prompt and the context-limit compact retries all pair a head with a tail; a WeakSet makes nested re-entry one logical request and prompt_end is written in a finally so a request that dies still shows its head.
- accumulateSessionTokenUsage gains an explicit lane option with NO default, so all 11 pre-existing call sites stay byte-compatible while executor, merger, triage, workflow-step and heartbeat each get exactly one row from the credit they already make. Lanes that have no task.tokenUsage writer at all get an event-only seam instead (reviewer session disposal, idle isNoTaskRun heartbeat runs, one wrapper around the four chat token writes) — none of them touches the task row.
- Command Center llm-telemetry route on the async layer, read-only and project-scoped, window=10m|1h with an unknown window resolving to 1h, using the same resolver convention the neighbouring Command Center routes use.
- Dashboard LLMHourGrid plate in Command Center -> Tokens with its own 10m/1h axis (a cache that regressed must be visible inside the session that caused it; a 7-day average buries it). Lanes and models are built from the payload, never a fixed list; every non-empty cell is a real button with an accessible name and a >=36px target that filters the prompt-head list below by a LANE predicate, since a prompt_head row carries no model column. Adds the median-system-bytes column and the offset-bucket split, renders an unmeasured share as the word unmeasured, and degrades a malformed payload to the section's own empty sentence instead of taking the token totals down with it.
- Catalog keys added to all 7 locales by hand rather than extract-generated, so the extractor does not rewrite every catalog wholesale and drag in unrelated keys; i18n parity and the generated resources type both confirm the block.
- Tests: core unit coverage for the contract and the aggregate, engine coverage for lane naming and per-request head/tail emission, a postgres test proving the three kinds pass the existing CHECK constraint and that an unregistered meta key is dropped rather than stored, dashboard responsive plus design-token plus route coverage, and type-level pins between the lane vocabularies. Also repairs a pre-existing chat-manager.test.ts defect (readQuestionAnswerLink and QUESTION_ANSWER_METADATA_KEY used with no import — six cases threw ReferenceError at HEAD).
- docs/diagnostics.md gains the incident-time contract: one named owning seam per lane so "this lane has no rows" is answerable, what baseline / identical / diverged and firstDivergenceOffset mean for the RUFU-267/268 byte-stability claims, the restart-cadence caveat, and the query shape. docs/dashboard-guide.md gains the feature bullet. Minor changeset added.

Files changed:
 .changeset/rufu-284-llm-prompt-cache-telemetry.md  |   7 +
 docs/dashboard-guide.md                            |   2 +
 docs/diagnostics.md                                |  91 +++++
 .../src/__tests__/lane-usage-analytics.test.ts     | 272 +++++++++++++
 .../src/__tests__/llm-prompt-telemetry.test.ts     | 198 ++++++++++
 .../postgres/llm-telemetry-usage-events.pg.test.ts | 277 ++++++++++++++
 packages/core/src/board/lane-usage-analytics.ts    | 310 +++++++++++++++
 packages/core/src/index.gate.ts                    |  52 +++
 packages/core/src/index.ts                         |  56 +++
 packages/core/src/task-store/async/async-events.ts |  36 +-
 packages/core/src/tasks/usage-events.ts            | 120 +++---
 .../core/src/telemetry/llm-prompt-telemetry.ts     | 421 ++++++++++++++++++++
 .../command-center/areas/LLMHourGrid.css           | 350 +++++++++++++++++
 .../command-center/areas/LLMHourGrid.tsx           | 324 ++++++++++++++++
 .../components/command-center/areas/TokensArea.tsx |  63 +++
 .../__tests__/LLMHourGrid.responsive.test.tsx      |  68 ++++
 .../__tests__/TokensArea.llm-telemetry.test.tsx    | 284 ++++++++++++++
 .../areas/__tests__/TokensArea.test.tsx            |  13 +-
 .../command-center/areas/__tests__/areas.test.tsx  |  28 +-
 .../dashboard/src/__tests__/chat-manager.test.ts   |   4 +
 .../command-center-llm-telemetry-route.test.ts     |  62 +++
 packages/dashboard/src/chat.ts                     |  61 ++-
 .../src/routes/register-command-center-routes.ts   |  40 ++
 .../__tests__/lane-token-usage-telemetry.test.ts   | 247 ++++++++++++
 .../prompt-head-request-telemetry.test.ts          | 245 ++++++++++++
 packages/engine/src/agent-heartbeat.ts             |  16 +-
 .../engine/src/agents/agent-session-helpers.ts     |  51 +++
 packages/engine/src/agents/prompt-telemetry.ts     | 424 +++++++++++++++++++++
 packages/engine/src/execution/reviewer.ts          |  22 ++
 .../engine/src/execution/session-token-usage.ts    |  34 +-
 .../engine/src/execution/step-session-executor.ts  |  43 +++
 .../executor/attempt-executor-verification-fix.ts  |   2 +
 .../engine/src/executor/execute-workflow-step.ts   |   6 +-
 .../engine/src/executor/persist-token-usage.ts     |  28 ++
 packages/engine/src/executor/run-implementation.ts |   2 +
 packages/engine/src/executor/token-usage-pure.ts   |  17 +-
 packages/engine/src/merge/merger-ai.ts             |   6 +-
 packages/engine/src/merger.ts                      |  11 +-
 packages/engine/src/pi.ts                          |  13 +
 packages/engine/src/triage.ts                      |   7 +
 packages/i18n/locales/en/app.json                  |  40 ++
 packages/i18n/locales/es/app.json                  |  40 ++
 packages/i18n/locales/fr/app.json                  |  40 ++
 packages/i18n/locales/ko/app.json                  |  40 ++
 packages/i18n/locales/pt-BR/app.json               |  40 ++
 packages/i18n/locales/zh-CN/app.json               |  40 ++
 packages/i18n/locales/zh-TW/app.json               |  40 ++
 packages/i18n/src/resources.d.ts                   |  50 +++
 48 files changed, 4576 insertions(+), 67 deletions(-)

Fusion-Task-Id: RUFU-284
Fusion-Task-Lineage: a7db93e0-96f0-4aed-9686-24525a3fcd12
Co-authored-by: Fusion <noreply@runfusion.ai>
…t its episode

An operator told a repeated Review stall had been released could not later tell whether that release ever survived a merge cycle: the disposition row recorded only `repetitionCount`/`threshold`, so a card re-parked hours later with `mergeRetries` still 0 looked identical to one that had been released, genuinely retried, and stalled again on new content. This lands the missing measurement — observability only; no threshold, log sentence, or pause payload changes.

Key changes:
- core: add `countPriorStallDeadlockParks(task, {code, reason})` in `tasks/in-review-stall.ts`, counting every earlier auto-disposed park line on the card's own log carrying the same code AND reason. Deliberately the opposite walk of `countRecentIdenticalStallEntries`: park lines sit far apart, separated by the next episode's surfaced lines, so a trailing-run walk could only ever answer 0 or 1. It takes no `progressAt` reset and no projection envelope, and the park sentence's reason marker is a named constant so a reworded park cannot silently blind the counter.
- core: export the new helper from both `index.ts` and the reduced `index.gate.ts` barrel, since the engine-core gate project resolves `@fusion/core` through the gate barrel.
- engine: the stall sweep's fenced park transaction additionally captures `priorParkCount`, `mergeRetriesAtPark` (live `mergeRetries`, default 0) and `episodeHasMergeAttempt`, and attaches all three to the deadlock arm of `task:in-review-stall-deadlock-disposed`. The capture point is load-bearing: core appends the park's own log entry AFTER the compute callback returns, so the first park of an episode correctly reads 0, and every release arm resets `mergeRetries` alongside the four park fields, so a merge attempt is provable only at the park itself. A re-park with `priorParkCount >= 1` and `episodeHasMergeAttempt: false` is the queryable signature of a cosmetic release. The transient merge-retry counter is deliberately not consulted.
- tests: 12 cases for the counter in `in-review-stall.test.ts` (whole-log scan, code/reason strictness, trim parity, surfaced/terminal-line exclusion, dual-barrel parity); a new `describe` block in `in-review-stall-deadlock-disposition.test.ts` drives park -> release -> re-park through the real sweep seam and pins the three keys on the second row, with a control case for an honest release (plus the one-line `workflowDefinitionEpoch` stub the hand-rolled fake store needed to reach the sweep at all).
- docs: new run-audit section "Naming the park cycle a stall release did not break (RUFU-398)", and the extended "In-review stall deadlock" lifecycle contract in architecture.md.
- changeset: patch for `@runfusion/fusion`.

Files changed:
 .changeset/rufu-398-stall-park-cycle-evidence.md                      |   7 ++
 docs/architecture.md                                                  |   2 +-
 docs/run-audit.md                                                     |   8 ++
 packages/core/src/__tests__/in-review-stall.test.ts                   | 116 +++++++++++++++++++
 packages/core/src/index.gate.ts                                       |   1 +
 packages/core/src/index.ts                                            |   1 +
 packages/core/src/tasks/in-review-stall.ts                            |  54 +++++++++
 .../in-review-stall-deadlock-disposition.test.ts                      | 193 ++++++++++++++++++++++++++++++++
 packages/engine/src/self-healing.ts                                   |  37 +++++-
 9 files changed, 416 insertions(+), 3 deletions(-)

Fusion-Task-Id: RUFU-398
Fusion-Task-Lineage: 6128a67d-07c8-4614-a79a-ecf246937256
Co-authored-by: Fusion <noreply@runfusion.ai>
… quote its re-run budget

A review gate that dies with no verdict used to record its own status word as the cause, so one card's six consecutive full Code Review runs wrote byte-identical History lines reading `... failed before producing a verdict: failed` and said nothing about how many automatic re-runs were left; the synthesized `output` now carries a real cause, or honestly says there is none and names the position in the automatic re-run budget.

- Add `NO_VERDICT_STATUS_ECHO_VALUES` as a deny-list of details that are NOT a cause (exactly the bare `failed` status word), so a status echo is treated as "no cause" while genuine categorizations (`aborted`, `exception`, `timeout`, `malformed`, `plugin-node-handler-error`) still flow through.
- Add `countConsecutiveNoVerdictAttempts()` and `formatNoVerdictCauseUndetected()`: when no cause exists the record says `no cause detected (attempt N of 4; automatic re-run budget: R of 3 left)`, or `automatic re-run budget exhausted`, quoting the single authority `MAX_VERDICTLESS_GATE_RERUN_ATTEMPTS` instead of a second drifting number; with no record context it degrades to `attempt count unavailable` rather than inventing `attempt 1`.
- `runGraphCustomNode` now publishes a failed gate result's own `error` text on the canonical `node:<id>:error` carrier at both boundaries — the raw-CLI gate branch (which previously returned no contextPatch at all) and the prompt/script mapping (previously narrowed to `review-input-unprovable` only) — so returned failures agree with thrown ones and the session-contention / unusable-worktree classifiers can finally see the text they exist to route on; an existing key is never overwritten.
- Thread `workflowStepId` plus `priorResults` through `recordOptionalGroupStepResult`'s diagnostic and `recordNodeProgressFinish` (all six call sites) so the optional-group and `source:"node"` surfaces quote the same attempt/budget clause instead of one of them still recording the echo.
- Tests: new `workflow-graph-no-verdict-cause-fidelity.test.ts` (cause precedence, deny-list, attempt/budget clause on both record surfaces) and `run-graph-custom-node-error-forwarding.test.ts` (CLI + mapped-gate error forwarding); `review-input-proof-fail-closed.test.ts`'s old narrowing assertion is inverted to the new contract, and the optional-group fixture gains a `priorResults` seam.
- Docs: `docs/workflow-steps.md` gains a "No-Verdict Gate Failure Diagnostics" section — ordered cause precedence, the enforced-versus-displayed budget ownership table across the re-seed, dispatch-sweep and operator-Retry lanes, and the statement that the synthesis is `output`-only.
- Add a patch changeset for `@runfusion/fusion`.
- Unchanged by design: `status`, `verdict`, `verdictRequired`, graph edge routing, `priorAttempts` supersession, and failed-gate operator-bypass eligibility.

Files changed:
 .changeset/rufu-381.md                             |   7 +
 docs/workflow-steps.md                             |  30 ++
 .../review-input-proof-fail-closed.test.ts         |  15 +-
 .../run-graph-custom-node-error-forwarding.test.ts | 223 +++++++++++
 ...orkflow-graph-no-verdict-cause-fidelity.test.ts | 426 +++++++++++++++++++++
 ...rkflow-graph-optional-group-no-feedback.test.ts |  15 +-
 .../engine/src/executor/run-graph-custom-node.ts   |  52 ++-
 .../src/workflows/workflow-graph-executor.ts       | 156 +++++++-
 8 files changed, 901 insertions(+), 23 deletions(-)

Fusion-Task-Id: RUFU-381
Fusion-Task-Lineage: d696767e-26f0-4960-92ec-7e3c7a204194
Co-authored-by: Fusion <noreply@runfusion.ai>
…aces, and in Mailbox

Report the V8 heap ceiling a dashboard process actually resolved, on every surface an
operator already reads, so a board silently running on V8's ~4 GiB platform default is a
named condition instead of a crash loop known only from a body count (28 supervisor
restarts at 3.90-4.03 GiB while `/api/health` answered `ok` on every probe). Strictly
observation-only: no product path sets, raises, defaults, or injects `--max-old-space-size`.

- Add `packages/dashboard/src/lib/heap-ceiling.ts`, the ONE resolver over
  `v8.getHeapStatistics().heap_size_limit` (never `heapSizeLimit`, the semispace cap that
  read ~2.6x low) classifying the declaring launch site as `execArgv` / `NODE_OPTIONS` /
  `v8-default`, memoized once per boot and re-exported from `@fusion/dashboard` so no
  surface forks a second answer.
- `createServer()` calls `reportHeapCeilingAtBoot()` (`heap-ceiling-notice.ts`): a log line
  on every boot, and when nobody chose a ceiling also a warning, one Mailbox notice per UTC
  day (`heap-ceiling:v8-default:<day>` through the same idempotent `sendMessageOnce` seam the
  retention notice uses), and one `runtime:heap-ceiling` run-audit row via the FN-9177
  bounded core seam. Metadata is `{ heapLimitBytes, source, isV8Default }` — the raw
  `NODE_OPTIONS` string (which legitimately carries credentials) never enters any row, log,
  or payload, and `runId` carries the pid instead of a path.
- `/api/health` additively publishes `heapLimitBytes`, `heapLimitSource`,
  `heapLimitIsV8Default` and never feeds them to `status`; `/api/system-stats` gains the two
  descriptors beside its byte-identical `heapLimit`, classified from the same live sample.
- CLI boot banner (TUI and plain branches) plus the supervisor's attempt, backoff, and
  give-up lines state the ceiling, which site carries it, and whether a respawn carries it;
  `resolveSupervisorRespawnCommand` takes injectable `execArgv`/`entryPoint` so the
  flag-before-script carry is proven by a test instead of asserted by a comment.
- Extract `lib/format-bytes.ts` out of `retention-pressure-notice.ts` so the ceiling and the
  retention notice that quotes "% of <ceiling>" render identical bytes identically.
- Docs: prose section in `docs/run-audit.md` plus the AGENTS bullet, deliberately outside the
  curated delivery-pipeline catalogue (keeping `run-audit-catalogue.test.ts`'s table-for-table
  lock green); the `/api/health` shape in `docs/architecture.md`; dashboard-guide, diagnostics,
  and the heap-growth runbook.
- Merge resolution: main's RUFU-302 closed `database` health shape and its
  `taskIdIntegrity.failure` class are kept verbatim, with RUFU-462's three fields appended to the
  published payload and its run-audit declaration added alongside main's `store:open` /
  `build:provenance-stale`; both sides' new `docs/run-audit.md` prose sections survive.

Files changed:
 .changeset/rufu-462-heap-ceiling-observability.md  |   7 +
 AGENTS.md                                          |   1 +
 docs/architecture.md                               |   3 +-
 docs/dashboard-guide.md                            |   1 +
 docs/diagnostics.md                                |   1 +
 docs/run-audit.md                                  |  34 ++
 .../performance/dashboard-heap-growth-runbook.md   |  20 +
 .../commands/__tests__/dashboard-supervise.test.ts |  32 ++
 packages/cli/src/commands/dashboard.ts             |  58 ++-
 packages/core/src/types/audit/run-audit.ts         |  17 +
 .../dashboard-heap-ceiling-observability.test.ts   | 186 +++++++++
 .../src/__tests__/heap-ceiling-notice.test.ts      | 432 +++++++++++++++++++++
 packages/dashboard/src/heap-ceiling-notice.ts      | 297 ++++++++++++++
 packages/dashboard/src/index.ts                    |  38 ++
 .../src/lib/__tests__/heap-ceiling.test.ts         | 228 +++++++++++
 packages/dashboard/src/lib/format-bytes.ts         |  17 +
 packages/dashboard/src/lib/heap-ceiling.ts         | 195 ++++++++++
 .../dashboard/src/retention-pressure-notice.ts     |  13 +-
 .../routes/register-system-maintenance-routes.ts   |  13 +
 packages/dashboard/src/server.ts                   |  39 ++
 20 files changed, 1618 insertions(+), 14 deletions(-)

Fusion-Task-Id: RUFU-462
Fusion-Task-Lineage: 734f9423-e110-46e2-be5c-b81b263c4049
Co-authored-by: Fusion <noreply@runfusion.ai>
… exhausted parks

Branch-conflict recovery now counts refusals, decides terminal exhaustion, and suppresses re-parks from a card-owned register instead of the shared `recoveryRetryCount`, so a parked card stops re-parking on every sweep and a Retry genuinely restarts the budget.

- core: new `TaskBranchConflictRecoveryEpisode` at `sourceMetadata.branchConflictRecoveryEpisode` (exported via index/index.gate); `buildManualRetryResetPatch` nulls the key in the same patch that lifts the pause, so Retry restarts the budget from zero instead of re-parking on a surviving count.
- engine: `planBranchConflictRecoveryPass` derives terminal and `boundedPassCount` from the family register only (the shared field keeps its dispatcher value under its own clamp, never lowered or re-raised); the remedy sentence interpolates the register, so "after 4 bounded passes (budget 3)" can no longer happen.
- suppression: `hasBranchConflictRecoveryParkHold` / `branchConflictRecoverySuppressed` gate the reject, PR, dirty-hold, self-owned, and executor arms BEFORE the budget branch — a suppressed pass writes, counts, stamps, and announces nothing, leaving one deduped `recovery-park-hold` -no-action audit row.
- the auto-recovery dispatcher's irreducible pause finally has a terminal disposition authored through the shared park patch and attributed to its own source; the executor's verdict stays `sticky` for a card carrying its own exhausted park instead of handing it back as retryable.
- the pause arm no longer overwrites an existing `recovery-exhausted` hold with `branch-conflict-unrecoverable` (that erased the suppression key and let the next pass count again).
- tests: four new suites (repeated-park emissions count, dispatcher terminal park, retry-surface structural guard, manual-retry-reset clears) plus rewrites of the RUFU-231/RUFU-432 assertions that encoded the borrowed count; fake stores learned the key-level `sourceMetadataPatch` fold and the `updateTaskAtomic` seam they were missing.
- docs: architecture.md authority split, run-audit.md `recovery-park-hold` vocabulary, AGENTS.md run-audit bullet, RUFU-231 solution doc's third failure mode, and an operator-visible changeset.

Files changed:
.changeset/rufu-395-branch-conflict-recovery-register.md        |   7 +
AGENTS.md                                                       |   1 +
docs/architecture.md                                            |  11 +-
docs/run-audit.md                                               |  26 ++
docs/solutions/reliability/zero-own-commit-foreign-base-recovery-loop.md |  51 ++++
packages/cli/src/__tests__/retry-surface-clears-recovery-register.test.ts |  61 +++++
packages/cli/src/commands/__tests__/task.test.ts                |  16 +-
packages/core/src/__tests__/manual-retry-reset.test.ts          |  64 ++++-
packages/core/src/index.gate.ts                                 |   3 +-
packages/core/src/index.ts                                      |   4 +-
packages/core/src/tasks/manual-retry-reset.ts                   |  21 +-
packages/core/src/types.ts                                      |   3 +
packages/core/src/types/task/task-core.ts                       |  45 ++++
packages/engine/src/__tests__/auto-recovery-branch-worktree.test.ts |  27 +-
packages/engine/src/__tests__/branch-conflict-foreign-reject-history.test.ts |  87 ++++++-
packages/engine/src/__tests__/branch-conflict-recovery-accounting.test.ts | 286 ++++++++++++++++++---
packages/engine/src/__tests__/branch-conflict-recovery-repeated-park.test.ts | 384 ++++++++++++++++++++++++++++
packages/engine/src/__tests__/branch-conflict-retry-bound.test.ts |  43 +++-
packages/engine/src/__tests__/branch-conflict-terminal-park-durability.test.ts |  53 +++-
packages/engine/src/__tests__/reliability-interactions/branch-worktree-recovery-terminal-park.test.ts | 165 ++++++++++++
packages/engine/src/__tests__/self-healing-overlap-evidence-guard.test.ts |  11 +-
packages/engine/src/__tests__/self-healing-zero-own-commit-foreign-base.test.ts |  59 ++++-
packages/engine/src/auto-recovery-handlers/branch-worktree.ts   |  66 ++++-
packages/engine/src/executor/worktree-branch-conflict-handle.ts |  38 ++-
packages/engine/src/recovery/branch-conflict-recovery-accounting.ts | 278 ++++++++++++++++++--
packages/engine/src/self-healing.ts                             | 116 ++++++---
packages/engine/src/worktree/acquisition-conflict-revalidation.ts |   9 +
27 files changed, 1801 insertions(+), 134 deletions(-)

Fusion-Task-Id: RUFU-395

Fusion-Task-Lineage: b7c1ac08-cfa9-4ca1-aaa0-203ef6749a37

Co-authored-by: Fusion <noreply@runfusion.ai>
@coderabbitai

coderabbitai Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Walkthrough

Warning

Review details and warnings were omitted to fit the comment limit.

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ThreatCrush found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

@greptile-apps

greptile-apps Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[High impact] The inspected metrics change appears safe to merge, with no blocking finding established.

Summary

The metrics sampler now requests fields: []. The list reader can omit both comment columns together while keeping the columns needed to count cards.

  • The metrics sampler counts board columns without loading card comment histories.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Metrics sampler] --> B["listTasks: slim true, fields empty"]
  B --> C[Resolve selected columns]
  C --> D[Read rows without comment columns]
  D --> E[Count cards by column]
Loading

Reviews (2) · Last reviewed commit: "fix(RUFU-883): stop the 5s metrics read ..." · Reviewed by Greptile

… never reads

The domain sampler tallies cards per column and reads exactly one field
(`SlimTaskLike { column: string }`), but its board read still carried every card's
`comments` and `steeringComments` for every registered project, every 5 seconds. A
10.35 GiB production heap snapshot showed 394 503 live copies of one steering
sentence held off 18 801 `comments` + 18 751 `steeringComments` properties of
hydrated rows, against 2 766 live cards of which 836 carry any comment: ~22 living
hydrations per commented card. That is why RUFU-640's write-time bound stopped the
growth of the data and not the growth of the heap.

RUFU-627's declared-field mechanism already existed but could not express this,
because `comments` was undeclarable and therefore selected by every projection
including `fields: []`. `comments` is now a member owning BOTH jsonb columns, since
`rowToTask` deliberately does not merge the pair and a half-declaration would
describe a shape the mapper cannot produce.

Adding a declarable member silently narrows every EXISTING declared read, so the
four production declared call sites were re-audited for comment access and are clean
(triage.ts:3202/:6368 read identity/title/description/column/createdAt;
auto-claim-snapshot.ts has zero comment reads). triage.ts's two `.comments` reads
are single-task `getTask` paths, not declared list rows.

Fusion-Task-Id: RUFU-883
@ischindl
ischindl force-pushed the rufu-883-sampler-comment-projection branch from 9c77eac to c97e8eb Compare November 4, 2026 21:46
@ischindl

ischindl commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

Closing: wrong base. This PR targets Runfusion/Fusion main = eb7eef4, but our working main sits 1029 commits ahead of it, so the PR carries the whole 1029-commit history instead of the change (GitHub reports 3913 changed files, +566 986/-143 888). That also explains every red check - engine-core scheduler assertions, the tsc OOM and the Lint warnings are all against the June-era base, not against this diff. The change itself is one commit, 5 files: core/src/task-store/async/async-persistence.ts, dashboard/src/metrics/domain-sampler.ts, two tests, one changeset. Verified locally on the real base: engine-core 539 tests green, the two new suites 9/9 and 18/18, pnpm verify:fast PASS (68 steps).

@ischindl

ischindl commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

Superseded: see the comment above. Will land on the working main instead.

@ischindl ischindl closed this Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants