The startup JS gzip gate was a fixed ceiling that main drifted to within
1-3 KiB of, forcing repeated hand-bumps (315->317 KiB) and failing
feature PRs on drift they did not cause (#112649 burned two CI rounds).
check-control-ui-performance.mjs now also enforces a committed baseline
(config/control-ui-startup-budget-baseline.json) with a 512 B tolerance:
regressions beyond baseline+tolerance fail with an actionable message,
intentional increases become a reviewed one-line diff via
--update-baseline --reason, meaningfully lighter builds print a
lower-the-baseline hint, and a malformed/missing baseline fails closed.
The fixed ceiling stays as the cumulative-creep backstop.
Closes#112743.
* feat(scripts): add watch-pr-ci CI watcher with mergeable and attach prechecks
* fix(scripts): require successful attached PR CI run
* fix(scripts): harden watch-pr-ci verdicts against foreign runs and truncated rollups
* fix(scripts): add fresh-run gating to watch-pr-ci attachment
* fix(scripts): guard stale-cancelled verdict against truncated context pages
* fix(scripts): declare rollup context total count
* fix(scripts): sanitize check names and close the final polling interval
* test(scripts): type deadline wait samples
* feat(cron): system-owned heartbeat monitor jobs replace the interval scheduler
- new internal cron payload kind {kind:"heartbeat"}: execution pokes
requestHeartbeat({source:"interval"}); reported in the protocol job
schema, not accepted from client create/patch
- gateway converges one declaration-keyed monitor job per heartbeat-enabled
agent (schedule every+deterministic phase anchor) at startup and on
config reload; removes monitors for unconfigured agents
- heartbeat runner loses its interval setTimeout machinery; nextDueMs
stays as the cooldown gate, event wakes unchanged
* test(cron): heartbeat monitor regressions; docs for cron-owned cadence
- converge/prune/failure-containment tests for heartbeat monitor jobs
- heartbeat payload run fires an interval wake, no system event
- scheduler tests converted from timer self-fire to wake-queue pokes;
timer-mechanics-only tests deleted with the timer
- persisted-shape accepts the heartbeat payload kind
- docs: heartbeat cadence ownership + system payload kind
* fix(cron): heartbeat monitor review round 1
- targeted cron-monitor interval ticks use the full per-agent path so
due-commitment sessions still deliver
- cron-disabled gateways keep a local fallback interval timer (shipped
cron.enabled=false contract; removed when heartbeat config folds into
cron in #110950)
- heartbeat job reconciliations serialize with latest-wins epochs and a
bounded 30s retry after a failed convergence pass
* fix(cron): chain clamped fallback heartbeat timers past the setTimeout cap
* fix(cron): heartbeat monitor review round 3
- targeted monitor redirect skips wakes carrying heartbeat overrides and
surfaces the per-agent terminal skip reason instead of not-due
- cron-disabled fallback timer re-arms with a 1s floor after each firing
so a dropped wake cannot end the chain
- heartbeat payloads are system-owned at the service boundary: add requires
the gateway opt-in, patches to the kind are rejected
* fix(cron): heartbeat monitor review round 4 — full ownership enforcement
- prune only jobs proven to be monitors (prefix AND heartbeat payload)
- existing monitors reject every update patch; declarative upserts on the
monitor key require the gateway opt-in even with a different payload
* fix(cron): complete heartbeat monitor ownership boundary
- converge scopes declarative matching to real monitors so a colliding
user job with the same key is never adopted or overwritten
- monitor removal requires the gateway systemOwned opt-in; ad-hoc
API/CLI deletion is rejected, reconciliation cleanup still prunes
* docs(cron): record intentional enrollment-snapshot semantics for monitor ticks
* fix(cron): repair heartbeat monitor CI gates
The core-runtime-media-ui shard runs its ui config non-isolated for speed, but the
full chat-pane lifecycle tests instantiate the pane component, which relies on
chat-thread/chat-message module-level singletons (thread-state map, confirmation
dismisser WeakMap, module-scoped document context-menu listeners) and spies on those
modules. Under the shared non-isolated graph a stateful predecessor file can leave
those modules duplicated, so the pane binds to a different instance than the test's
spy/registry -- producing order-dependent flakes: `removeEventListener`-not-called
teardown assertions or 120s session-lifecycle hangs. Reproduced deterministically on
a Linux Node 24 Testbox (MAX_WORKERS=1); isolating a single file only shifted the
failure to a sibling pane test, so the whole full-pane family shares the fragility.
Route the 7 full-pane lifecycle test files through a new isolated jsdom lane
(vitest.ui-isolated.config.ts, isolate: true) for a fresh module graph; the other
~370 ui tests stay fast and non-isolated. Registered in both shard registries
(ci-node-test-plan.mjs, vitest.test-shards.mjs) and excluded from vitest.ui.config.ts.
Verified on Testbox: the deterministic single-worker media-ui shard goes from failing
to 5182 passed / 0 failed. Test-infrastructure only; no product code changes.
* chore(scripts): end failing oxlint runs with a stable status line
A crashed run-oxlint wrapper printed only a stack trace, and a lint
invocation whose output was truncated (cmd | tail -N) read as success —
which recently let a wrapper crash (stale node_modules after a dep-adding
merge) masquerade as a clean lint. Route the CLI entry through a small
wrapper that converts crashes into exit 1 and ends every failing run
with '[oxlint] FAILED (exit N)' as the final line.
* chore(scripts): declare runOxlintCliEntry in the script declaration contract
check-guards verifies .d.mts contracts against .mjs exports and
check-test-types consumes them; the new entry export needed both the
declaration and explicit log-parameter annotations in the test.
* feat(cron): stream schedule sources (supervised command stdout)
Add gated argv stream schedules with bounded line batching and trigger.streamBatch composition.
Reuse the gateway ProcessSupervisor for source ownership, deterministic teardown, capped restart backoff, and schedule-key guarded batch execution. Expose additive protocol, CLI, tool, UI, docs, and generated snapshot surfaces without storage DDL.
Contract: stream schedules are event-driven, require cron.triggers.enabled, reject command payloads, and retain at most one bounded pending batch.
* fix(cron): reject retired stream source epochs at run admission
Thread an invalidatable per-owner source-generation token (ownerNonce.generation)
through cron.run admission alongside the schedule key. A batch handed to cron.run
under one source epoch can wait behind another run while its owner is stopped; a
disable→re-enable or A→B→A edit leaves the schedule key unchanged, so the key
check alone would admit the retired epoch's batch. The token is persisted in
job.state on every lifecycle write and compared at every admission site plus the
executeJobCore guard, so a stale epoch's batch is skipped.
Also fix direct stream-job mutations recording the wrong lifecycle status when
global cron is off but triggers are on: extract resolveStreamStopReason so the
direct path reports the remediable cron-disabled state like reconcile does.
* fix(cron): close stream admission windows from round-10 review
- Persist the retired source generation before draining stop teardown, so a
batch queued behind another cron run cannot gain admission during the up-to-10s
in-flight-batch wait (server-cron routed stop path).
- Add streamSourceGeneration to the closed gateway response schema (excluded from
the writable patch schema) so a running stream job passes strict result
validation without letting callers spoof source identity.
- Close the mutation-epoch ABA: track an eviction epoch so a snapshotted absent-0
is trusted as unchanged only when no LRU eviction happened during the await.
* refactor(cron): stream sources own a durable logical identity
Split the conflated restart-generation/admission token into two concepts:
a persisted streamSourceIdentity owned by cron store mutations (rotates on
enable/disable, source replacement, once-trigger auto-disable, and explicit
retirement; stable across supervised child restarts) and a watcher-local
process generation used only to fence stale child callbacks. Admission now
requires schedule key + identity together at every window, closing the
A-to-B-to-A and restart-flush races from review rounds 8-11.
Also: match-mode regexes now see raw source text (the [truncated] marker is
applied after matching), stop-timeout failures set the live restartExhausted
mirror so shutdown preserves the terminal diagnostic, and the watcher is
split into owner/output/registry modules under the max-lines budget.
* fix(cron): harden stream teardown and intake from round-4 review
An exit queued ahead of a requested stop no longer counts toward restart
exhaustion (the synchronous stop fence owns it), overlapping cron.stop and
stopAndDrain share one memoized shutdown drain instead of double-stopping
every owner, raw output intake is bounded at 4x the batch cap so normal
64 KiB pipe reads stop losing complete lines to OS chunk boundaries, and
persisted-shape quarantine coverage for unsafe match expressions is pinned.
* fix(cron): keep watcher-internal owner disposal from retiring live identity
Disposing an obsolete owner while a start replaces it is not a durable
removal; a retiring stop there rotated the live job's identity and stranded
the replacement behind the CAS ownership guard. Also align the docs with the
implemented match semantics: complete lines match on full text past the
batch cap, only intake-cut prefixes are unmatchable.
* fix(cron): make oversized-line matching independent of pipe chunking
Partial lines are retained up to the raw-intake bound rather than the
delivery cap, so a complete over-cap line matches identically whether it
arrives in one callback or several; only a line the intake bound itself cut
remains an unprovable prefix. Reconcile also contains schedule-replacement
stop failures per job, matching the other stop branches.
* fix(cron): bound assembled lines, drop stale payload override, barrier stopAll
- enforce the 4x raw-intake per-line cap while assembling split callbacks,
so an oversized line stays an unprovable prefix regardless of chunking
- stop passing the watcher-cached payload as a cron.run override; the run
snapshots the persisted payload under its admission lock
- stopAll waits for every owner stop to settle before surfacing failures
- split cron-stream-output interleaving tests into their own file (max-lines)
* fix(cron): address first full-CI round (lint, knip, schema test, unused param)
* chore(cron): refresh codex prompt snapshots for stream schedule schema
* fix(cron): keep the first clean line after an intake drop ending at a newline
* fix(cron): fence stream reconcile list snapshots against direct mutation routes
A cron.list snapshot captured across the reconcile await could be applied
after a direct add/update route already started the owner, stopping it as
removed and retiring its live identity. A mutation revision bumped at every
direct route start invalidates the stale snapshot; reconcile re-lists
(bounded) instead of applying it.
* style(cron): format stream owner imports
* fix(cron): discard severed stream prefixes at EOF
* fix(cron): retry failed stream shutdown drains
* fix(cron): honor stream stop fence after output drain
* chore(cron): refresh landing checks