@@ -331,6 +331,78 @@ recorded SHA to print exactly the lines that are ours. A correspondence
331331test (` scripts/vendor-patch-ledger.test.ts ` ) fails if a marker anchor
332332does not resolve to a ledger heading, or if a ledger heading has no marker.
333333
334+ ## Approval rail hygiene audit (CL-10679)
335+
336+ App-side redaction built on CL-10678's provenance gate, audited against the
337+ CL-10678 base (` 950a3e37 ` ). Nothing in ` vendor/ ` was modified for this audit;
338+ the vendored findings below are recorded as suppression/disposition notes.
339+
340+ ** Vendored validator-throw log (` reactor.ts:687 ` ,
341+ ` logger.warn ` Correlation validator threw for ${correlationId}...``) —
342+ unreachable in production wiring.** ` tryCorrelate ` only calls a validator when
343+ one is injected (` correlationValidator !== undefined ` ), and both the vendored
344+ ` @intx/agent ` env contract and every production call site forward none (see
345+ the "Bearer correlation IDs and approval provenance" section above and the
346+ comment at ` src/agent/live-tool-dispatch.ts ` ). Foreign ingress is dropped the
347+ line before it could ever be correlated (` createApprovalProvenanceGate ` wraps
348+ the app's ` createAgent ` deliver seam, fail-closed on unknown mailboxes), so
349+ this vendored warn can log a bearer correlation ID only if a future wiring
350+ injects a validator — and then only when that validator throws. When a seam
351+ forwards the app validator, keep ` createApprovalCorrelationValidator ` 's
352+ ` validate ` non-throwing: it returns ` Promise.resolve(...) ` , never throws, so
353+ the death of this branch stays guaranteed even hot-swapped. ** Second vendored
354+ full-ID site, promotion candidate:** the ` processDelivery ` fatal path
355+ (` reactor.ts ` ~ 1906-1923 — ` logger.error ` Correlation dispatch failed: ${cause}``
356+ plus the ` closeMessageRun ` failure text) can carry a full correlation ID a
357+ correlation-path invariant failure embeds in its message (the registry's
358+ double-registration throw names the ID). The deliver-gate pre-drop makes that
359+ fatal branch effectively unreachable today (a throw can arise inside
360+ ` tryCorrelate ` 's patched acceptance/correlation region, but no untrusted
361+ delivery reaches it without first injecting a validator); recorded here so a
362+ future wiring that can reach it gets an ID-free receipt at the next sync.
363+
364+ ** Vendored retry-policy throw log (` harness.ts:1749 ` ) — app-side
365+ disposition, no vendor edit.** The vendored ` runInference ` catch sanitizes the
366+ terminal error it shows the caller and passes policies pre-sanitized inputs,
367+ but the warn about a policy's own exception interpolates raw
368+ (` error=${cause.message} ` ). The app keeps its vendor tree untouched here and
369+ answers the exposure contractually instead: ` RetryPolicy ` implementations are
370+ trusted first-party code and must never embed credential-bearing text in an
371+ exception — documented on ` createCorbitsRetryPolicy `
372+ (` src/agent/retry-policy.ts ` ). The vendored line is a promotion candidate if
373+ upstream adopts a sanitized clone for policy-exception logging; the raw
374+ terminal ` inference.error ` event keeps its existing sanitization.
375+
376+ ** App-side correlation-ID receipt redaction.** All app-side log sites that
377+ interpolated a correlation ID now emit the truncated, hash-suffixed form
378+ (` src/session/redacted-correlation.ts ` ): the parked-approval,
379+ late-decision and duplicate-resume warns in ` src/session/approval-resume.ts ` ,
380+ the forged-approval discard receipt in ` src/session/approval-provenance.ts ` ,
381+ and the acceptance-timeout warn in ` src/tui/approval-delivery.ts ` . The file
382+ log sink (` src/logging/sink.ts ` ) records debug level too, so nothing app-side
383+ carries the full ID at any log level; the full ID lives only on the in-memory
384+ transport that consumes it (message headers, gate table, operator surface).
385+
386+ ** Event-sink audit.** Correlation IDs do not reach model-visible history or
387+ tool-output-readable surfaces: ` run.json ` excludes correlation IDs (status/task/turn
388+ counts and usage only), the audit collector records ` tool.start ` /` tool.done `
389+ keyed by callId — not correlation ID — and ` message.correlated ` /
390+ ` reactor.gate.blocked ` events fan out to in-memory consumers (TUI occupancy
391+ wait, runtime bridge, watchdog recovery) without being persisted. The
392+ ` metadata.json ` checkpoint persists pending operations with their correlation
393+ IDs by design (the restart-resume path re-registers parked calls) — that is
394+ the credential cell's own storage, not a log or event sink.
395+
396+ ** Approval snapshot args at rest (CL-10679 R5).** The app's context-store
397+ write boundary (` src/session/pending-snapshot-scrub.ts ` , wired into
398+ ` src/session/optimized-context-store.ts ` ) now runs the result-side secret-shape
399+ scrubber (` scrubSecretShapedValue ` ) over every persisted
400+ ` approvalSnapshot.arguments ` . The re-dispatch artifact (` suspendedCall ` )
401+ stays raw because the approved call must re-run with the arguments the
402+ operator saw; residual at-rest exposure in ` suspendedCall.arguments ` inside
403+ ` metadata.json ` is documented as a known gap for the vendored re-dispatch
404+ owner, not silently widened here.
405+
334406## Re-syncing a vendored package to a newer upstream commit
335407
3364081 . In the read-only upstream clone, confirm the commit to sync to and note
0 commit comments