Preserve opaque source strings across tool-result replay while retaining masking for direct .env reads, registered or recognizable secrets, structured sensitive fields, and payment data.
* fix: unblock replies after recovery owner release conflict
* fix(diagnostics): keep an unreclaimed active run as an observed skip
Removing the noop outcome left recoverStuckDiagnosticSession able to fall off
the end of its try block when an active run neither aborted nor released,
returning undefined from a Promise<StuckSessionRecoveryOutcome> (tsgo TS2366).
Report that residual path as skipped/active_embedded_run so the watchdog never
clears diagnostic state for work that still owns its session.
* fix(sessions): compare persisted row bytes in session-entry replacement CAS
The replacement projection snapshotted entries with the status/store readers
(no participant projection) and revalidated inside the transaction with
readExactSessionEntryRow, which merges session_participants into the entry.
Any row with a participant that is not its owner therefore serialized
differently on the two sides, so the compare-and-swap threw "SQLite session
entry changed before replacement" on every attempt with no concurrent write
at all. Startup orphan marking selects by status, so such a session could
never be repaired: it stayed status=running across restarts, its rotated
session id never persisted, and every later turn failed with "changed while
starting work", retried by the ingress spool for 24h.
Compare the persisted entry_json bytes on both sides, the same raw-bytes CAS
the sibling lifecycle and projection paths already use, so separately mutable
decorations (participants today, owner columns next) cannot invalidate a
logical-session write. Renames the raw reader to readExactSessionEntryJson
now that it is the general CAS reader rather than repair-only, and aligns
shouldRemoveSessionEntry with the participants-excluding equality its own
callers already use.
* fix(sessions): fail closed when a selected replacement row has no persisted bytes
The raw-bytes CAS could not distinguish "row unchanged" from "row gone". If a
selected row was deleted between hydrating the snapshot entry and reading its
persisted bytes, both the snapshot and the transaction read undefined, the
compare agreed, the transaction's source map stayed empty, and the stale
replacement was written back into the deleted key.
A selected key must hold bytes, so treat a missing snapshot read as the
conflict it is. Reported by ClawSweeper on the previous head.
---------
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
* perf(gateway): remove repeated logging and delivery scans
Exact session-delivery retries no longer scan the full queue. Logging and diagnostics reuse lifecycle-owned settings and listener interest so uninterested projections are skipped, while outbound WebSocket summaries are built only after recipient admission.
* fix(infra): break diagnostic listener import cycle
Keep event-type validation at the diagnostic dispatcher while the process-wide listener presence counter remains a leaf module.
* test(cli): use logging override owner
Exercise late one-shot JSON diagnostics through the canonical logger override setter so lifecycle-cached console settings are invalidated as they are in production.
* test(auth): use logging override owner
Configure the locked-update warning test through the canonical logger override setter so lifecycle-cached console settings are invalidated before assertion.
* test(gateway): normalize redacted media fixture
Compare durable inbound media facts against the public redaction contract so random identifiers that resemble sensitive text do not make the Gateway suite flaky.
* fix(logging): scale RSS pressure to runtime limits
Derive Gateway RSS warning and critical thresholds from the smaller of the effective V8 heap and OS process limit. This preserves heap and rapid-growth pressure detection while avoiding routine false alerts on large heaps.
* test(logging): narrow optional heap fixture
* fix(logging): bound process memory sentinels
* test(logging): pin Bun physical memory fixture
* fix(recovery): reclaim proven-stale reply-only ownership with zero queued backlog
Stuck-session recovery kept reply-only ownership forever when the queued
backlog was empty: isActiveRunProgressStale short-circuits to false at
queueDepth 0, so stale active_reply_work was never reclaimed even after
the durable session became killed. Evaluate staleness for reply-only
ownership without the queue gate (the gate stays for run-handle paths),
so proven-stale reply work expires through the existing abort-and-drain
owner path while global-lane and deferred-maintenance exemptions and
live reply work with recent progress are preserved.
Closes#122227
* fix(recovery): keep maintenance phases out of zero-backlog stale reclaim
The zero-backlog reclaim path (requireQueueBacklog: false) applied to
every reply-only operation, including preflight_compacting and
memory_flushing. Those phases are explicitly recognized as compaction
and may honor a configured timeout above the stale threshold, so a valid
long-running maintenance operation could be force-cleared early.
Restore the queue-backlog guard for the maintenance phases so an
unqueued compaction or memory flush is never reclaimed by this path;
ordinary reply-only ownership keeps the zero-backlog expiry. Adds
regressions for both maintenance phases.
* refactor(plugins): retire deactivate hook alias
* refactor(plugin-sdk): prune retired facade exports
* test(logging): isolate logger test controls
* refactor(logging): internalize file transport controls
* test(plugin-sdk): preserve retired facade coverage
* test(auto-reply): remove stale diagnostic imports
* refactor(logging): delete dead config-read guard
shouldSkipMutatingLoggingConfigRead had no production caller even on main;
it survived the dead-export scan only via logger's testApi re-export. The
test-isolation commit removed that mask, exposing the fossil. Delete the
guard, its test-only re-export, its mock entry, and its dedicated test file.
* refactor(plugin-sdk): retire due compatibility subpaths
* test(plugin-sdk): type group policy predicates
* refactor(plugin-sdk): split removed subpath records
* refactor(secrets): remove retired collector barrel
* test(plugin-sdk): tighten wildcard surface pin
* refactor(plugin-sdk): retire matrix facade metadata
* style(plugin-sdk): format facade metadata
* fix(ci): load channel setup contracts from source
Repair the main-owned regression from 99d662473c (Peter Steinberger): the new env-contract test could consume stale ignored dist metadata instead of the checked-in plugin declaration.
* test(plugin-sdk): refresh API baseline after rebase
* fix(logging): parse diagnostics.stability limit/sinceSeq as strict decimal
normalizeDiagnosticStabilityQuery is the only input boundary of the
diagnostics.stability gateway method (no protocol schema), and its
string branch used Number(): "0x10", "1e3", " 5 ", "+42" were
silently coerced instead of rejected, so malformed clients could
filter snapshots by unintended seq values. String params now require
strict decimal digits, matching the boundary's fail-closed contract.
* fix(logging): reject unsafe integers for numeric stability query inputs
Replace Number.isInteger with parseStrictNonNegativeInteger for the numeric path so MAX_SAFE_INTEGER + 1 (and larger) is rejected uniformly across string and number inputs. Add test cases for MAX_SAFE_INTEGER (accepted) and MAX_SAFE_INTEGER + 1 (rejected) for both limit and sinceSeq.
* fix(logging): use normalization-core parser import
---------
Co-authored-by: Altay <altay@hey.com>
* refactor: consolidate coercion ownership
Centralize four canonical coercion helpers, migrate exact core and plugin duplicates through narrow Plugin SDK facades, and enforce declaration and plugin-normalization ownership boundaries.
The sweep adds eight focused SDK exports while deleting more production and tooling code than it adds. User-visible behavior is unchanged except for safer equivalent object and UI parsing at existing boundaries.
* fix: guard integer option ownership
Register resolveIntegerOption with the canonical function owner and extend the declaration-guard fixture so future local duplicates fail validation.
* fix: keep integer helpers on numeric facade
Remove the unshipped duplicate string-coerce exports and route every affected plugin consumer through the existing number-runtime contract.
* fix: point numeric coercion to number runtime
Make boundary and declaration diagnostics recommend the canonical numeric facade, with failing-before coverage for both guidance paths.
* fix(logging): publish diagnostics ZIP through staged atomic overwrite
A named support export wrote the in-memory ZIP buffer straight to the
final path, so a failed or interrupted overwrite truncated the previous
archive, and an existing file kept its pre-existing (potentially
permissive) mode since writeFile only applies mode on creation.
Route publication through writeExternalFileWithinRoot's sibling staging:
the new archive is written to a private sibling, fsynced, and atomically
renamed over the final path. A failed publication leaves the previous
ZIP untouched with no staging residue, and the rename always installs
the staged file's 0o600 mode. The command now returns the actual
published path.
Fixes#122064
* test(logging): prove failed zip replacement stays atomic
* test(sqlite): create private reliability fixture
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>