instar 1.3.746 → 1.3.747

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +41 -0
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  5. package/dist/config/ConfigDefaults.js +18 -0
  6. package/dist/config/ConfigDefaults.js.map +1 -1
  7. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  8. package/dist/core/devGatedFeatures.js +6 -0
  9. package/dist/core/devGatedFeatures.js.map +1 -1
  10. package/dist/core/durableSecretScrub.d.ts +129 -0
  11. package/dist/core/durableSecretScrub.d.ts.map +1 -0
  12. package/dist/core/durableSecretScrub.js +220 -0
  13. package/dist/core/durableSecretScrub.js.map +1 -0
  14. package/dist/core/types.d.ts +18 -0
  15. package/dist/core/types.d.ts.map +1 -1
  16. package/dist/core/types.js.map +1 -1
  17. package/dist/data/durableOutputChokepoints.d.ts +84 -0
  18. package/dist/data/durableOutputChokepoints.d.ts.map +1 -0
  19. package/dist/data/durableOutputChokepoints.js +145 -0
  20. package/dist/data/durableOutputChokepoints.js.map +1 -0
  21. package/dist/messaging/SessionSummarySentinel.d.ts +28 -0
  22. package/dist/messaging/SessionSummarySentinel.d.ts.map +1 -1
  23. package/dist/messaging/SessionSummarySentinel.js +25 -1
  24. package/dist/messaging/SessionSummarySentinel.js.map +1 -1
  25. package/dist/monitoring/DurableOutputScrubber.d.ts +123 -0
  26. package/dist/monitoring/DurableOutputScrubber.d.ts.map +1 -0
  27. package/dist/monitoring/DurableOutputScrubber.js +179 -0
  28. package/dist/monitoring/DurableOutputScrubber.js.map +1 -0
  29. package/dist/monitoring/guardManifest.d.ts.map +1 -1
  30. package/dist/monitoring/guardManifest.js +23 -0
  31. package/dist/monitoring/guardManifest.js.map +1 -1
  32. package/package.json +1 -1
  33. package/src/data/builtin-manifest.json +2 -2
  34. package/src/data/durableOutputChokepoints.ts +192 -0
  35. package/upgrades/1.3.747.md +61 -0
  36. package/upgrades/side-effects/durable-output-hygiene-standard.md +229 -0
@@ -0,0 +1,229 @@
1
+ # Side-Effects Review — Durable-Output Hygiene Standard (Layer B scrub, defect class 4)
2
+
3
+ **Version / slug:** `durable-output-hygiene-standard`
4
+ **Date:** `2026-07-03`
5
+ **Author:** `echo (instar-dev agent)`
6
+ **Second-pass reviewer:** `not required (tier 2; dark-first, dryRun-defaulted content transform — no allow/deny gate surface)`
7
+
8
+ ## Summary of the change
9
+
10
+ Implements the converged + approved spec `docs/specs/durable-output-hygiene-standard.md`
11
+ (INSTAR-Bench v2 defect class 4: a session-digest writer reproduced a live access token
12
+ verbatim into stored memory). Ships the Layer-B deterministic security floor: a shared
13
+ credential-pattern module (`src/core/durableSecretScrub.ts` — `scrubForStore` /
14
+ `scrubStructured`, metadata-returning, fail-safe-toward-redaction, size-bounded, linear
15
+ patterns only) wrapped by the config-gated `src/monitoring/DurableOutputScrubber.ts`
16
+ (enabled via the developmentAgent dark-gate, dryRun defaults TRUE, FeatureMetricsLedger
17
+ counters under `durable-output-scrub`, per-store poisoning alarm), wired at the FIRST
18
+ chokepoint — `src/messaging/SessionSummarySentinel.ts` `saveSummary` (fields task/
19
+ blockers/files/topics; mandatory `redactionNote` provenance on any altered summary).
20
+ The auditable chokepoint inventory (`src/data/durableOutputChokepoints.ts`) + a
21
+ shrink-only ratchet test pin every other known store as `pending`/`exempt` with owners.
22
+ Config: `monitoring.durableOutputScrub` in ConfigDefaults (enabled OMITTED — dev-gate;
23
+ `dryRun: true`; `perStore: {}`); registered in `DEV_GATED_FEATURES` and in
24
+ `guardManifest` (configPath + dryRunConfigPath, event-driven, no tick). Server boot
25
+ wiring in `src/commands/server.ts` (scrubber construction, metrics sink, poisoning →
26
+ one deduped attention item per store). Types in `src/core/types.ts`. Tests: unit ×3
27
+ (floor, scrubber, chokepoint ratchet) + integration ×1 (SessionSummarySentinel write
28
+ path), 32 tests.
29
+
30
+ ## Decision-point inventory
31
+
32
+ - `SessionSummarySentinel.saveSummary → scrubForStore` — add — a content TRANSFORM at
33
+ the persistence write (never a block: a scrubbed write still lands). Strict no-op
34
+ when the scrubber is absent or not engaged (dark), original-text passthrough in
35
+ dryRun.
36
+ - `DurableOutputScrubber.scrub / scrubRecord` — add — the gating layer: enabled
37
+ (dev-gate) × dryRun (default TRUE) decide whether the computed redaction is applied
38
+ or only counted. Fail-safe: dryRun is `config.dryRun !== false`, so a missing flag
39
+ can never silently enable real redaction.
40
+ - `durableSecretScrub.scrubForStore` — add — pure deterministic floor; on scrub
41
+ exception or oversize input the FIELD is withheld under a typed marker
42
+ (`[REDACTED:scrub-error]` / `[REDACTED:oversize]`) — raw bytes never land because
43
+ the scrub broke, and the event is counted (never fail-open, never silent loss).
44
+ - `Poisoning alarm (onPoisoningSuspected → createAttentionItem)` — add — signal-only
45
+ escalation, one deduped attention item per store (stable id
46
+ `durable-output-poisoning:<store>`); the AttentionTopicGuard flood ceiling backstops.
47
+ - `Chokepoint ratchet (tests/unit/durable-output-chokepoint-ratchet.test.ts)` — add —
48
+ CI-side: statuses may only graduate pending→wired, never regress; a new durable
49
+ persistence path must classify itself in the inventory.
50
+
51
+ ---
52
+
53
+ ## 1. Over-block
54
+
55
+ **What legitimate inputs does this change reject that it shouldn't?**
56
+
57
+ Nothing is rejected — the scrub is a content transform, never a block; every write
58
+ still lands. The over-ACTION risk is a FALSE-POSITIVE redaction destroying a
59
+ legitimate span (the known FP suspect: the JWT-shaped pattern on dotted identifiers).
60
+ That risk is exactly why dryRun defaults TRUE and the dryRun:false flip is the
61
+ operator's endpoint decision on the measured soak packet (spec Frontloaded Decision
62
+ #4: ≥25 reviewed would-redact events with zero FPs, or ≥14 days AND ≥10 reviewed).
63
+ While dark/dryRun (the shipped state) the worst case is a spurious would-redact
64
+ counter — no content is altered.
65
+
66
+ ---
67
+
68
+ ## 2. Under-block
69
+
70
+ **What failure modes does this still miss?**
71
+
72
+ - The floor catches KNOWN TOKEN SHAPES ONLY (stated normatively in the spec/standard
73
+ text): encoded, split, or paraphrased secrets pass the regex floor — Layer A (the
74
+ prompt clause, a separate rollout step) is the only layer that can catch those, and
75
+ it is best-effort.
76
+ - Only the session-summary chokepoint is WIRED in this change; the other inventoried
77
+ stores (self-knowledge facts, KB synthesizer, correction distiller, cartographer
78
+ summaries, learnings + their replicated-receive paths) are `pending` with owners,
79
+ pinned shrink-only by the ratchet so they cannot silently stay unwired.
80
+ - A secret that only PARTIALLY matches a pattern leaves a residual fragment
81
+ (span-replacement, documented + accepted in the spec; mitigated by Layer A + the
82
+ planted-secret bench axis).
83
+
84
+ ---
85
+
86
+ ## 3. Level-of-abstraction fit
87
+
88
+ Right layer: a deterministic, cheap, pattern-based DETECTOR-plus-floor at the
89
+ persistence write — deliberately NOT an LLM gate (LLM compliance is probabilistic; the
90
+ floor is not). It does not re-implement an existing primitive: the ≥3 diverged inline
91
+ scrub copies (`autonomousHeartbeatScrub`, PolicyEnforcementLayer patterns,
92
+ `SecretRedactor.BUILTIN_PATTERNS`) are the drift problem the shared module exists to
93
+ close; they migrate onto it per the spec's rollout step 0 (tracked in the inventory,
94
+ not silently duplicated). It FEEDS existing surfaces (FeatureMetricsLedger, attention
95
+ queue, /guards via guardManifest) instead of growing parallel ones.
96
+
97
+ ---
98
+
99
+ ## 4. Signal vs authority compliance
100
+
101
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
102
+
103
+ - [x] Yes — but sanctioned: this is a detector holding MUTATION authority over durable
104
+ content, classified per the spec (§Decision points touched) as a **sanctioned
105
+ deterministic safety floor** (the fork-bomb spawn-cap precedent), carrying the
106
+ bypass-carries-its-own-cap preconditions: dark-first, dryRun-defaulted soak with
107
+ measured FP rate, MANDATORY provenance marker on every alteration (no silent
108
+ mutation), typed markers + structured kind/offset/length metadata, per-store opt-out
109
+ (`perStore` in config + `exempt` in the inventory), operator-gated enforce flip.
110
+ - It has NO block/allow surface — a write is never refused, only (eventually,
111
+ post-operator-flip) span-redacted with a visible marker.
112
+
113
+ ---
114
+
115
+ ## 5. Interactions
116
+
117
+ - **Shadowing:** the scrub runs INSIDE `saveSummary` on the fresh-generate path only;
118
+ the staleness re-save path deliberately re-persists the already-scrubbed summary
119
+ (re-scrubbing would recompute an empty provenance and drop `redactionNote`). It does
120
+ not run before/after any existing allow/deny check — none exists on this write.
121
+ - **Double-fire:** the existing telemetry scrubs (heartbeat scrub, PolicyEnforcementLayer)
122
+ cover DIFFERENT surfaces (outbound messages, logs); no store is scrubbed twice. The
123
+ poisoning alarm dedupes per store id; the AttentionTopicGuard is the flood backstop.
124
+ - **Races:** the scrubber is constructed once at boot and is stateless per call except
125
+ the in-memory poisoning window (single process, no shared file state) — no new
126
+ cross-process state.
127
+ - **Feedback loops:** metrics/alarm consumers never feed back into the scrub decision;
128
+ a metrics-sink or alarm-callback throw is swallowed (tagged @silent-fallback-ok) so
129
+ observability can never break the persist path.
130
+
131
+ ---
132
+
133
+ ## 6. External surfaces
134
+
135
+ - Other agents / install base: none while dark (fleet resolves the dev-gate to OFF;
136
+ behavior byte-identical to today). On a dev agent, dryRun TRUE means stored bytes
137
+ are still unchanged — only metrics rows + (if a burst) one attention item appear.
138
+ - External systems: none — pure local regex, no LLM call, no spawn-cap slot, no
139
+ egress, no third-party spend.
140
+ - Persistent state: adds the optional `redactionNote` field on persisted
141
+ SessionSummary records (additive JSON field, old readers unaffected) — and only
142
+ post-operator-flip.
143
+ - **Operator surface (Mobile-Complete Operator Actions):** no new operator-facing
144
+ ACTION in this change. The eventual dryRun:false flip is a config decision made on
145
+ an attention-queue decision packet (readable from the phone); no PIN route or form
146
+ is added here.
147
+
148
+ ---
149
+
150
+ ## 6b. Operator-surface quality (Operator-Surface Quality standard)
151
+
152
+ No operator surface — not applicable (no dashboard renderer/approval page/grant form
153
+ touched).
154
+
155
+ ---
156
+
157
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
158
+
159
+ **Declared: machine-local execution over git/code-replicated config, with the
160
+ replicated-store RECEIVE paths named as their own chokepoints.** The scrub runs where
161
+ the write runs; the inventory + config replicate via code. The spec (§3) names the
162
+ real multi-machine hazard — a peer running with the scrub dark writes an unscrubbed
163
+ secret that replication delivers into a scrub-enabled machine's store, bypassing
164
+ writer-side chokepoints — and the inventory therefore lists each replicated store's
165
+ receive path as its own chokepoint (`receivePath`, scrub-on-receive on the LOCAL
166
+ materialized copy only, never what re-replicates; identity fingerprints computed with
167
+ redaction markers normalized out; redaction metadata merge-inert). Those receive-path
168
+ wirings are `pending` entries pinned by the ratchet. No user-facing notices (one-voice
169
+ n/a — the poisoning alarm rides the existing attention queue, which is pool-coalesced
170
+ via P17); no URLs generated; the only durable state is the store content itself, which
171
+ already has an owner machine.
172
+
173
+ ---
174
+
175
+ ## 8. Rollback cost
176
+
177
+ Pure code + config change, shipped dark. Rollback = revert the commit and ship a
178
+ patch. No data migration: while dark/dryRun NO stored bytes differ; post-flip, altered
179
+ records carry `redactionNote` + typed markers (the original span is destroyed BY
180
+ DESIGN — that irreversibility is why the flip is operator-gated, and it is a property
181
+ of the feature, not of rolling it back). No agent state repair; no user-visible
182
+ regression during a rollback window.
183
+
184
+ ---
185
+
186
+ ## Conclusion
187
+
188
+ The review confirmed the shipped state is inert on the fleet (dev-gate OFF), metrics-
189
+ only on the dev agent (dryRun TRUE, fail-safe default `!== false`), and that every
190
+ failure path fails toward redaction/withholding rather than leaking (scrub-error /
191
+ oversize withhold the field under a typed marker; metrics/alarm throws never break the
192
+ write). One design point was tightened during the finish pass: the scrub was
193
+ registered in `guardManifest` with `dryRunConfigPath` so /guards, the boot tripwire,
194
+ and the dark-but-load-bearing classifier cover the posture from day one. Clear to
195
+ ship dark; the enforce flip remains the operator's decision on the soak packet.
196
+
197
+ ---
198
+
199
+ ## Second-pass review (if required)
200
+
201
+ **Reviewer:** not required
202
+ **Independent read of the artifact: not required** — tier 2, no allow/deny gate
203
+ surface, dark-first + dryRun-defaulted; the operator-gated flip is the human check
204
+ on the only destructive transition.
205
+
206
+ ---
207
+
208
+ ## Evidence pointers
209
+
210
+ - Bounded test runs (2026-07-03): `tests/unit/durableSecretScrub.test.ts` (12),
211
+ `tests/unit/DurableOutputScrubber.test.ts` (10),
212
+ `tests/unit/durable-output-chokepoint-ratchet.test.ts` (6),
213
+ `tests/integration/session-summary-durable-scrub.test.ts` (4) — all pass.
214
+ - Ratchet/lint verification: `tests/unit/lint-dev-agent-dark-gate.test.ts` (24, golden
215
+ map regenerated via `attributeEnabledFalsePaths` over the rebased ConfigDefaults),
216
+ `tests/unit/no-silent-fallbacks.test.ts` (back at baseline),
217
+ `tests/unit/lint-guard-manifest.test.ts` (14),
218
+ `tests/unit/feature-delivery-completeness.test.ts` + `tests/unit/CapabilityIndex.test.ts`
219
+ (125), guard-posture-view (52) — all pass. `tsc --noEmit` clean;
220
+ `scripts/docs-coverage.mjs` floors green.
221
+
222
+ ---
223
+
224
+ ## Class-Closure Declaration (display-only mirror)
225
+
226
+ No agent-authored-artifact defect fixed in THIS commit — not applicable. (This change
227
+ BUILDS the class-4 guard machinery per the spec; the defect-class closure itself is
228
+ graded by the Standards Enforcement Coverage audit over the chokepoint inventory once
229
+ the registry text ships, which remains operator-gated.)