instar 1.3.739 → 1.3.741

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.
@@ -14,6 +14,47 @@
14
14
  */
15
15
  import fs from 'node:fs';
16
16
  import path from 'node:path';
17
+ /**
18
+ * Read only the last `maxBytes` of a file and return its non-empty lines,
19
+ * newest last. Bounded by construction: an arbitrarily large log is never
20
+ * loaded whole (the readFileSync-whole-file pattern that froze the event loop
21
+ * on a 142MB reap-log, 2026-07-03). If the read starts mid-file (the file is
22
+ * larger than `maxBytes`), the first — possibly partial — line is dropped so a
23
+ * torn record is never mis-parsed. Never throws; an absent/unreadable file
24
+ * yields [].
25
+ */
26
+ function tailLines(filePath, maxBytes) {
27
+ let fd;
28
+ try {
29
+ const size = fs.statSync(filePath).size;
30
+ if (size === 0)
31
+ return [];
32
+ const readBytes = Math.min(size, maxBytes);
33
+ const start = size - readBytes;
34
+ const buf = Buffer.allocUnsafe(readBytes);
35
+ fd = fs.openSync(filePath, 'r');
36
+ fs.readSync(fd, buf, 0, readBytes, start);
37
+ const text = buf.toString('utf-8');
38
+ const lines = text.split('\n');
39
+ // Dropped: a leading partial line when we didn't read from byte 0.
40
+ if (start > 0 && lines.length > 0)
41
+ lines.shift();
42
+ return lines.filter((l) => l.trim().length > 0);
43
+ }
44
+ catch {
45
+ return []; // @silent-fallback-ok — absent/unreadable ⇒ no rows.
46
+ }
47
+ finally {
48
+ if (fd !== undefined) {
49
+ try {
50
+ fs.closeSync(fd);
51
+ }
52
+ catch {
53
+ /* fd already gone */
54
+ }
55
+ }
56
+ }
57
+ }
17
58
  const NOTIFY_OUTCOMES = new Set([
18
59
  'enqueued',
19
60
  'sent',
@@ -21,12 +62,47 @@ const NOTIFY_OUTCOMES = new Set([
21
62
  'no-topic',
22
63
  'enqueue-failed',
23
64
  ]);
65
+ /** Rotate the reap-log once it crosses this many bytes. The current file is
66
+ * renamed to `<path>.1` (O(1), no data rewrite) and a fresh file started, so
67
+ * the on-disk log can NEVER grow unbounded — even if some future caller floods
68
+ * it past the transition-dedup below. One backup generation is retained;
69
+ * `read()` merges its tail so recent history survives a rotation boundary. */
70
+ const MAX_LOG_BYTES = 16 * 1024 * 1024;
71
+ /** `read()` only ever pulls the last this-many bytes of a log file, so a large
72
+ * reap-log can never be slurped whole into memory (the 142MB readFileSync +
73
+ * split that blocked the event loop, 2026-07-03). Generous vs the default
74
+ * `limit` of 200 rows (~300 B/row ⇒ ~6.5k rows fit in 2 MB). */
75
+ const TAIL_READ_BYTES = 2 * 1024 * 1024;
76
+ /** Cap on the in-memory transition-dedup map. Live session names are bounded
77
+ * (dozens); this is a safety ceiling so a pathological churn of distinct names
78
+ * can't grow the map without bound. Oldest entries are pruned first. */
79
+ const MAX_SKIP_STATE = 2000;
24
80
  export class ReapLog {
25
81
  logPath;
26
82
  machineId;
27
- constructor(stateDir, machineId) {
83
+ maxLogBytes;
84
+ tailReadBytes;
85
+ maxSkipState;
86
+ /** session name → last-LOGGED skip signature (`${reason}::${skipped}`).
87
+ * A `recordSkipped` whose signature equals the session's last-logged one is
88
+ * the SAME permanent veto being re-evaluated at tick speed (open-commitment,
89
+ * not-lease-holder, protected …) — it is NOT re-appended. This is the
90
+ * primary cure for the reaper self-inflicted log flood (3218 open-commitment
91
+ * + 1608 not-lease-holder repeat rows ⇒ 142MB, 2026-07-03): mirror the
92
+ * reaper-audit "log on transition, not every tick" pattern. Cleared when the
93
+ * session is reaped so a same-named successor logs its first skip fresh. */
94
+ skipState = new Map();
95
+ /** Cheap running estimate of the current log's byte size, so rotation is
96
+ * decided WITHOUT a statSync on every append. Seeded from the real size on
97
+ * first append, then advanced by each write; re-synced on rotation. -1 =
98
+ * not yet seeded. */
99
+ approxSize = -1;
100
+ constructor(stateDir, machineId, opts = {}) {
28
101
  this.logPath = path.join(stateDir, '..', 'logs', 'reap-log.jsonl');
29
102
  this.machineId = machineId;
103
+ this.maxLogBytes = opts.maxLogBytes ?? MAX_LOG_BYTES;
104
+ this.tailReadBytes = opts.tailReadBytes ?? TAIL_READ_BYTES;
105
+ this.maxSkipState = opts.maxSkipState ?? MAX_SKIP_STATE;
30
106
  }
31
107
  recordReaped(e) {
32
108
  this.append({
@@ -44,6 +120,9 @@ export class ReapLog {
44
120
  ...(e.workEvidence && e.workEvidence.length > 0 ? { workEvidence: e.workEvidence } : {}),
45
121
  ...(e.evidenceSource ? { evidenceSource: e.evidenceSource } : {}),
46
122
  });
123
+ // The session is gone — drop its skip-dedup state so a same-named successor
124
+ // logs its first skip fresh and the map never leaks reaped names.
125
+ this.forgetSkip(e.session);
47
126
  }
48
127
  /**
49
128
  * Append one reap-notice delivery outcome record (reap-notify spec R1.3).
@@ -66,6 +145,18 @@ export class ReapLog {
66
145
  });
67
146
  }
68
147
  recordSkipped(e) {
148
+ // Log-on-transition: a reaper evaluates a permanently-vetoed session
149
+ // (open-commitment, not-lease-holder, protected, …) on EVERY tick and would
150
+ // emit an identical `skipped` row each time — 5k+ repeat rows ⇒ a 142MB log
151
+ // that froze the event loop when read (2026-07-03). Only append when the
152
+ // skip STATE changes for this session; a re-evaluation with the same
153
+ // (reason, skipped) is the same veto and is dropped. The reaper keeps
154
+ // evaluating every tick, so the moment the veto lifts (commitment closes,
155
+ // lease moves) the state changes and the next skip — or the reap — logs.
156
+ const sig = `${e.reason}::${e.skipped}`;
157
+ if (this.skipState.get(e.session) === sig)
158
+ return;
159
+ this.rememberSkip(e.session, sig);
69
160
  this.append({
70
161
  ts: new Date().toISOString(),
71
162
  type: 'skipped',
@@ -78,27 +169,77 @@ export class ReapLog {
78
169
  machine: this.machineId?.(),
79
170
  });
80
171
  }
172
+ /** Record a session's last-logged skip signature, enforcing the map ceiling
173
+ * (oldest-first prune — Map preserves insertion order). Re-inserting an
174
+ * existing key does not reorder it, which is fine: churny NEW names are the
175
+ * growth risk, and those get pruned. */
176
+ rememberSkip(session, sig) {
177
+ this.skipState.set(session, sig);
178
+ while (this.skipState.size > this.maxSkipState) {
179
+ const oldest = this.skipState.keys().next().value;
180
+ if (oldest === undefined)
181
+ break;
182
+ this.skipState.delete(oldest);
183
+ }
184
+ }
185
+ /** Forget a session's skip state — call when the session is gone (reaped) so a
186
+ * same-named successor logs its first skip fresh and the map can't leak. */
187
+ forgetSkip(session) {
188
+ this.skipState.delete(session);
189
+ }
81
190
  append(entry) {
82
191
  try {
83
192
  fs.mkdirSync(path.dirname(this.logPath), { recursive: true });
84
- fs.appendFileSync(this.logPath, JSON.stringify(entry) + '\n');
193
+ const line = JSON.stringify(entry) + '\n';
194
+ this.rotateIfNeeded(Buffer.byteLength(line));
195
+ fs.appendFileSync(this.logPath, line);
196
+ if (this.approxSize >= 0)
197
+ this.approxSize += Buffer.byteLength(line);
85
198
  }
86
199
  catch {
87
200
  // never throw from the audit sink
88
201
  }
89
202
  }
90
- /** Read the most-recent `limit` entries (newest last), tolerating partial/corrupt lines. */
91
- read(limit = 200) {
92
- let raw;
203
+ /** Roll the log to `<path>.1` (single retained generation) once it would cross
204
+ * MAX_LOG_BYTES, so the file can never grow unbounded. Rename is O(1) — no
205
+ * data rewrite on the append hot path (rewriting a large file synchronously
206
+ * here would reintroduce the very event-loop stall we're removing). Seeds the
207
+ * cheap size estimate from the real file size on first use. */
208
+ rotateIfNeeded(incomingBytes) {
209
+ if (this.approxSize < 0) {
210
+ try {
211
+ this.approxSize = fs.statSync(this.logPath).size;
212
+ }
213
+ catch {
214
+ this.approxSize = 0; // absent file ⇒ empty
215
+ }
216
+ }
217
+ if (this.approxSize + incomingBytes <= this.maxLogBytes)
218
+ return;
93
219
  try {
94
- raw = fs.readFileSync(this.logPath, 'utf-8');
220
+ fs.renameSync(this.logPath, `${this.logPath}.1`); // overwrites a prior .1
95
221
  }
96
222
  catch {
97
- // @silent-fallback-ok — no log file yet means no reaps recorded; an empty
98
- // list is the correct answer, not a degraded one.
99
- return [];
223
+ // @silent-fallback-ok — if the roll fails the append still proceeds; the
224
+ // read path is tail-bounded so an oversize file is a perf hit, never a
225
+ // correctness failure.
226
+ }
227
+ this.approxSize = 0;
228
+ }
229
+ /** Read the most-recent `limit` entries (newest last), tolerating partial/corrupt lines.
230
+ * Only the last TAIL_READ_BYTES of each file are ever read, so a large log can
231
+ * never be slurped whole into memory (the readFileSync-whole-file freeze). If
232
+ * the current file's tail doesn't yield enough rows and a rotated `.1` backup
233
+ * exists, its tail is merged so recent history survives a rotation boundary. */
234
+ read(limit = 200) {
235
+ const wanted = limit > 0 ? limit : Number.MAX_SAFE_INTEGER;
236
+ let lines = tailLines(this.logPath, this.tailReadBytes);
237
+ if (lines.length < wanted) {
238
+ // Backfill from the rotated generation (older lines first).
239
+ const older = tailLines(`${this.logPath}.1`, this.tailReadBytes);
240
+ if (older.length > 0)
241
+ lines = older.concat(lines);
100
242
  }
101
- const lines = raw.split('\n').filter((l) => l.trim().length > 0);
102
243
  const tail = limit > 0 ? lines.slice(-limit) : lines;
103
244
  const out = [];
104
245
  for (const line of tail) {
@@ -1 +1 @@
1
- {"version":3,"file":"ReapLog.js","sourceRoot":"","sources":["../../src/monitoring/ReapLog.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ReapLog.js","sourceRoot":"","sources":["../../src/monitoring/ReapLog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;;;;;;;GAQG;AACH,SAAS,SAAS,CAAC,QAAgB,EAAE,QAAgB;IACnD,IAAI,EAAsB,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;QACxC,IAAI,IAAI,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAC1B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC3C,MAAM,KAAK,GAAG,IAAI,GAAG,SAAS,CAAC;QAC/B,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;QAC1C,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QAChC,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;QAC1C,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/B,mEAAmE;QACnE,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,CAAC,KAAK,EAAE,CAAC;QACjD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC,CAAC,qDAAqD;IAClE,CAAC;YAAS,CAAC;QACT,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;YACrB,IAAI,CAAC;gBACH,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YACnB,CAAC;YAAC,MAAM,CAAC;gBACP,qBAAqB;YACvB,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAeD,MAAM,eAAe,GAAwB,IAAI,GAAG,CAAC;IACnD,UAAU;IACV,MAAM;IACN,uBAAuB;IACvB,UAAU;IACV,gBAAgB;CACjB,CAAC,CAAC;AAmDH;;;;+EAI+E;AAC/E,MAAM,aAAa,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AACvC;;;iEAGiE;AACjE,MAAM,eAAe,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AACxC;;yEAEyE;AACzE,MAAM,cAAc,GAAG,IAAI,CAAC;AAU5B,MAAM,OAAO,OAAO;IACD,OAAO,CAAS;IAChB,SAAS,CAA4B;IACrC,WAAW,CAAS;IACpB,aAAa,CAAS;IACtB,YAAY,CAAS;IACtC;;;;;;;iFAO6E;IAC5D,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvD;;;0BAGsB;IACd,UAAU,GAAG,CAAC,CAAC,CAAC;IAExB,YAAY,QAAgB,EAAE,SAAoC,EAAE,OAAuB,EAAE;QAC3F,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC;QACnE,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,aAAa,CAAC;QACrD,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,eAAe,CAAC;QAC3D,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,cAAc,CAAC;IAC1D,CAAC;IAED,YAAY,CAAC,CAWZ;QACC,IAAI,CAAC,MAAM,CAAC;YACV,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC5B,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,UAAU;YACxC,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/C,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE;YAC3B,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrD,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1D,GAAG,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACxF,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClE,CAAC,CAAC;QACH,4EAA4E;QAC5E,kEAAkE;QAClE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IAED;;;;;OAKG;IACH,YAAY,CAAC,CAMZ;QACC,IAAI,CAAC,MAAM,CAAC;YACV,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC5B,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,CAAC,CAAC,QAAQ;YACnB,WAAW,EAAE,GAAG;YAChB,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,OAAO;YAC7B,WAAW,EAAE,UAAU,CAAC,CAAC,OAAO,EAAE;YAClC,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE;YAC3B,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,IAAI,IAAI,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAChF,OAAO,EAAE,CAAC,CAAC,OAAO;SACnB,CAAC,CAAC;IACL,CAAC;IAED,aAAa,CAAC,CAMb;QACC,qEAAqE;QACrE,4EAA4E;QAC5E,4EAA4E;QAC5E,yEAAyE;QACzE,qEAAqE;QACrE,sEAAsE;QACtE,0EAA0E;QAC1E,yEAAyE;QACzE,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;QACxC,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,GAAG;YAAE,OAAO;QAClD,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAClC,IAAI,CAAC,MAAM,CAAC;YACV,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC5B,IAAI,EAAE,SAAS;YACf,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,WAAW,EAAE,WAAW,CAAC,CAAC,OAAO,EAAE;YACnC,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE;SAC5B,CAAC,CAAC;IACL,CAAC;IAED;;;6CAGyC;IACjC,YAAY,CAAC,OAAe,EAAE,GAAW;QAC/C,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACjC,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;YAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;YAClD,IAAI,MAAM,KAAK,SAAS;gBAAE,MAAM;YAChC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAED;iFAC6E;IACrE,UAAU,CAAC,OAAe;QAChC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;IAEO,MAAM,CAAC,KAAmB;QAChC,IAAI,CAAC;YACH,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;YAC1C,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;YAC7C,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YACtC,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC;gBAAE,IAAI,CAAC,UAAU,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACvE,CAAC;QAAC,MAAM,CAAC;YACP,kCAAkC;QACpC,CAAC;IACH,CAAC;IAED;;;;oEAIgE;IACxD,cAAc,CAAC,aAAqB;QAC1C,IAAI,IAAI,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;YACxB,IAAI,CAAC;gBACH,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC;YACnD,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,sBAAsB;YAC7C,CAAC;QACH,CAAC;QACD,IAAI,IAAI,CAAC,UAAU,GAAG,aAAa,IAAI,IAAI,CAAC,WAAW;YAAE,OAAO;QAChE,IAAI,CAAC;YACH,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,wBAAwB;QAC5E,CAAC;QAAC,MAAM,CAAC;YACP,yEAAyE;YACzE,uEAAuE;YACvE,uBAAuB;QACzB,CAAC;QACD,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;IACtB,CAAC;IAED;;;;qFAIiF;IACjF,IAAI,CAAC,KAAK,GAAG,GAAG;QACd,MAAM,MAAM,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,gBAAgB,CAAC;QAC3D,IAAI,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;QACxD,IAAI,KAAK,CAAC,MAAM,GAAG,MAAM,EAAE,CAAC;YAC1B,4DAA4D;YAC5D,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;YACjE,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;gBAAE,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACpD,CAAC;QACD,MAAM,IAAI,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QACrD,MAAM,GAAG,GAAmB,EAAE,CAAC;QAC/B,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;YACxB,IAAI,CAAC;gBACH,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAA0B,CAAC,CAAC,CAAC;YAC3E,CAAC;YAAC,MAAM,CAAC;gBACP,iEAAiE;YACnE,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAEO,cAAc,CAAC,KAA4B;QACjD,2EAA2E;QAC3E,0EAA0E;QAC1E,qEAAqE;QACrE,MAAM,IAAI,GACR,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;QACvF,MAAM,OAAO,GAAG,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;QAC9E,MAAM,OAAO,GACX,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,IAAI,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC;YACrE,CAAC,CAAE,KAAK,CAAC,OAA6B;YACtC,CAAC,CAAC,SAAS,CAAC;QAChB,IAAI,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;QACpC,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,WAAW;gBACT,IAAI,KAAK,SAAS;oBAChB,CAAC,CAAC,WAAW,OAAO,IAAI,SAAS,EAAE;oBACnC,CAAC,CAAC,IAAI,KAAK,QAAQ;wBACjB,CAAC,CAAC,UAAU,OAAO,IAAI,SAAS,EAAE;wBAClC,CAAC,CAAC,UAAU,CAAC;QACrB,CAAC;QAED,MAAM,UAAU,GACd,KAAK,CAAC,UAAU,KAAK,UAAU,IAAI,KAAK,CAAC,UAAU,KAAK,sBAAsB;YAC5E,CAAC,CAAC,KAAK,CAAC,UAAU;YAClB,CAAC,CAAC,SAAS,CAAC;QAChB,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC;YACpD,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;YACtE,CAAC,CAAC,SAAS,CAAC;QAEd,OAAO;YACL,EAAE,EAAE,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;YACvE,IAAI;YACJ,OAAO,EAAE,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;YACtE,WAAW,EAAE,OAAO,KAAK,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;YAClF,MAAM,EAAE,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;YACnE,WAAW;YACX,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,OAAO;YACP,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrC,GAAG,CAAC,OAAO,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzE,GAAG,CAAC,YAAY,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACpE,GAAG,CAAC,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3E,GAAG,CAAC,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACxE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAChC,CAAC;IACJ,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.739",
3
+ "version": "1.3.741",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-07-03T22:57:15.282Z",
5
- "instarVersion": "1.3.739",
4
+ "generatedAt": "2026-07-03T23:25:24.796Z",
5
+ "instarVersion": "1.3.741",
6
6
  "entryCount": 202,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -360,3 +360,203 @@ export const LLM_JUDGES_CLAIMS: Readonly<Record<string, JudgesClaimsFlag>> = {
360
360
  CartographerSweep: false, // authors doc-tree summaries over code
361
361
  StandardsCoverageEnrichment: false, // enriches standards-coverage rows
362
362
  };
363
+
364
+ // ───────────────────────────────────────────────────────────────────────────
365
+ // Prompt↔parser contract standard (defect class 1 — docs/specs/prompt-parser-
366
+ // contract-standard.md §4)
367
+ //
368
+ // The `contract` axis of the program's shared per-callsite metadata record
369
+ // (class-closure-gate.md §"Program-shared machinery"). It co-locates in THIS
370
+ // file with the bench-coverage record it extends, exactly like the sibling
371
+ // `untrustedInput` (authority-clause) and `judgesClaims` (evidence-bar) axes.
372
+ //
373
+ // THE FIELD IS REQUIRED AND EXPLICIT FOR EVERY COMPONENT_CATEGORY KEY — there is
374
+ // NO DEFAULT. A callsite whose output is machine-parsed into a CLOSED, taught
375
+ // verdict/decision vocabulary (the B15 failure surface: prompt teaches "B15",
376
+ // parser accepts only "B15_CONTEXT_DEATH_STOP") either NAMES its contract-test
377
+ // file (`{ contractTest: '<path>' }`, covered) or is queued in the shrink-only
378
+ // pending set (`{ pending: 'contract-wave-1' | 'contract-wave-2' }`). A callsite
379
+ // with NO such closed-vocabulary parse — no live LLM prompt, a fixed canary, or
380
+ // free-text / open-set CONTENT consumed as data (summaries, extractions,
381
+ // syntheses, briefs, reviews, open-set id routing) — is `{ false: '<reason>' }`
382
+ // and pinned shrink-only in
383
+ // tests/unit/parser-contract-classification-ratchet.test.ts. A silent omission
384
+ // is red CI, so the flag can NEVER default toward the un-contracted state.
385
+ //
386
+ // POLARITY (spec §3: "Undeclared content is hazard-scanned by default"): the
387
+ // default pulls TOWARD "needs a contract" — a callsite is `false` only with an
388
+ // argued reason, exactly like the sibling `untrustedInput` axis defaults toward
389
+ // `true`.
390
+ //
391
+ // WAVE-1 is the spec-named SHIPPED-FIX set (rollout §0/§1): the four
392
+ // highest-stakes parsed callsites — MessagingToneGate (the motivating B15
393
+ // defect), ExternalOperationGate, CompletionEvaluator (stop-judge), and
394
+ // InputClassifier. It is pinned as a seed floor in the ratchet so a
395
+ // highest-stakes callsite can never silently slip out of scope. WAVE-2 is every
396
+ // other enumerated-verdict/decision callsite, graduating on the shrink-only
397
+ // schedule (rollout §2).
398
+ //
399
+ // DARK / REPORT-ONLY: this record is build-time metadata read ONLY by the new
400
+ // pinned ratchet. Nothing here is a contract test yet (those render the REAL
401
+ // production prompt and need the live-builder render refactor, deferred to its
402
+ // own A/B-gated increments — spec rollout §0/§1). The pending set IS the report
403
+ // (spec §4: "report-only inventory happens by construction").
404
+ //
405
+ // A cross-check lint flags any GATE/SENTINEL-category callsite marked `false`
406
+ // for review (these categories most often parse a verdict) — see the ratchet's
407
+ // REVIEWED_FALSE_PARSER_GATE pin.
408
+ // ───────────────────────────────────────────────────────────────────────────
409
+
410
+ export type ParserContractFlag =
411
+ | { contractTest: string }
412
+ | { pending: 'contract-wave-1' | 'contract-wave-2' }
413
+ | { false: string };
414
+
415
+ export const LLM_PARSER_CONTRACT: Readonly<Record<string, ParserContractFlag>> = {
416
+ // ── WAVE-1: the four spec-named highest-stakes parsed callsites (rollout §0) ──
417
+ MessagingToneGate: { pending: 'contract-wave-1' }, // THE motivating defect: prompt taught "B15", parser accepts only "B15_CONTEXT_DEATH_STOP"
418
+ ExternalOperationGate: { pending: 'contract-wave-1' }, // parses a closed mutability/reversibility classification
419
+ CompletionEvaluator: { pending: 'contract-wave-1' }, // the stop-judge surface parses a closed done/blocked/continue verdict
420
+ InputClassifier: { pending: 'contract-wave-1' }, // parses a closed auto-approve vs relay decision
421
+
422
+ // ── WAVE-2: every other enumerated-verdict / decision callsite (rollout §2) ──
423
+ MessageSentinel: { pending: 'contract-wave-2' }, // closed intent set (pause / emergency / normal)
424
+ LLMSanitizer: { pending: 'contract-wave-2' }, // parses a closed sanitize verdict/decision
425
+ WarrantsReplyGate: { pending: 'contract-wave-2' }, // closed should-reply yes/no verdict
426
+ InputGuard: { pending: 'contract-wave-2' }, // closed input-coherence verdict
427
+ StallTriageNurse: { pending: 'contract-wave-2' }, // closed stall-triage diagnosis label
428
+ CommitmentSentinel: { pending: 'contract-wave-2' }, // closed commitment-detected verdict + structured envelope
429
+ PresenceProxy: { pending: 'contract-wave-2' }, // closed tier-3 stall verdict
430
+ ProjectDriftChecker: { pending: 'contract-wave-2' }, // closed on-project verdict
431
+ TemporalCoherenceChecker: { pending: 'contract-wave-2' }, // closed temporal-coherence verdict
432
+ SessionWatchdog: { pending: 'contract-wave-2' }, // closed stuck verdict
433
+ ResumeQueueDrainer: { pending: 'contract-wave-2' }, // closed resume-sanity verdict
434
+ TopicIntentArcCheck: { pending: 'contract-wave-2' }, // closed arc-check classification label
435
+ TelegramAdapter: { pending: 'contract-wave-2' }, // stall-confirm — closed genuinely-stalled verdict
436
+ SlackAdapter: { pending: 'contract-wave-2' }, // stall-confirm (byte-identical prompt to Telegram's; parity)
437
+ PromptGate: { pending: 'contract-wave-2' }, // closed injection-detected verdict
438
+ UnjustifiedStopGate: { pending: 'contract-wave-2' }, // closed stop-justified verdict
439
+ OverrideDetector: { pending: 'contract-wave-2' }, // closed override-intent verdict
440
+ TaskClassifier: { pending: 'contract-wave-2' }, // closed task-type label set
441
+ ResumeValidator: { pending: 'contract-wave-2' }, // closed resume-UUID match yes/no verdict
442
+ CoherenceReviewer: { pending: 'contract-wave-2' }, // gate-triage — closed coherence verdict
443
+
444
+ // ── Argued false (pinned shrink-only) — no closed-vocabulary verdict parse ──
445
+ // No live LLM callsite, a fixed canary, or free-text / open-set content.
446
+ InputDetector: {
447
+ false:
448
+ 'attribution-manifest alias only (a legacy prompt-pattern matcher); the live matcher calls with attribution PromptGate, contracted there',
449
+ },
450
+ SessionActivitySentinel: {
451
+ false:
452
+ 'authors a free-text activity DIGEST — the product is prose, not a closed verdict vocabulary a prompt teaches and a parser gates on',
453
+ },
454
+ PromiseBeacon: {
455
+ false:
456
+ 'no live LLM prompt — generateStatusLine/classifyProgress hooks are unwired at the construction site; nothing parses a taught vocabulary (matches its bench-coverage exemption)',
457
+ },
458
+ InteractivePoolCanaryJudge: {
459
+ false:
460
+ 'judges a FIXED known-answer canary probe — the expected output is a constant, so the canary is its own contract; a prompt↔parser contract test would re-test the same constant',
461
+ },
462
+ SessionSummarySentinel: {
463
+ false:
464
+ 'extracts task/phase/files as open-set free-text FIELDS — there is no closed taught verdict vocabulary, so the B15 prompt↔parser drift cannot arise here',
465
+ },
466
+ AutoApprover: {
467
+ false:
468
+ 'mechanical key injection + audit logging, no LLM prompt of its own; the upstream parsed decision is InputClassifier.classify(), contracted there',
469
+ },
470
+ IntegrationGate: {
471
+ false:
472
+ 'no LLM prompt of its own — delegates to JobReflector.reflect(); zero LLM-provider callsites of its own that parse a taught vocabulary',
473
+ },
474
+ CoherenceGate: {
475
+ false:
476
+ 'no callsite carries attribution CoherenceGate — all LLM calls flow through CoherenceReviewer.callApi(), contracted there',
477
+ },
478
+ JobReflector: {
479
+ false:
480
+ 'reflection produces free-text content over a job — no closed taught verdict vocabulary a parser gates on (its own bench coverage is wave-3)',
481
+ },
482
+ crossModelReviewer: {
483
+ false:
484
+ 'produces a free-text review of a SPEC document — no closed output vocabulary the prompt teaches and a parser must accept',
485
+ },
486
+ SelfKnowledgeTree: {
487
+ false:
488
+ 'extracts self-knowledge tree fragments as content that is stored/merged — no closed verdict token gates a branch on a taught vocabulary',
489
+ },
490
+ TreeTriage: {
491
+ false:
492
+ 'triages knowledge-tree fragments into content — no closed taught output vocabulary a parser must accept or reject',
493
+ },
494
+ TopicSummarizer: {
495
+ false:
496
+ 'produces a free-text topic summary — the prose is the product; there is no closed vocabulary the prompt teaches and a parser gates on',
497
+ },
498
+ ContextualEvaluator: {
499
+ false:
500
+ 'evaluates context relevance into content — no closed taught verdict vocabulary that a parser accepts a promised form of',
501
+ },
502
+ RelationshipManager: {
503
+ false:
504
+ 'extracts relationship facts as open-set structured content — no closed taught vocabulary a prompt promises and a parser gates on',
505
+ },
506
+ StandardsConformanceReviewer: {
507
+ false:
508
+ 'reviews artifact-vs-standard conformance as content — no closed output vocabulary the prompt teaches and a parser must accept',
509
+ },
510
+ DiscoveryEvaluator: {
511
+ false:
512
+ 'evaluates serendipity discoveries into content — no closed taught verdict vocabulary a parser accepts a promised form of',
513
+ },
514
+ Usher: {
515
+ false:
516
+ 'routes a turn to candidate TOPIC IDS — an OPEN, machine-supplied set, not a closed taught vocabulary the prompt fixes and a parser gates on',
517
+ },
518
+ TopicIntentExtractor: {
519
+ false:
520
+ 'extracts a topic-intent description from a turn — free-text content, not a closed taught verdict vocabulary a parser accepts',
521
+ },
522
+ PreCompactionFlush: {
523
+ false:
524
+ 'extracts durable facts before compaction as free-text content — no closed taught output vocabulary a parser gates on',
525
+ },
526
+ TreeSynthesis: {
527
+ false:
528
+ 'synthesizes knowledge fragments into a free-text answer — the prose is the product, no closed taught verdict vocabulary a parser accepts',
529
+ },
530
+ LLMConflictResolver: {
531
+ false:
532
+ 'resolves divergent multi-machine state into a merged value/content — no closed taught verdict vocabulary a prompt promises and a parser gates on',
533
+ },
534
+ openConversationBrief: {
535
+ false:
536
+ 'generates a free-text A2A conversation brief — the prose is the product, no closed output vocabulary the prompt teaches and a parser must accept',
537
+ },
538
+ 'a2a-checkin': {
539
+ false:
540
+ 'summarizes A2A check-in threads into free-text content — no closed taught verdict vocabulary a parser accepts a promised form of',
541
+ },
542
+ 'correction-learning': {
543
+ false:
544
+ 'distills recurring corrections into a preference (content) — no closed taught output vocabulary a parser gates on',
545
+ },
546
+ 'mentor-stage-b': {
547
+ false:
548
+ 'classifies mentor signals over mentee output into differential content — no closed taught verdict vocabulary a parser gates on (its own bench coverage is wave-3)',
549
+ },
550
+ PipeSessionSpawner: {
551
+ false:
552
+ 'spawns sessions from task descriptions — no LLM output parsed into a closed taught verdict vocabulary',
553
+ },
554
+ CartographerSweep: {
555
+ false:
556
+ 'authors doc-tree summaries over code as free text — the prose is the product, no closed taught output vocabulary a parser gates on',
557
+ },
558
+ StandardsCoverageEnrichment: {
559
+ false:
560
+ 'enriches standards-coverage rows with content — no closed taught verdict vocabulary a prompt promises and a parser accepts',
561
+ },
562
+ };
@@ -0,0 +1,65 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ Three self-limiting changes to `src/monitoring/ReapLog.ts` (the audit trail behind
9
+ `GET /sessions/reap-log`) that stop the reaper from flooding — and freezing on — its own log.
10
+
11
+ The reaper re-evaluates every session on each tick. When a session **can't** be reaped yet — it
12
+ holds an open commitment, or it belongs to another machine (`not-lease-holder`), or it's
13
+ protected — the reaper skips it and recorded a `skipped` row **every tick**. On a live agent
14
+ that produced 3218 `open-commitment` + 1608 `not-lease-holder` **identical repeat rows** and
15
+ grew `reap-log.jsonl` to **142MB / 463k lines** (observed 2026-07-03). Worse, `read()` did
16
+ `fs.readFileSync(WHOLE file).split('\n')` just to return the last N rows — a 142MB slurp that
17
+ **blocked the event loop** (~90s CPU-bound stalls) whenever the route was queried.
18
+
19
+ - **Write-side transition dedup (primary cure).** `recordSkipped()` now logs on *transition*:
20
+ it keeps an in-memory `session → last-logged skip signature (${reason}::${skipped})` map and
21
+ drops a re-append when the signature is unchanged — mirroring the sibling `reaper-audit.jsonl`
22
+ "log on change, not every tick" discipline. State is cleared when the session is reaped
23
+ (`forgetSkip`) so a same-named successor logs fresh and the map can't leak (hard ceiling
24
+ `MAX_SKIP_STATE`, 2000, oldest-pruned). The reaper still **evaluates** every tick, so the
25
+ moment a veto lifts the next skip — or the reap — is logged.
26
+ - **Read-side bounded tail.** `read()` now reads only the last `TAIL_READ_BYTES` (2MB) via a
27
+ positional `readSync`, drops a torn leading line, and merges the rotated `.1` tail if needed —
28
+ so a large log can never be slurped whole into memory again.
29
+ - **Size-cap rotation (defense-in-depth).** `append()` rolls the file to `<path>.1` (O(1)
30
+ `renameSync`, no hot-path rewrite) once it crosses `MAX_LOG_BYTES` (16MB), so it can never
31
+ grow unbounded even if a future caller floods it.
32
+
33
+ Caps are overridable via an optional `ReapLogOptions` constructor arg (defaults = the module
34
+ constants) purely so tests can trigger rotation cheaply; the production callsite is unchanged.
35
+
36
+ ## What to Tell Your User
37
+
38
+ If your agent's server ever felt like it "froze for a minute" periodically, or your disk was
39
+ filling up with a giant session-audit log, this is a fix for that. The background tidier that
40
+ closes finished sessions was writing the same "couldn't clean this up yet" note thousands of
41
+ times, and reading that log loaded the whole thing at once. Now it writes the note once and
42
+ reads only the end of the file. Nothing you do changes — your "why did my session vanish?"
43
+ history is still complete (the first skip, every change of reason, and every actual close are
44
+ all still recorded); it just stops the duplicate spam and the stall it caused, and a log that's
45
+ already huge stops growing and gets trimmed automatically.
46
+
47
+ ## Summary of New Capabilities
48
+
49
+ None — this is a reliability fix to existing behavior, not a new capability. The session-audit
50
+ history keeps the same shape and meaning; it's just bounded and de-duplicated now.
51
+
52
+ ## Evidence
53
+
54
+ - `npx vitest run tests/unit/reap-log.test.ts` → **16 tests, 0 failures** (transition-dedup:
55
+ identical-skip-suppressed, reason-transition-logs, per-session independence, re-log-after-reap;
56
+ bounded read returns correct last-N; rotation to `.1` + cross-boundary merge + live file
57
+ bounded; absent-file empty read).
58
+ - `npx vitest run tests/unit/reap-log.test.ts tests/unit/session-reaper.test.ts
59
+ tests/unit/reap-notifier.test.ts tests/unit/reap-guard.test.ts` → **115 tests, 0 failures**
60
+ (reaper family regression-clean; `normalizeEntry` whitelist untouched).
61
+ - `npx tsc --noEmit -p tsconfig.json` → **0 errors**.
62
+ - Independent second-pass reviewer: **Concur** — observability-only, touches no kill/keep
63
+ decision; no operator-needed row hidden; read/rotation off-by-one-clean.
64
+ - Side-effects review: `upgrades/side-effects/reaper-log-flood-fix.md` (8 questions +
65
+ signal-vs-authority + multi-machine posture + second-pass concurrence).
@@ -0,0 +1,85 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: minor -->
5
+
6
+ ## What Changed
7
+
8
+ The mechanical arm of the **"The Prompt and the Parser Are One Contract"** standard
9
+ (`docs/specs/prompt-parser-contract-standard.md`, defect class 1 / `prompt-parser-contract`
10
+ closure), shipped as a self-contained DARK increment — no runtime wiring, no operator-gated
11
+ registry text, no config key, and **no change to any live prompt or parser**. It is the
12
+ structural answer to the 2026-07-02 INSTAR-Bench v2 finding: the tone gate's PROMPT taught
13
+ models the short rule id (`B15`) while its production PARSER accepts only the full identifier
14
+ (`B15_CONTEXT_DEATH_STOP`) and fails closed on the short form — so every model through every
15
+ door obeyed the prompt and "failed." A single CI test comparing what the prompt promises
16
+ against what the parser accepts would have made that defect unrepresentable.
17
+
18
+ This increment ships **the shared contract library + the per-callsite classification + its CI
19
+ ratchet only**:
20
+
21
+ - A **shared contract library** (`src/core/promptContract.ts`): the `PromptContract` manifest
22
+ type (the co-located machine-readable promise a prose-shaped callsite carries next to its
23
+ prompt) and `deriveRejectedForms` — a pure generator for the counter-examples a contract
24
+ test feeds the REAL parser to prove its fail-closed behavior (case-mutation,
25
+ prefix-truncation at each separator — the exact `B15` shape — and separator-stripping, plus
26
+ hand-picked extras, minus any form that collides with a promised token). Machine-derived by
27
+ design: a hand-only reject list invites trivial rejects that prove nothing.
28
+ - The **`contract` classification** (`LLM_PARSER_CONTRACT` in `src/data/llmBenchCoverage.ts`):
29
+ the `contract` axis of the program's ONE shared per-callsite metadata record (sibling of the
30
+ authority-clause `untrustedInput` and evidence-bar `judgesClaims` axes), required-explicit
31
+ for every LLM component — no default, so a silent omission is red CI and the flag can never
32
+ default toward the un-contracted state. The four spec-named highest-stakes parsed callsites
33
+ (tone gate, external-op gate, stop judge, input classifier) seed `contract-wave-1`; every
34
+ other enumerated-verdict callsite is `contract-wave-2`; a callsite with no closed-vocabulary
35
+ parse (no live prompt, a fixed canary, or free-text/open-set content) is an argued `false`.
36
+ - A **classification ratchet** (`tests/unit/parser-contract-classification-ratchet.test.ts`):
37
+ required-explicit + no-dangling + valid-wave + the wave-1 seed pinned as a floor (a
38
+ highest-stakes callsite can never silently slip scope) + the pending set pinned shrink-only
39
+ + the argued-false set pinned shrink-only with a real-reason floor + a gate/sentinel
40
+ cross-check. Same pinned-baseline family as `llm-bench-coverage-ratchet` and the sibling
41
+ `untrusted-input-classification-ratchet`.
42
+
43
+ Deliberately OUT of scope (not orphan deferrals — see `upgrades/side-effects/`):
44
+
45
+ - The **per-callsite contract tests** and the **live-builder render refactor** (spec rollout
46
+ §0/§1). A contract test renders the REAL production prompt through an exported pure render
47
+ function; several production builders are private instance methods with live deps and need
48
+ that refactor, which TOUCHES live parsing code. It is deferred to its own A/B-gated
49
+ increments, one callsite at a time — never inside this dark inventory increment.
50
+ - The **runtime contract-drift warning** (spec Frontloaded Decision #3) — a prompt-build-time
51
+ signal that also touches the live builders; it lands with the single-sourcing migrations.
52
+ - The **registry / constitution text** (spec §1) — operator-gated; ships ONLY with Justin's
53
+ explicit sign-off. It is DRAFTED in the spec; this run does not edit the standards registry.
54
+
55
+ ## What to Tell Your User
56
+
57
+ Nothing changes for you right now — this ships dark, and it is maintainer-only machinery (a
58
+ no-op on your install unless you develop instar itself). What it does is give my own AI checks
59
+ a safety net: the words a check's prompt tells a model to answer with, and the words the code
60
+ behind that check will actually accept, are now recorded together and held in sync by an
61
+ automatic test. That closes a class of bug where a check would quietly reject every correct
62
+ answer because the two halves had drifted apart — a mistake that was entirely mine, never the
63
+ model's. The real per-check tests and any wording changes come later, one at a time, and only
64
+ after careful review.
65
+
66
+ ## Summary of New Capabilities
67
+
68
+ None active for end users in this increment — everything ships dark and additive. (For instar
69
+ maintainers: a shared prompt↔parser contract library plus a required per-callsite `contract`
70
+ classification with a shrink-only ratchet and a pinned highest-stakes seed floor, so a
71
+ machine-parsed callsite can never silently ship a taught output vocabulary its own parser
72
+ rejects.)
73
+
74
+ ## Evidence
75
+
76
+ - `npx vitest run tests/unit/parser-contract-classification-ratchet.test.ts tests/unit/promptContract.test.ts tests/unit/llm-bench-coverage-ratchet.test.ts`
77
+ → **3 files, 26 tests, 0 failures** (required-explicit + no-dangling + wave-1 seed floor +
78
+ shrink-only pending + shrink-only argued-false + real-reason floor + gate/sentinel
79
+ cross-check + the derive-rejected-forms mutation logic incl. the B15 prefix shape; the
80
+ pre-existing coverage ratchet still green against the additive record).
81
+ - `npx tsc --noEmit` → exit 0. `npm run lint` → exit 0.
82
+ - Dark-by-construction: `LLM_PARSER_CONTRACT` is build-time metadata read only by the new
83
+ pinned ratchet; `promptContract.ts` has no runtime caller. It changes NO prompt text and
84
+ wires NO runtime parser — verified by the absence of any src import of the new module
85
+ outside tests.