@henols/vice-mcp 0.2.2 → 0.2.4

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 (84) hide show
  1. package/README.md +2 -2
  2. package/THIRD-PARTY-NOTICES.md +422 -1
  3. package/anno-bank.ts +171 -0
  4. package/anno-cli.ts +1736 -163
  5. package/anno-confidence.ts +2 -2
  6. package/anno-derive.ts +6 -6
  7. package/anno-details.ts +4 -4
  8. package/anno-enum-gen.ts +416 -30
  9. package/anno-export-asm.ts +1211 -126
  10. package/anno-graphics.ts +338 -0
  11. package/anno-hazard-report.ts +1367 -0
  12. package/anno-import.ts +495 -0
  13. package/anno-index.ts +8 -8
  14. package/anno-join.ts +480 -0
  15. package/anno-memmap-render.ts +22 -21
  16. package/anno-provenance-ledger.ts +472 -0
  17. package/anno-regbits-gen.ts +13 -13
  18. package/anno-register.ts +159 -0
  19. package/anno-store-export.ts +661 -0
  20. package/anno-store.ts +635 -124
  21. package/anno-symbols.ts +7 -7
  22. package/anno-tools.ts +1169 -16
  23. package/anno-types.ts +313 -40
  24. package/backend-detect.mts +124 -312
  25. package/build.ts +3 -1
  26. package/capture-predicate.ts +597 -0
  27. package/channel-lock.ts +349 -0
  28. package/evid-ingest.ts +217 -0
  29. package/evid-reconcile.ts +316 -0
  30. package/host-tool-client.ts +430 -0
  31. package/incident-record.ts +23 -12
  32. package/install-resources.ts +29 -13
  33. package/memmap-lookup.ts +285 -0
  34. package/package.json +27 -8
  35. package/prg-image.ts +1 -2
  36. package/repo-root.ts +87 -3
  37. package/resources/backend-detect.mjs +98 -236
  38. package/resources/broker-control.mjs +220 -54
  39. package/resources/broker-epoch.mjs +7 -8
  40. package/resources/broker-kill.mjs +36 -31
  41. package/resources/broker-launch.mjs +511 -374
  42. package/resources/broker-state.mjs +69 -24
  43. package/resources/container-guard.mjs +1 -1
  44. package/resources/ghidra-project.mjs +790 -0
  45. package/resources/host-tool.mjs +2533 -0
  46. package/resources/vice-broker.mjs +434 -290
  47. package/resources/vice-launcher.sh +127 -9
  48. package/stock-address.ts +1 -1
  49. package/stock-condition.ts +1 -1
  50. package/stock-connect.ts +9 -5
  51. package/stock-derived.ts +29 -37
  52. package/stock-diagnose.ts +200 -36
  53. package/stock-dispatch.ts +179 -77
  54. package/stock-handler.ts +1 -1
  55. package/stock-paths.ts +18 -14
  56. package/stock-petscii.ts +1 -1
  57. package/stock-protocol.ts +1 -1
  58. package/stock-recycle.ts +83 -2
  59. package/stock-reproducible-run.ts +811 -0
  60. package/stock-run-until.ts +100 -1
  61. package/stock-symbols.ts +4 -4
  62. package/stock-timing.ts +1 -1
  63. package/stop-oracle.ts +167 -0
  64. package/text-capability-probe.ts +660 -0
  65. package/text-connect.ts +157 -0
  66. package/text-protocol.ts +810 -0
  67. package/text-tools.ts +778 -0
  68. package/textmon-backtrace.ts +385 -0
  69. package/textmon-cpuhistory.ts +335 -0
  70. package/textmon-memmap.ts +494 -0
  71. package/textmon-profile.ts +458 -0
  72. package/textmon-registers.ts +748 -0
  73. package/tools-manifest.stock.json +864 -3
  74. package/vice-broker-client.ts +253 -108
  75. package/vice-errors.ts +268 -0
  76. package/vice-proxy.ts +339 -2144
  77. package/vsf-slice.ts +640 -0
  78. package/anno-d64.ts +0 -310
  79. package/capability-registry.ts +0 -390
  80. package/refresh-manifest.ts +0 -124
  81. package/tools-manifest.json +0 -1223
  82. package/vice-probe.ts +0 -278
  83. package/vice-sync.ts +0 -336
  84. package/vice.ts +0 -772
package/anno-store.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  //
4
4
  // The ONE module in this repo that names `node:sqlite`. Nothing else may open,
5
5
  // query or write an annotation store file; every other module reaches the
6
- // store through the functions below (STORE-07).
6
+ // store through the functions below.
7
7
  //
8
8
  // ---------------------------------------------------------------------------
9
9
  // WHY THIS FILE EXISTS
@@ -54,7 +54,7 @@
54
54
  // module is not yet reachable from the published entry point's import closure,
55
55
  // and `scripts/check-npm-packages.mjs` asserts only one direction -- every
56
56
  // REACHABLE module must be listed -- never the converse. The real reason to
57
- // list it: `STORE-07`'s assertion scans `shippedTsModules()`, which is derived
57
+ // list it: the shipped-module assertion scans `shippedTsModules()`, which is derived
58
58
  // from `files[]`, so an unlisted module makes that assertion VACUOUS. It would
59
59
  // pass by scanning a set this file is not in. Copying the reachability sentence
60
60
  // here would plant a false claim in a brand-new seam header, which is the
@@ -85,7 +85,7 @@
85
85
  // `LIKE 'prefix%'` is 2.02 ms against FTS5 `MATCH`'s 2.99 ms, with a
86
86
  // 121.8 ms index rebuild, over 20,000 rows. Adding FTS5 later is
87
87
  // ADDITIVE; removing it is a schema migration. The search surface belongs
88
- // to `STORE-06` and this module must simply not foreclose it.
88
+ // elsewhere and this module must simply not foreclose it.
89
89
  // 6. NEVER add an explicit save or flush verb. Durability is this module's
90
90
  // responsibility, not the caller's: every accepted write commits before it
91
91
  // returns. A save verb is a way for a caller to lose data by forgetting.
@@ -103,9 +103,9 @@
103
103
  // look like tightening and are the opposite. A REFUSAL would push a caller
104
104
  // toward deleting the comment to get the retype through, converting a
105
105
  // reported loss into a silent one -- the exact outcome the report exists to
106
- // prevent (`STORE-03`). A WIDENED rule would fire on every retype of a
106
+ // prevent. A WIDENED rule would fire on every retype of a
107
107
  // commented range, and a report that fires every time is a report nobody
108
- // reads, so the one case that matters stops being noticed (`STORE-01`).
108
+ // reads, so the one case that matters stops being noticed.
109
109
  // 10. NEVER prune the snapshot ring INSIDE the write transaction, and never
110
110
  // let a revert fall back to the nearest retained revision. A filesystem
111
111
  // unlink is not part of the transaction, so pruning inside it means a
@@ -125,7 +125,7 @@
125
125
  // conclusion. And a revert that SUBSTITUTES the nearest
126
126
  // retained revision for the one asked for changes the caller's intent with
127
127
  // nothing recording that it happened, so a revert past the bound is
128
- // refused BY NAME instead (`STORE-04`).
128
+ // refused BY NAME instead.
129
129
  import { randomUUID } from "node:crypto";
130
130
  import { closeSync, copyFileSync, existsSync, fsyncSync, mkdirSync, openSync, readdirSync, renameSync, rmSync } from "node:fs";
131
131
  import { basename, dirname, join, resolve } from "node:path";
@@ -138,11 +138,17 @@ import {
138
138
  assertCommentType,
139
139
  assertDataType,
140
140
  assertEnumName,
141
+ assertEvidSourceBank,
141
142
  assertLabelKind,
142
143
  assertLegalLabel,
143
144
  assertRangeShape,
145
+ assertRunIdentityDigest,
146
+ assertRunIdentitySeed,
144
147
  isSplitDataType,
145
148
  splitEntryAddressPairs,
149
+ ADDRESS_MAX,
150
+ ADDRESS_MIN,
151
+ AnnoCommentError,
146
152
  AnnoCommentGradeError,
147
153
  AnnoLabelError,
148
154
  AnnoRangeShapeError,
@@ -162,9 +168,13 @@ import {
162
168
  type CommentType,
163
169
  type ContradictedComment,
164
170
  type DataType,
171
+ type EvidExecRow,
172
+ type EvidSourceBank,
173
+ type ExcludedRangeRow,
165
174
  type LabelKind,
166
175
  type LabelRow,
167
176
  type EnumUsageRow,
177
+ type ObservedRunRow,
168
178
  type ProjectEnumRow,
169
179
  type RangeRow,
170
180
  type ScopeRow,
@@ -179,7 +189,7 @@ import { CONFIDENCE_GRADES, parseConfidencePrefix, AnnoConfidenceGradeError } fr
179
189
  // decide "rethrow unchanged" versus "wrap". Every `Anno*Error` in `anno-types.ts`
180
190
  // already extends it, so nothing new enters the module graph -- `anno-types.ts`
181
191
  // imports the same class from the same file.
182
- import { ViceError } from "./vice.ts";
192
+ import { ViceError } from "./vice-errors.ts";
183
193
 
184
194
  /**
185
195
  * What every write entry point in this module returns.
@@ -206,8 +216,8 @@ export interface AnnoWriteResult {
206
216
  * the very first write. The SECOND half became false: `anno_snapshot` carried a
207
217
  * `path text not null` column holding the snapshot's ABSOLUTE location, and two
208
218
  * destructive consequences were reproduced against committed code -- two stores
209
- * in one directory sharing one ring (CR-01) and a directory rename plus one
210
- * write destroying the whole revert history (CR-03). The column is DROPPED at
219
+ * in one directory sharing one ring and a directory rename plus one
220
+ * write destroying the whole revert history. The column is DROPPED at
211
221
  * `SCHEMA_VERSION` 2 and the location is computed from the handle by
212
222
  * `snapshotDirFor()` at every read and every delete, so there is no persisted
213
223
  * absolute string left for a second namespace -- a bind mount seen from the
@@ -220,7 +230,7 @@ export interface AnnoWriteResult {
220
230
  * was byte-identical to version 1's.
221
231
  *
222
232
  * THAT SENTENCE IS KEPT AND SCOPED RATHER THAN DELETED, because at
223
- * `SCHEMA_VERSION` 3 it stopped being the whole truth: D-15 (2026-08-29) ADDS
233
+ * `SCHEMA_VERSION` 3 it stopped being the whole truth: version 3 (2026-08-29) ADDS
224
234
  * one table, `anno_enum_usage`, and its index. It changes no existing table's
225
235
  * column list, so the scoped claim above still holds of every table version 2
226
236
  * had. The version 3 table associates ONE address with ONE `anno_enum` row by
@@ -229,7 +239,7 @@ export interface AnnoWriteResult {
229
239
  * the day that cost was accepted.
230
240
  *
231
241
  * `anno_xref` and its `access_kind` column exist from the very first write.
232
- * Two requirement texts look like they conflict here and do not: `STORE-05`
242
+ * Two requirement texts look like they conflict here and do not: one
233
243
  * requires the column, while the cross-reference criterion forbids CACHING a
234
244
  * DERIVED cross-reference on disk. Both hold at once -- the table exists, and
235
245
  * only non-derivable references (hand-asserted, or resolved from something
@@ -241,6 +251,29 @@ export interface AnnoWriteResult {
241
251
  * that NOTHING in this module interprets and no code path reads except the row
242
252
  * mapper in `listRanges()`. It is reserved, and every row written today has it
243
253
  * null.
254
+ *
255
+ * AT `SCHEMA_VERSION` 4, ONE MORE TABLE IS ADDED:
256
+ * `anno_evid_exec`, the durable runtime-execution evidence table. See
257
+ * `anno-types.ts`'s `SCHEMA_VERSION` doc comment for what the bump buys and
258
+ * the decided, dated fate of an existing version-3 store (`reaffirm-refusal`
259
+ * -- no migration arm). `anno_evid_exec` carries NO `bank` column and NO
260
+ * nullable column at all: unlike the annotation tables above, every field on
261
+ * a row here is a fact the runtime evidence layer is licensed to assert, or
262
+ * the row does not exist. Its run-identity key is the bare triple
263
+ * `(image_sha256, argv_digest, seed)`, selected by a live A/B that measured
264
+ * `no-perturbation` from instrumentation -- there is deliberately no
265
+ * `run_class` column.
266
+ *
267
+ * AT `SCHEMA_VERSION` 5, ONE MORE TABLE IS ADDED:
268
+ * `anno_excluded_range`, the durable record of a user-requested exclusion --
269
+ * its extent and the reason the user gave. See `anno-types.ts`'s
270
+ * `SCHEMA_VERSION` doc comment for what the bump buys, why a table was chosen
271
+ * over a column on `anno_range`, and the decided, dated fate of an existing
272
+ * version-4 store (`reaffirm-refusal` -- no migration arm). `reason` is `not
273
+ * null`: an exclusion with no reason is a hole with a row in front of it.
274
+ * `anno_excluded_range` carries NO `bank` column, for the same reason
275
+ * `ExcludedRangeRow`'s own doc comment gives -- an exclusion is a statement
276
+ * about the subject program, not a memory view.
244
277
  */
245
278
  export const DDL = `
246
279
  create table anno_meta (
@@ -307,11 +340,31 @@ create table anno_snapshot (
307
340
  revision integer primary key
308
341
  );
309
342
 
343
+ create table anno_evid_exec (
344
+ id integer primary key autoincrement,
345
+ image_sha256 text not null,
346
+ argv_digest text not null,
347
+ seed text not null,
348
+ address integer not null,
349
+ source_bank text not null,
350
+ unique(image_sha256, argv_digest, seed, address, source_bank)
351
+ );
352
+
353
+ create table anno_excluded_range (
354
+ id integer primary key autoincrement,
355
+ start integer not null,
356
+ end_inclusive integer not null,
357
+ reason text not null,
358
+ unique(start, end_inclusive)
359
+ );
360
+
310
361
  create index anno_range_end_start on anno_range(end_inclusive, start);
311
362
  create index anno_label_address on anno_label(address);
312
363
  create index anno_comment_address on anno_comment(address);
313
364
  create index anno_enum_usage_address on anno_enum_usage(address);
314
365
  create index anno_xref_to on anno_xref(to_address);
366
+ create index anno_evid_exec_address on anno_evid_exec(address);
367
+ create index anno_excluded_range_start on anno_excluded_range(start);
315
368
  `;
316
369
 
317
370
  /** An open store: the connection, the resolved store path, and the directory
@@ -325,7 +378,7 @@ export interface AnnoStoreHandle {
325
378
  /**
326
379
  * THIS CONNECTION'S TRANSACTION STATE IS UNKNOWN: a housekeeping sweep run on
327
380
  * it reported that its own `rollback` threw, so it may still hold an open
328
- * transaction and the store's write lock (WR-18).
381
+ * transaction and the store's write lock.
329
382
  *
330
383
  * THE REMEDY IS THE ONE THE COMMIT HANDLER ALREADY PRINTS, in the same words:
331
384
  * CLOSE IT AND REOPEN rather than reusing it. Node 22's `DatabaseSync` exposes
@@ -340,7 +393,7 @@ export interface AnnoStoreHandle {
340
393
  * what prohibition 28-11 P5 forbids, and would send a caller to retry an
341
394
  * additive verb. So the accepted write returns its revision unchanged and it
342
395
  * is the NEXT call on this connection that refuses BY NAME -- which is what
343
- * turns CR-07's bare `cannot start a transaction within a transaction` into a
396
+ * turns the bare `cannot start a transaction within a transaction` error into a
344
397
  * diagnosis.
345
398
  *
346
399
  * `false` on every freshly opened handle, set in `openStore` at the one place
@@ -387,7 +440,7 @@ function fsyncPath(path: string): void {
387
440
  *
388
441
  * A `workspaceRoot` IS REQUIRED unless the caller explicitly asks for the
389
442
  * unconfined path with `unconfinedModuleDerivedPath: true`, and the inversion is
390
- * deliberate (WR-25). Confinement used to be opt-IN, which made the mitigation
443
+ * deliberate. Confinement used to be opt-IN, which made the mitigation
391
444
  * for the one unvalidated input this module's own header calls out the one a
392
445
  * caller could forget -- and two of this store's recorded blockers were confinement
393
446
  * escapes. The escape exists for exactly one shape: a path THIS MODULE derived
@@ -433,12 +486,12 @@ export function openStore(
433
486
  path: string,
434
487
  opts: { workspaceRoot?: string; mustExist?: boolean; unconfinedModuleDerivedPath?: boolean } = {},
435
488
  ): AnnoStoreHandle {
436
- // CONFINEMENT IS THE DEFAULT, AND THE ESCAPE IS A WORD A GREP CAN FIND
437
- // (WR-25). `anno-types.ts`'s header names the three things nothing upstream
489
+ // CONFINEMENT IS THE DEFAULT, AND THE ESCAPE IS A WORD A GREP CAN FIND.
490
+ // `anno-types.ts`'s header names the three things nothing upstream
438
491
  // validates -- "an address of 65536, a misspelled data type, and a store path
439
492
  // pointing outside the workspace all look identical to the transport" -- and
440
493
  // this was the only one of the three whose mitigation a caller could simply
441
- // forget. Two of this phase's blockers (CR-03, CR-04) were confinement
494
+ // forget. Two of this project's own review findings were confinement
442
495
  // escapes.
443
496
  //
444
497
  // REFUSED BEFORE THE PATH IS RESOLVED AND LONG BEFORE `new DatabaseSync`, for
@@ -539,10 +592,21 @@ export function openStore(
539
592
 
540
593
  if (meta.schema_version !== SCHEMA_VERSION) {
541
594
  db.close();
542
- throw new AnnoStoreCorruptError(`${resolved}: schema_version ${meta.schema_version}, expected ${SCHEMA_VERSION}`, { path: resolved });
595
+ // NAMES THE REMEDY AND DENIES NOTHING IS LOST. This build refuses rather than upgrades -- see
596
+ // `anno-types.ts`'s `SCHEMA_VERSION` doc comment for the decided,
597
+ // dated reason -- and the refusal happens BEFORE any write, so the
598
+ // file on disk is exactly what it was a moment ago: its labels,
599
+ // comments and enums are not lost, only unreadable by this build.
600
+ throw new AnnoStoreCorruptError(
601
+ `${resolved}: schema_version ${meta.schema_version}, expected ${SCHEMA_VERSION} -- refusing to open rather than upgrade. This file is ` +
602
+ `left exactly as it was: nothing on it is read, rewritten or deleted by this refusal. Open it with a build whose SCHEMA_VERSION is ` +
603
+ `${meta.schema_version} to read it (see anno-types.ts's SCHEMA_VERSION doc comment for what changed at each version), or hand-copy ` +
604
+ `its rows into a fresh store at this build's version.`,
605
+ { path: resolved },
606
+ );
543
607
  }
544
608
 
545
- // WR-04, THE LAST KNOWN FAMILY ESCAPE IN THIS FUNCTION. The two blocks either
609
+ // THE LAST KNOWN FAMILY ESCAPE IN THIS FUNCTION. The two blocks either
546
610
  // side of this one are already wrapped, and for the same two reasons: an
547
611
  // unwrapped failure here leaks the CONNECTION as well as escaping the
548
612
  // `ViceError` family, so the caller loses the file handle with no way to
@@ -583,14 +647,14 @@ export function currentRevision(handle: AnnoStoreHandle): number {
583
647
  * The suffix appended to the store FILENAME to name its snapshot ring
584
648
  * directory. Appended to the FILENAME rather than being a fixed directory name
585
649
  * (`<dir>/snapshots`, which is what this was), and the distinction is the whole
586
- * of CR-01's fix: two distinct store files in one directory have distinct
650
+ * of the fix: two distinct store files in one directory have distinct
587
651
  * basenames by definition of a filesystem, so distinct basenames give distinct
588
652
  * rings BY CONSTRUCTION rather than by an ownership predicate layered over a
589
653
  * shared location.
590
654
  *
591
655
  * THE PREDICATE ROUTE WAS ALREADY TRIED AND COULD NOT SEE THE DEFECT. Plan
592
656
  * 28-07 added a per-revision ownership check over the shared `<dir>/snapshots`
593
- * ring; it was structurally blind to CR-01 because revision numbers are not
657
+ * ring; it was structurally blind to the collision because revision numbers are not
594
658
  * unique ACROSS stores -- two stores in one directory both write `r1.db`, and
595
659
  * every per-revision predicate says "yes, revision 1 is mine" to both of them.
596
660
  * A location that cannot collide has no such blind spot to test for.
@@ -604,12 +668,12 @@ const SNAPSHOT_DIR_SUFFIX = ".snapshots";
604
668
  * `<dir>/proj.annostore.snapshots`.
605
669
  *
606
670
  * THE RESIDUAL, STATED RATHER THAN CLAIMED CLOSED -- AND RESTATED AFTER THIS
607
- * PARAGRAPH'S EARLIER VERSION WAS FALSIFIED BY DRIVING THE CODE (CR-05). What
671
+ * PARAGRAPH'S EARLIER VERSION WAS FALSIFIED BY DRIVING THE CODE. What
608
672
  * it got RIGHT and keeps: the location is a pure function of the handle, the
609
673
  * sweep only ever reads `snapshotDirFor(handle)` so it cannot see a ring it
610
674
  * does not name, and renaming the containing DIRECTORY is not a residual at all
611
- * -- the ring moves with the directory, so nothing is lost (pinned by the CR-03
612
- * rename test). What became FALSE: it claimed the old ring was never deleted at
675
+ * -- the ring moves with the directory, so nothing is lost, confirmed by a
676
+ * directory-rename test. What became FALSE: it claimed the old ring was never deleted at
613
677
  * all and that `retainedRevisions()` reporting an empty list was therefore a
614
678
  * truthful under-claim. That was true of the FILES and false of the ROWS -- so
615
679
  * the claim is not repeated here even to disown it, because the next reader
@@ -622,7 +686,7 @@ const SNAPSHOT_DIR_SUFFIX = ".snapshots";
622
686
  * a SYMLINK ALIAS, or a store-file rename (`mv proj.annostore
623
687
  * other.annostore`) -- names a DIFFERENT ring, so a handle opened under it
624
688
  * publishes into a SECOND ring. The first ring's files are never deleted, and
625
- * since CR-05 its pointer rows are never deleted BY THE SWEEP -- but
689
+ * since the fix above its pointer rows are never deleted BY THE SWEEP -- but
626
690
  * `pruneSnapshots`' doomed loop still deletes every row below
627
691
  * `currentRevision() - MAX_SNAPSHOT_REVISIONS`, so restoring the original name
628
692
  * restores the floor ONLY while the wrong-spelling handle has not advanced past
@@ -672,8 +736,8 @@ export const NO_RETAINED_REVISION = -1;
672
736
  * reconciliation below derives from a filename alone.
673
737
  *
674
738
  * ANCHORED ON PURPOSE, and the anchoring is load-bearing rather than tidy:
675
- * plan 28-08 introduces per-attempt STAGING files in this same directory under
676
- * a different suffix, and a sweep that matched them would delete another
739
+ * this module's own staging mechanism writes per-attempt STAGING files in this
740
+ * same directory under a different suffix, and a sweep that matched them would delete another
677
741
  * writer's in-flight snapshot -- the exact loss this reconciliation exists to
678
742
  * prevent, committed by the repair itself.
679
743
  *
@@ -692,7 +756,7 @@ const SNAPSHOT_FILE_PATTERN = /^r(\d+)\.db$/;
692
756
  *
693
757
  * IT REPLACED A PRESENCE TEST AT BOTH OF THE TWO SITES THAT CARRIED ONE -- the
694
758
  * filter inside `retainedRevisions` and `revertTo`'s step-2 gate -- and the
695
- * promotion is the whole of CR-08's supporting half. Presence was never a
759
+ * promotion is the whole of the supporting half of this fix. Presence was never a
696
760
  * witness that a file is a store: this module's FIRST MEASURED FACT (header,
697
761
  * `:22-31`) is that a ZERO-LENGTH FILE OPENS as a SQLite database and reports
698
762
  * `integrity_check ok`. So the store advertised a revision whose image was not a
@@ -789,7 +853,7 @@ function claimedRevisions(handle: AnnoStoreHandle): number[] {
789
853
  * one file are two things that can disagree. They did, twice, both reproduced:
790
854
  * a directory rename invalidated every persisted path at once, after which this
791
855
  * function reported NO retained revisions while the files sat there on disk, and
792
- * the next write's prune destroyed them (CR-03). The same shape covers every
856
+ * the next write's prune destroyed them. The same shape covers every
793
857
  * adjacent case rather than just that one repro -- a bind mount seen from two
794
858
  * namespaces (this repo's entire architecture is built around that boundary), a
795
859
  * symlinked ancestor, a container/host path pair, a case-insensitive filesystem,
@@ -815,8 +879,8 @@ function claimedRevisions(handle: AnnoStoreHandle): number[] {
815
879
  * `begin immediate` would have opened up to 32 databases with the store's write
816
880
  * lock held. Every remaining consumer reads this function rather than deciding
817
881
  * for itself what "retained" means: three independent decisions is precisely how
818
- * the three answers came to disagree, CR-08 was the gap between two of them, and
819
- * a fourth would also hide the row-only regression from the proofs that exist to
882
+ * the three answers came to disagree -- that was the gap between two of them --
883
+ * and a fourth would also hide the row-only regression from the proofs that exist to
820
884
  * catch it.
821
885
  */
822
886
  export function retainedRevisions(handle: AnnoStoreHandle): number[] {
@@ -842,7 +906,7 @@ export function retainedRevisions(handle: AnnoStoreHandle): number[] {
842
906
  * the first element of `retainedRevisions()` -- and that reading was then one
843
907
  * step short a SECOND time, in the same direction: an existence check published
844
908
  * `0` on a store whose `r0.db` was present but was not a database, and
845
- * following THAT floor destroyed the live store (CR-08). The floor now requires
909
+ * following THAT floor destroyed the live store. The floor now requires
846
910
  * the image to OPEN, not merely to exist, so the store still cannot publish a
847
911
  * number it will then refuse -- in either direction.
848
912
  */
@@ -860,8 +924,8 @@ export function oldestRetainedRevision(handle: AnnoStoreHandle): number {
860
924
  *
861
925
  * * AN ORPHAN ROW (a pointer row whose file is gone) IS NO LONGER SWEPT AT
862
926
  * ALL, and the reversal is recorded here rather than left to be inferred
863
- * from an absence. This function used to delete every such row. CR-05
864
- * reproduced, twice, what that costs: the ring is named from
927
+ * from an absence. This function used to delete every such row. A live
928
+ * reproduction showed, twice, what that costs: the ring is named from
865
929
  * `basename(handle.path)` -- a PATH SPELLING -- so a SYMLINK ALIAS of the
866
930
  * store file, or a store-file rename (`mv proj.annostore
867
931
  * other.annostore`), makes `retainedRevisions()` report every EXISTING
@@ -889,11 +953,11 @@ export function oldestRetainedRevision(handle: AnnoStoreHandle): number {
889
953
  * ring. That is no longer true: the keep-set below reads
890
954
  * `claimedRevisions`, so this sweep now looks at rows the advertisement
891
955
  * ignores. THE NEW BASIS IS BETTER RATHER THAN WEAKER, and it is the
892
- * conclusion CR-08 forced: a row the sweep KEEPS is precisely what makes a
956
+ * conclusion that promotion forced: a row the sweep KEEPS is precisely what makes a
893
957
  * corrupt image survive on disk as EVIDENCE instead of being unlinked. A
894
958
  * sweep that deleted the image of a failure would be destroying the only
895
959
  * record of the failure that has to be diagnosed -- a second destruction
896
- * dressed as a repair. The CR-05 conclusion is unchanged: the row direction
960
+ * dressed as a repair. That conclusion is unchanged: the row direction
897
961
  * stays abandoned, for the ownership reason above.
898
962
  *
899
963
  * THE KEEP-SET QUERY RUNS ON THE CONNECTION ALREADY IN HAND, and that is
@@ -937,7 +1001,7 @@ export function oldestRetainedRevision(handle: AnnoStoreHandle): number {
937
1001
  * over a ring with no half-states). It must NOT run from `openStore`:
938
1002
  * `anno-durability.test.ts:291-347` asserts that an orphan snapshot file left
939
1003
  * in the kill window SURVIVES a reopen and is identified by its revision, and
940
- * that is a verified truth of plan 28-06 -- merely LOOKING at a store must not
1004
+ * that is a verified truth -- merely LOOKING at a store must not
941
1005
  * change it, and the orphan a kill window leaves is deliberately the harmless
942
1006
  * direction. Reconciling on open would redden that test, and rightly. BOTH
943
1007
  * SITES ARE OUTSIDE ANY OPEN TRANSACTION, which is now a REQUIREMENT rather
@@ -945,7 +1009,7 @@ export function oldestRetainedRevision(handle: AnnoStoreHandle): number {
945
1009
  * own, so calling it from inside one is not supported.
946
1010
  *
947
1011
  * IT NOW TAKES THE STORE'S WRITE LOCK BEFORE IT DECIDES ANYTHING, and the
948
- * reason is a reproduced defect (CR-02) rather than caution. A snapshot becomes
1012
+ * reason is a reproduced defect rather than caution. A snapshot becomes
949
1013
  * a FILESYSTEM fact (the `renameSync` inside `publishSnapshot`) before it
950
1014
  * becomes a TRANSACTIONAL one (the pointer-row insert), so a sweep reading only
951
1015
  * its own committed view sees a live writer's published file as unowned and
@@ -1000,7 +1064,7 @@ export function oldestRetainedRevision(handle: AnnoStoreHandle): number {
1000
1064
  * NO SECOND DISCRIMINATOR WAS ADDED FOR `deferred`'s TWO CAUSES, and the reason
1001
1065
  * is not economy. Its only consumer, `pruneSnapshots`, returns early
1002
1066
  * identically in both cases, so a discriminator would have no reader -- and
1003
- * CR-07's actual complaint, that a LEAKED transaction makes every later sweep
1067
+ * the actual complaint here, that a LEAKED transaction makes every later sweep
1004
1068
  * report `deferred` indistinguishably from contention, is removed AT ITS SOURCE
1005
1069
  * by the handler rather than papered over with a label. A field describing a
1006
1070
  * state this code can no longer reach would be exactly the kind of comment
@@ -1012,7 +1076,7 @@ export function oldestRetainedRevision(handle: AnnoStoreHandle): number {
1012
1076
  * for up to the connection's five-second `busy_timeout` before it proceeds --
1013
1077
  * at BOTH of the two call sites: every accepted write (through `pruneSnapshots`
1014
1078
  * at `runWriteSequence` step 9) and every `revertTo` (through its own step-6
1015
- * sweep on the restored handle). Phase 29 puts both on an MCP tool path. Each is
1079
+ * sweep on the restored handle). Both sit on an MCP tool path. Each is
1016
1080
  * bounded at ONE timeout and not two, because `pruneSnapshots` returns early
1017
1081
  * when this function reports `deferred` rather than running its own autocommit
1018
1082
  * deletes into the same contention. An honest cost stated at the seam is worth
@@ -1024,8 +1088,8 @@ export function oldestRetainedRevision(handle: AnnoStoreHandle): number {
1024
1088
  * left (see the ORPHAN ROW bullet above), so what the ordering now guarantees is
1025
1089
  * narrower and is stated narrowly: an interruption between the commit and the
1026
1090
  * unlinks leaves extra FILES, never a pointer row aimed at a deleted file.
1027
- * Pinned by a source-order control in `anno-store.test.ts`, which since CR-05
1028
- * asserts the ABSENCE of any pointer-row delete in this body as well as the
1091
+ * Pinned by a source-order control in `anno-store.test.ts`, which since the fix
1092
+ * above asserts the ABSENCE of any pointer-row delete in this body as well as the
1029
1093
  * surviving commit-before-unlink order -- a presence assertion cannot see
1030
1094
  * either.
1031
1095
  */
@@ -1054,12 +1118,12 @@ export function reconcileSnapshotRing(handle: AnnoStoreHandle): { droppedFiles:
1054
1118
  const orphanFiles: string[] = [];
1055
1119
 
1056
1120
  // EVERYTHING FROM HERE TO THE COMMIT IS BRACKETED, AND THE BRACKET IS THE
1057
- // FIX (CR-07). `begin immediate` above has already opened a transaction on
1121
+ // FIX. `begin immediate` above has already opened a transaction on
1058
1122
  // the CALLER's connection. Before this handler existed, any throw between
1059
1123
  // that statement and the commit -- `readdirSync` on a ring directory that
1060
1124
  // became unreadable, a failure of the keep-set `select`, anything --
1061
1125
  // propagated out with the transaction still
1062
- // OPEN. Step 9's WR-02 wrap then swallowed it, so an ordinary `setDataType`
1126
+ // OPEN. Step 9's own error-swallowing wrap then swallowed it, so an ordinary `setDataType`
1063
1127
  // reported SUCCESS while leaving the handle permanently inside a transaction:
1064
1128
  // every later write failed with "cannot start a transaction within a
1065
1129
  // transaction", and every later sweep reported `deferred` indistinguishably
@@ -1067,8 +1131,8 @@ export function reconcileSnapshotRing(handle: AnnoStoreHandle): { droppedFiles:
1067
1131
  // path-dependent: there is no route out of this block that does not either
1068
1132
  // commit or roll back.
1069
1133
  try {
1070
- // STEP 2. With the lock held, compute the drop set -- which since CR-05 has
1071
- // exactly ONE direction, the FILE direction -- before changing anything.
1134
+ // STEP 2. With the lock held, compute the drop set -- which since the fix
1135
+ // above has exactly ONE direction, the FILE direction -- before changing anything.
1072
1136
  //
1073
1137
  // THE KEEP-SET IS THE POINTER-ROW SET, AND THAT IS A DIFFERENT QUESTION
1074
1138
  // rather than a fourth answer to "what is retained". This resolver still
@@ -1080,12 +1144,12 @@ export function reconcileSnapshotRing(handle: AnnoStoreHandle): { droppedFiles:
1080
1144
  // WHY THE QUESTION IS GENUINELY DIFFERENT, AND WHY THE ANSWERS ONLY DIVERGE
1081
1145
  // NOW. A sweep over FILES asks "is this file claimed by a pointer row";
1082
1146
  // `retainedRevisions` asks "can a caller revert to this revision". Before
1083
- // CR-08's promotion those two were identical for every reachable input,
1147
+ // that promotion those two were identical for every reachable input,
1084
1148
  // because the presence half of the old definition is trivially true of a
1085
1149
  // file `readdirSync` just returned. The promotion is what separates them,
1086
1150
  // and the separation runs in the SAFE direction: an image that fails to open
1087
1151
  // but that a row still claims is kept on disk as EVIDENCE. Leaving this
1088
- // keep-set on `retainedRevisions` would have made the CR-08 fix its own
1152
+ // keep-set on `retainedRevisions` would have made that fix its own
1089
1153
  // second destroyer -- the sweep would unlink exactly the corrupt image whose
1090
1154
  // refusal has to be diagnosed, one ordinary write after the refusal.
1091
1155
  //
@@ -1118,7 +1182,7 @@ export function reconcileSnapshotRing(handle: AnnoStoreHandle): { droppedFiles:
1118
1182
  }
1119
1183
  }
1120
1184
 
1121
- // STEP 3 IS GONE ON PURPOSE, and its absence is the fix for CR-05. It
1185
+ // STEP 3 IS GONE ON PURPOSE, and its absence is the fix described above. It
1122
1186
  // deleted every pointer row this handle's spelling of the ring could not
1123
1187
  // vouch for; under a second spelling of the same store file that was every
1124
1188
  // row it had. The whole argument is in the ORPHAN ROW bullet above.
@@ -1133,7 +1197,7 @@ export function reconcileSnapshotRing(handle: AnnoStoreHandle): { droppedFiles:
1133
1197
  } catch {
1134
1198
  // ROLLED BACK INSIDE ITS OWN SWALLOWING `try`: there is nothing useful to
1135
1199
  // do with a second error here, and reporting it would replace the first.
1136
- // WHAT IS NEW IS THAT THE OUTCOME IS RECORDED RATHER THAN ASSUMED (WR-16).
1200
+ // WHAT IS NEW IS THAT THE OUTCOME IS RECORDED RATHER THAN ASSUMED.
1137
1201
  // Node 22's `DatabaseSync` exposes no transaction-state accessor, so this
1138
1202
  // boolean is the only thing that can tell a caller which of the two
1139
1203
  // happened -- and this function cannot tell it by throwing, because
@@ -1147,7 +1211,7 @@ export function reconcileSnapshotRing(handle: AnnoStoreHandle): { droppedFiles:
1147
1211
  rolledBack = false;
1148
1212
  }
1149
1213
  // AND DELIBERATELY NOT RETHROWN. A throw from here is swallowed by step 9's
1150
- // WR-02 wrap anyway, so rethrowing would buy nothing on the write path --
1214
+ // own error-swallowing wrap anyway, so rethrowing would buy nothing on the write path --
1151
1215
  // and on `revertTo`'s own step-6 call site it would convert a COMMITTED
1152
1216
  // write into a caller-visible failure, which prohibition 28-11 P5 forbids.
1153
1217
  // The sweep changed nothing, which is precisely what `deferred` reports --
@@ -1241,7 +1305,7 @@ export function pruneSnapshots(handle: AnnoStoreHandle): boolean {
1241
1305
  // revision number, and the same direction a deferred sweep already accepts.
1242
1306
  //
1243
1307
  // AND ITS `rollbackFailed` IS CONSUMED FOR THE SAME REASON, RETURNED RATHER
1244
- // THAN DISCARDED (WR-18). The argument recorded above for consuming
1308
+ // THAN DISCARDED. The argument recorded above for consuming
1245
1309
  // `.deferred` is the argument for consuming this one, so it is extended here
1246
1310
  // rather than restated: a fact this function throws away is a fact its caller
1247
1311
  // cannot act on, and `rollbackFailed` reports the ONE state the sweep's own
@@ -1280,7 +1344,7 @@ export function pruneSnapshots(handle: AnnoStoreHandle): boolean {
1280
1344
  // an orphan FILE -- which the reconciliation at the top of the NEXT prune
1281
1345
  // can still see and retry. Under the earlier arrangement the row was
1282
1346
  // deleted unconditionally after a swallowed failure, so the file became
1283
- // invisible to the bound forever (WR-01's secondary point).
1347
+ // invisible to the bound forever.
1284
1348
  try {
1285
1349
  // THE PATH IS COMPUTED HERE, at the delete, from the handle -- never read
1286
1350
  // from the row. A persisted absolute path is environment-controlled input
@@ -1326,7 +1390,7 @@ export function pruneSnapshots(handle: AnnoStoreHandle): boolean {
1326
1390
  * because `vacuum into` refuses an existing target and because two writers
1327
1391
  * filling one file is the very collision this staging exists to remove.
1328
1392
  *
1329
- * AND THE IMAGE IS FSYNCED BEFORE THIS FUNCTION RETURNS (WR-13). The pointer row
1393
+ * AND THE IMAGE IS FSYNCED BEFORE THIS FUNCTION RETURNS. The pointer row
1330
1394
  * that names this file is inserted inside the write transaction and committed by
1331
1395
  * SQLite, WHICH DOES FSYNC -- so without the `fsyncPath` below the ROW is
1332
1396
  * durable and the FILE it names is not. Trap 10's durability premise covers a
@@ -1334,7 +1398,7 @@ export function pruneSnapshots(handle: AnnoStoreHandle): boolean {
1334
1398
  * anyway; it does NOT cover a host crash, which loses the cache. The consequence
1335
1399
  * is not an extra file: it is a PRESENT, PARTIAL snapshot that
1336
1400
  * `retainedRevisions()` would advertise as revertible, which is exactly the input
1337
- * CR-08 was reproduced with. 28-16's step-2 and step-3b gates make that input a
1401
+ * this defect was reproduced with. 28-16's step-2 and step-3b gates make that input a
1338
1402
  * REFUSAL rather than a destruction; this call removes the input at its source
1339
1403
  * rather than relying on the refusal, because a refusal on the only route back
1340
1404
  * is still a lost history.
@@ -1378,7 +1442,7 @@ export function stageSnapshot(handle: AnnoStoreHandle, revision: number): string
1378
1442
  * store that has been reverted and written forward again, rather than leaving
1379
1443
  * it as an argument.
1380
1444
  *
1381
- * AND THE RING DIRECTORY IS FSYNCED AFTER THE RENAME (WR-13). A rename is
1445
+ * AND THE RING DIRECTORY IS FSYNCED AFTER THE RENAME. A rename is
1382
1446
  * VISIBLE immediately and DURABLE only after the directory is fsynced -- the
1383
1447
  * distinction `fsyncPath`'s own doc sentence records. The pointer row that names
1384
1448
  * this file is inserted inside the write transaction a few statements below and
@@ -1389,7 +1453,7 @@ export function stageSnapshot(handle: AnnoStoreHandle, revision: number): string
1389
1453
  * `SIGKILL` and NOT of a host crash, which loses the page cache; the surviving
1390
1454
  * half-state there is a durable row naming a file whose bytes never reached
1391
1455
  * disk, i.e. the PRESENT, PARTIAL snapshot `retainedRevisions()` would advertise
1392
- * and the exact input CR-08 was reproduced with. 28-16 made that input a refusal
1456
+ * and the exact input this defect was reproduced with. 28-16 made that input a refusal
1393
1457
  * rather than a destruction; this call removes the input at its source instead of
1394
1458
  * relying on that refusal. The order is the same as `revertTo`'s steps 3 and 5
1395
1459
  * and uses the same helper, deliberately -- a second durability idiom in one
@@ -1409,7 +1473,7 @@ export function stageSnapshot(handle: AnnoStoreHandle, revision: number): string
1409
1473
  * half-state for a store that cannot be written at all: `openSync(dir, "r")`
1410
1474
  * needs the ring directory READABLE, so a writable-but-unreadable ring (mode
1411
1475
  * 0300 -- measured) would make every `setDataType` throw, and that is also the
1412
- * precondition CR-07's only behavioural control is built from. So the two
1476
+ * precondition this fix's only behavioural control is built from. So the two
1413
1477
  * reachable outcomes after an interruption stay exactly the two this module
1414
1478
  * bounds them to -- a missing entry, or an entry whose contents ARE durable --
1415
1479
  * and the failure of this call moves the outcome from the second to the first
@@ -1435,7 +1499,7 @@ function publishSnapshot(stagingPath: string, snapPath: string): void {
1435
1499
  * are on.
1436
1500
  *
1437
1501
  * AND IT IS THE ONE PLACE A STAGING FILE IS REMOVED, which is why `revertTo`'s
1438
- * three cleanup exits route through it too (WR-24 / WR-11). Those three used to
1502
+ * three cleanup exits route through it too. Those three used to
1439
1503
  * be bare `rmSync(staging, { force: true })` calls, so the module had two
1440
1504
  * answers to "where does a staging file get removed" and a later reader looking
1441
1505
  * for the one place found only half of them. The swallowing semantics below are
@@ -1510,12 +1574,12 @@ function runWriteSequence<T>(
1510
1574
  doCommit: boolean,
1511
1575
  baseRevision?: number,
1512
1576
  ): { revision: number; result: T } {
1513
- // BEFORE `begin immediate`, AND BEFORE ANYTHING IS READ (WR-18). A previous
1577
+ // BEFORE `begin immediate`, AND BEFORE ANYTHING IS READ. A previous
1514
1578
  // write's housekeeping sweep ran on THIS connection and reported that its own
1515
1579
  // `rollback` threw, so the connection may still hold an open transaction and
1516
1580
  // the store's write lock. Without this refusal the next `begin immediate`
1517
1581
  // surfaces SQLite's bare `cannot start a transaction within a transaction` --
1518
- // CR-07's exact reported symptom, outside the `ViceError` family, with nothing
1582
+ // the exact reported symptom, outside the `ViceError` family, with nothing
1519
1583
  // naming the cause or the remedy.
1520
1584
  //
1521
1585
  // AN EXISTING IN-FAMILY CLASS, NOT A NEW ONE: this is the same fact the commit
@@ -1542,7 +1606,7 @@ function runWriteSequence<T>(
1542
1606
  );
1543
1607
  }
1544
1608
 
1545
- // WR-01, THE PRE-LOCK ARM. `stageSnapshot` runs BEFORE `begin immediate`, so
1609
+ // THE PRE-LOCK ARM. `stageSnapshot` runs BEFORE `begin immediate`, so
1546
1610
  // it gets its own handler rather than sharing the outer one below: there is
1547
1611
  // no transaction to roll back yet and no staged file to discard, so the two
1548
1612
  // arms genuinely differ in what they have to undo. The reachable input is a
@@ -1561,7 +1625,7 @@ function runWriteSequence<T>(
1561
1625
  );
1562
1626
  }
1563
1627
 
1564
- // WR-01, THE MAIN WINDOW: `begin immediate`, the compare-and-swap, the
1628
+ // THE MAIN WINDOW: `begin immediate`, the compare-and-swap, the
1565
1629
  // publication and the pointer-row insert, wrapped as ONE region. Its catch
1566
1630
  // undoes both kinds of state this region can leave behind -- an open
1567
1631
  // transaction with the compare-and-swap applied, and a staged `.tmp` -- and
@@ -1570,7 +1634,7 @@ function runWriteSequence<T>(
1570
1634
  // keeps the family closed.
1571
1635
  //
1572
1636
  // THE INNER ROLLBACK AND DISCARD IN THE CAS-FAILURE BRANCH BELOW ARE NOT
1573
- // REDUNDANT AND MUST NOT BE "SIMPLIFIED" AWAY. `anno-store.test.ts`'s WR-11
1637
+ // REDUNDANT AND MUST NOT BE "SIMPLIFIED" AWAY. `anno-store.test.ts`'s own
1574
1638
  // control extracts the slice between `cas.changes` and `publishSnapshot` and
1575
1639
  // asserts a `rollback` is present inside it, positioned after the
1576
1640
  // `select revision from anno_meta` read -- that positioning is a VERIFIED
@@ -1614,7 +1678,7 @@ function runWriteSequence<T>(
1614
1678
  // claims (see `retainedRevisions`).
1615
1679
  handle.db.prepare("insert into anno_snapshot(revision) values (?)").run(rev);
1616
1680
  } catch (e) {
1617
- // THE ROLLBACK'S OUTCOME IS RECORDED, NOT ASSUMED (WR-16). The message below
1681
+ // THE ROLLBACK'S OUTCOME IS RECORDED, NOT ASSUMED. The message below
1618
1682
  // used to state the rollback as a fact after this `catch` had swallowed that
1619
1683
  // rollback's own failure, so the one case in which the claim is false is
1620
1684
  // exactly the case in which it was printed. Node 22's `DatabaseSync` exposes
@@ -1688,7 +1752,7 @@ function runWriteSequence<T>(
1688
1752
  }
1689
1753
 
1690
1754
  if (doCommit) {
1691
- // CR-06, THE COMMIT ARM. This is the ONE statement in the sequence whose
1755
+ // THE COMMIT ARM. This is the ONE statement in the sequence whose
1692
1756
  // failure leaves the transaction OPEN with everything already applied -- the
1693
1757
  // compare-and-swap, the caller's mutation and the pointer-row insert -- and
1694
1758
  // it was outside every handler until this arm was added. A concurrent READER
@@ -1698,7 +1762,7 @@ function runWriteSequence<T>(
1698
1762
  // transaction: a bare `Error: database is locked` after the connection's
1699
1763
  // 5000 ms `busy_timeout`, outside the `ViceError` family, with the write
1700
1764
  // lock still held and `currentRevision()` on this connection reporting the
1701
- // ADVANCED revision for a write that never landed. On the Phase 29 tool
1765
+ // ADVANCED revision for a write that never landed. On the MCP tool
1702
1766
  // path a handle lives as long as the session, so the leaked write lock
1703
1767
  // locks every other connection out for that long.
1704
1768
  //
@@ -1720,18 +1784,18 @@ function runWriteSequence<T>(
1720
1784
  // accepted write's sweep reclaims.
1721
1785
  //
1722
1786
  // The refusal carries `code` from the underlying error when it has one
1723
- // (IN-05's cheap half, on this wrap only), so a caller can ask whether the
1787
+ // (the cheap half of that, on this wrap only), so a caller can ask whether the
1724
1788
  // failure was lock contention without substring-matching the message.
1725
1789
  // `cause` is deliberately NOT added: that needs a new field on
1726
1790
  // `ViceErrorOptions` in `vice.ts`, a shared module outside this phase.
1727
1791
  try {
1728
1792
  commitTransaction(handle.db);
1729
1793
  } catch (e) {
1730
- // RECORDED, NOT ASSERTED (WR-16). "the transaction has been rolled back"
1794
+ // RECORDED, NOT ASSERTED. "the transaction has been rolled back"
1731
1795
  // was stated as a fact directly under a `catch` that swallowed the
1732
1796
  // rollback's own failure -- so on the one path where the claim is false it
1733
- // was still printed, and a refusal that reports the CR-06 state as its own
1734
- // repair sends the caller straight back into reusing a connection that may
1797
+ // was still printed, and a refusal that reports that half-committed state
1798
+ // as its own repair sends the caller straight back into reusing a connection that may
1735
1799
  // still hold the store's write lock. There is no cheap check available:
1736
1800
  // Node 22's `DatabaseSync` exposes no transaction-state accessor (surface
1737
1801
  // measured on this host: `open, close, prepare, exec, function, location,
@@ -1761,8 +1825,8 @@ function runWriteSequence<T>(
1761
1825
  // The wording here is FREE. It used to be constrained: the
1762
1826
  // single-commit-site control in `anno-seam.test.ts` counted the WORD
1763
1827
  // `commit` over this module's stripped source, so a `step` value
1764
- // reading "commit ..." reddened a control in a different file. WR-15
1765
- // replaced that count with a match on `exec()` calls carrying a bare
1828
+ // reading "commit ..." reddened a control in a different file. A later
1829
+ // revision replaced that count with a match on `exec()` calls carrying a bare
1766
1830
  // statement literal, which no error message can satisfy, and the
1767
1831
  // constraint went with it -- this value is unchanged only because
1768
1832
  // changing it would be a gratuitous behaviour change.
@@ -1775,12 +1839,12 @@ function runWriteSequence<T>(
1775
1839
  // branch because a sequence that never commits has no accepted write to
1776
1840
  // bound.
1777
1841
  //
1778
- // WR-02: WRAPPED, AND DELIBERATELY NOT RETHROWN. By this line the
1842
+ // WRAPPED, AND DELIBERATELY NOT RETHROWN. By this line the
1779
1843
  // transaction has already returned, so THE WRITE HAPPENED -- the mutation
1780
1844
  // and the pointer row are durable. A housekeeping failure that threw from
1781
1845
  // here would report a write that succeeded as a failure, and the caller
1782
- // would retry an ADDITIVE verb and produce a second row. That is WR-02's
1783
- // exact complaint, and it became more likely rather than less once the
1846
+ // would retry an ADDITIVE verb and produce a second row. That is exactly
1847
+ // the failure this wrap exists to prevent, and it became more likely rather than less once the
1784
1848
  // sweep started taking the write lock.
1785
1849
  //
1786
1850
  // The consequence of swallowing is an UN-PRUNED RING -- extra files, the
@@ -1790,7 +1854,7 @@ function runWriteSequence<T>(
1790
1854
  // one here would be new surface with its own stdio hazards on an MCP
1791
1855
  // transport.
1792
1856
  //
1793
- // AND ITS REPORT IS CONSUMED (WR-18). `pruneSnapshots` returns the sweep's
1857
+ // AND ITS REPORT IS CONSUMED. `pruneSnapshots` returns the sweep's
1794
1858
  // `rollbackFailed` -- the one state the sweep's own handler cannot fix --
1795
1859
  // and it is RECORDED ON THE HANDLE rather than thrown or logged. Not thrown,
1796
1860
  // because by this line the write is committed and 28-11 P5 forbids reporting
@@ -1842,7 +1906,7 @@ export function applyWriteWithoutCommit<T>(
1842
1906
 
1843
1907
  /**
1844
1908
  * The module's ONE range insert. `bank` is a parameter rather than a hardcoded
1845
- * `null` (IN-06): a remainder re-inserted by split-and-preserve carries the
1909
+ * `null`: a remainder re-inserted by split-and-preserve carries the
1846
1910
  * overlapped row's own `bank` forward, and a newly typed range carries `null`.
1847
1911
  * `bank` is reserved and interpreted by nothing today, which is exactly why a
1848
1912
  * write path that silently dropped it would be an unobservable loss a future
@@ -1931,7 +1995,7 @@ function entryPairKey(pair: readonly [number, number]): string {
1931
1995
 
1932
1996
  /**
1933
1997
  * What fragmenting `row` at the caller's range COSTS, or `null` when it costs
1934
- * nothing this record could describe (CR-10).
1998
+ * nothing this record could describe.
1935
1999
  *
1936
2000
  * `null` in exactly two cases, both of them honest:
1937
2001
  * * the row is not a split-table layout -- asked through `isSplitDataType`,
@@ -2032,15 +2096,14 @@ function splitReinterpretation(
2032
2096
  * signal that distinguishes a no-op, and the revision is never that signal.
2033
2097
  *
2034
2098
  * ---------------------------------------------------------------------------
2035
- * DECISION 1: THE SPLIT-ROW REMAINDER RULE -- TWO OUTCOMES, NEITHER SILENT
2036
- * (CR-09 and CR-10).
2099
+ * DECISION 1: THE SPLIT-ROW REMAINDER RULE -- TWO OUTCOMES, NEITHER SILENT.
2037
2100
  * ---------------------------------------------------------------------------
2038
2101
  * A split-table row has TWO things that can go wrong when a caller's range
2039
2102
  * fragments it, and this function answers them differently on purpose. The
2040
2103
  * comment and the code below state ONE rule, and both halves of it are here.
2041
2104
  *
2042
- * (1) THE ODD REMAINDER IS REFUSED, and the whole retype is refused with it
2043
- * (CR-09). A remainder that is not a legal shape for its OWN type -- the
2105
+ * (1) THE ODD REMAINDER IS REFUSED, and the whole retype is refused with it.
2106
+ * A remainder that is not a legal shape for its OWN type -- the
2044
2107
  * odd-byte-count tail of a fragmented split table is the reachable case -- is a
2045
2108
  * row `setDataType` would decline to create and `resolveSplitTargets()` cannot
2046
2109
  * decode. The store never persists a range row it would refuse at its own entry
@@ -2052,7 +2115,7 @@ function splitReinterpretation(
2052
2115
  * outward to an entry boundary is forbidden outright by `anno-types.ts` trap 7.
2053
2116
  *
2054
2117
  * (2) THE EVEN REMAINDER IS ACCEPTED **WITH A REPORT** -- never accepted
2055
- * silently (CR-10). Parity is not the only thing a fragment can break. A split
2118
+ * silently. Parity is not the only thing a fragment can break. A split
2056
2119
  * table pairs byte `i` with byte `n + i`, so an entry's partner is a function of
2057
2120
  * the row's START and its LENGTH, and changing either end re-pairs EVERY entry.
2058
2121
  *
@@ -2091,17 +2154,17 @@ function splitReinterpretation(
2091
2154
  * `delete`, and the ordering is the guarantee, not a tidiness preference: a
2092
2155
  * refusal must cost nothing observable, and leaning on the transaction's
2093
2156
  * rollback to undo a half-applied mutation would make that depend on a rollback
2094
- * that the CR-06 arm's own `rollbackFailed` handling shows can itself fail.
2157
+ * that the commit handler's own `rollbackFailed` handling shows can itself fail.
2095
2158
  * Compute, refuse, then mutate. The parity check runs FIRST and is untouched by
2096
2159
  * the disclosure: a refusing retype returns no report because it returns nothing
2097
2160
  * at all.
2098
2161
  *
2099
2162
  * ---------------------------------------------------------------------------
2100
- * DECISION 2: THE UNION COLLAPSE IS INTENDED (STORE-02, round-3 WR-08).
2163
+ * DECISION 2: THE UNION COLLAPSE IS INTENDED.
2101
2164
  * ---------------------------------------------------------------------------
2102
2165
  * A caller range that SPANS several existing rows deletes all of them and
2103
- * inserts one row. That is intended, and it does not contradict STORE-02:
2104
- * STORE-02 forbids the store joining adjacent ranges OF ITS OWN ACCORD, and
2166
+ * inserts one row. That is intended, and it does not contradict the store's
2167
+ * own adjacency rule: that rule forbids the store joining adjacent ranges OF ITS OWN ACCORD, and
2105
2168
  * here the caller asked for exactly one range and got exactly one range. The
2106
2169
  * store still never joins two rows nobody asked about -- see the behavioural
2107
2170
  * and structural adjacency controls.
@@ -2148,7 +2211,7 @@ function retype(
2148
2211
  // for a split row that survives that question, what the fragmentation COSTS.
2149
2212
  //
2150
2213
  // WHAT THIS GATE DOES NOT ASK, said here because "THE GATE" reads absolute
2151
- // and a reader will otherwise take it for one (WR-31, 28-21 P1 / 28-07 P3):
2214
+ // and a reader will otherwise take it for one (28-21 P1 / 28-07 P3):
2152
2215
  // the shape question is asked of REMAINDERS, never of the overlapped row
2153
2216
  // itself. A row the caller's range covers in full has no head and no tail,
2154
2217
  // so neither branch below runs and its shape is never examined -- correctly,
@@ -2397,7 +2460,7 @@ export function listRanges(handle: AnnoStoreHandle): RangeRow[] {
2397
2460
  * retained for it", with the oldest retained revision, the bound and the
2398
2461
  * available list.
2399
2462
  * * A ROW CLAIMS IT BUT ITS IMAGE WILL NOT OPEN as an annotation store (step
2400
- * 2, second arm) -- the arm CR-08 added, covering an absent image and a
2463
+ * 2, second arm) -- the arm added to cover an absent image and a
2401
2464
  * present-but-unusable one alike, with the underlying reason quoted so the
2402
2465
  * caller can tell which. This is the arm the old presence-only gate did not
2403
2466
  * have, and its absence is what let the store be destroyed installing an
@@ -2420,7 +2483,7 @@ export function listRanges(handle: AnnoStoreHandle): RangeRow[] {
2420
2483
  */
2421
2484
  /**
2422
2485
  * The one gate on a revision-shaped argument, and it exists because ONE
2423
- * unvalidated value lands in TWO places that can then disagree (WR-22): a bound
2486
+ * unvalidated value lands in TWO places that can then disagree: a bound
2424
2487
  * SQL parameter, and a snapshot FILENAME.
2425
2488
  *
2426
2489
  * Accepts a non-negative safe integer and nothing else. A numeric STRING is
@@ -2431,9 +2494,9 @@ export function listRanges(handle: AnnoStoreHandle): RangeRow[] {
2431
2494
  * ring, which is the confusion `AnnoRevisionArgumentError`'s doc comment records
2432
2495
  * in full.
2433
2496
  *
2434
- * DELIBERATELY NOT REUSED FOR `baseRevision`. The round-5 review's WR-22 sketch
2435
- * suggests it; that is WR-06, which the round-5 verification does not route to
2436
- * this round, so the declination is recorded here rather than left looking like
2497
+ * DELIBERATELY NOT REUSED FOR `baseRevision`. An earlier round's review sketch
2498
+ * suggested it, but that suggestion is a separate finding the same round's
2499
+ * verification did not route to this round, so the declination is recorded here rather than left looking like
2437
2500
  * an omission -- `runWriteSequence`'s existing `baseRevision` staleness refusal
2438
2501
  * is this validator's SIBLING, not its client.
2439
2502
  */
@@ -2456,10 +2519,10 @@ function assertRevisionArgument(value: unknown, parameter: string): number {
2456
2519
  }
2457
2520
 
2458
2521
  export function revertTo(handle: AnnoStoreHandle, revision: number): AnnoStoreHandle {
2459
- // STEP 0, AND IT IS FIRST FOR THE REASON WR-22 RECORDS: this argument reaches
2522
+ // STEP 0, AND IT IS FIRST FOR THE REASON RECORDED ABOVE: this argument reaches
2460
2523
  // a bound SQL parameter AND a filename, so it is judged before either exists.
2461
2524
  // Before this line, `revertTo(handle, "1")` silently reverted the store and
2462
- // `revertTo(handle, "0001")` refused with CR-08's CORRUPTION message.
2525
+ // `revertTo(handle, "0001")` refused with a CORRUPTION message.
2463
2526
  assertRevisionArgument(revision, "revision");
2464
2527
 
2465
2528
  // STEP 1. The pointer row -- the INDEX half of "retained". An EXISTENCE check
@@ -2477,8 +2540,8 @@ export function revertTo(handle: AnnoStoreHandle, revision: number): AnnoStoreHa
2477
2540
  //
2478
2541
  // THE FILE HALF NOW READS `snapshotOpenFailure` -- the same witness
2479
2542
  // `retainedRevisions` reads, in its other position. It used to read
2480
- // `existsSync` and nothing more, which is CR-08: a present image that was not
2481
- // a database passed this gate, and the store was destroyed installing it. The
2543
+ // `existsSync` and nothing more, which let a present image that was not
2544
+ // a database pass this gate, and the store was destroyed installing it. The
2482
2545
  // "oldest retained" and "available revisions" figures are built from
2483
2546
  // `retainedRevisions()` and never from the raw rows, so a refusal cannot
2484
2547
  // steer the caller at a revision the very next call would also refuse.
@@ -2486,8 +2549,8 @@ export function revertTo(handle: AnnoStoreHandle, revision: number): AnnoStoreHa
2486
2549
  // THE ARMS SPLIT ON THE ROW, NOT ON THE FILE'S PRESENCE, and that is
2487
2550
  // deliberate rather than an omission. Asking "is the image absent" separately
2488
2551
  // from "does the image open" would put a SECOND predicate back on a snapshot
2489
- // path -- a second truth about one file, which is how CR-03 and CR-08 both
2490
- // happened. The two file-half sub-cases are distinguished by the QUOTED
2552
+ // path -- a second truth about one file, which is exactly how two of this
2553
+ // store's past defects happened. The two file-half sub-cases are distinguished by the QUOTED
2491
2554
  // REASON instead: an absent image quotes "the file does not exist", a corrupt
2492
2555
  // one quotes what SQLite or `openStore` said. One witness, one message, and
2493
2556
  // the caller can still tell them apart.
@@ -2520,7 +2583,7 @@ export function revertTo(handle: AnnoStoreHandle, revision: number): AnnoStoreHa
2520
2583
 
2521
2584
  const storePath = handle.path;
2522
2585
  const dir = handle.dir;
2523
- // THE STAGING NAME IS UNIQUE PER ATTEMPT, NOT PER (PID, REVISION) (WR-24),
2586
+ // THE STAGING NAME IS UNIQUE PER ATTEMPT, NOT PER (PID, REVISION),
2524
2587
  // and it is built from `randomUUID` -- the SAME primitive `stageSnapshot`
2525
2588
  // uses, so there is ONE answer in this module to "how is a staging name made
2526
2589
  // unique" rather than two that can drift. `stageSnapshot`'s own doc comment
@@ -2537,12 +2600,12 @@ export function revertTo(handle: AnnoStoreHandle, revision: number): AnnoStoreHa
2537
2600
  // addressed the same path.
2538
2601
  //
2539
2602
  // AND WHAT IT DOES NOT CLOSE, stated because a comment that implied otherwise
2540
- // would be prohibition 28-07 P3's exact shape: THE LEAK HALF STAYS OPEN UNDER
2541
- // WR-11. A process killed between the copy and any of the three cleanups
2603
+ // would be prohibition 28-07 P3's exact shape: THE LEAK HALF STAYS OPEN.
2604
+ // A process killed between the copy and any of the three cleanups
2542
2605
  // still leaves this file behind, and it sits beside the store rather than
2543
2606
  // inside the ring directory, so `reconcileSnapshotRing`'s sweep -- anchored on
2544
2607
  // `r<digits>.db` inside `snapshotDirFor()` -- does not and must not match it.
2545
- // Nothing reclaims it. That is WR-11's other half and it is not closed here.
2608
+ // Nothing reclaims it. That is the other half of this gap and it is not closed here.
2546
2609
  //
2547
2610
  // THE PID IS KEPT deliberately: it is the diagnostic that lets a human finding
2548
2611
  // a leaked file say which process produced it, and the `.revert-` marker is
@@ -2553,8 +2616,8 @@ export function revertTo(handle: AnnoStoreHandle, revision: number): AnnoStoreHa
2553
2616
  // failure reachable here -- `ENOENT`, `ENOSPC`, `EACCES` -- therefore leaves
2554
2617
  // the caller a USABLE handle: nothing has been replaced yet, so
2555
2618
  // `currentRevision(handle)` and `listRanges(handle)` still answer and the
2556
- // caller can decide what to do. That is WR-02's entire complaint, and it is
2557
- // fixed by ordering rather than by a rescue path.
2619
+ // caller can decide what to do. That is exactly the failure this ordering
2620
+ // exists to prevent, and it is fixed by ordering rather than by a rescue path.
2558
2621
  try {
2559
2622
  copyFileSync(snapPath, staging);
2560
2623
  fsyncPath(staging);
@@ -2568,7 +2631,7 @@ export function revertTo(handle: AnnoStoreHandle, revision: number): AnnoStoreHa
2568
2631
  );
2569
2632
  }
2570
2633
 
2571
- // STEP 3b, AND ITS POSITION IS THE GUARANTEE (CR-08). OPEN THE STAGED IMAGE
2634
+ // STEP 3b, AND ITS POSITION IS THE GUARANTEE. OPEN THE STAGED IMAGE
2572
2635
  // BEFORE ANYTHING IS CLOSED AND BEFORE ANYTHING IS RENAMED. Until this step
2573
2636
  // existed the only witness that the image about to be installed was a store at
2574
2637
  // all was `existsSync` -- and this module's own FIRST MEASURED FACT (see the
@@ -2633,8 +2696,8 @@ export function revertTo(handle: AnnoStoreHandle, revision: number): AnnoStoreHa
2633
2696
  // function. The image just restored carries `anno_snapshot` rows for
2634
2697
  // revisions whose files an earlier prune removed, and it leaves every
2635
2698
  // snapshot taken AFTER `revision` unclaimed by any row. ONLY THE FILE HALF IS
2636
- // RESOLVED HERE, and the ROW half is deliberately left: since CR-05 the sweep
2637
- // abstains from the pointer-row direction entirely, because it cannot
2699
+ // RESOLVED HERE, and the ROW half is deliberately left: since the fix above
2700
+ // the sweep abstains from the pointer-row direction entirely, because it cannot
2638
2701
  // establish ownership of a row under a second spelling of the store file, so
2639
2702
  // the restored image's stale rows are TOLERATED rather than deleted. This
2640
2703
  // sentence previously claimed BOTH halves were resolved here -- the reversal
@@ -2668,7 +2731,7 @@ export function revertTo(handle: AnnoStoreHandle, revision: number): AnnoStoreHa
2668
2731
  // sweep below can itself stall for up to the connection's five-second
2669
2732
  // `busy_timeout` before `revertTo` returns.
2670
2733
  //
2671
- // CR-07's THIRD PROPERTY: THE SWEEP IS HOUSEKEEPING AND MUST NEVER COST THE
2734
+ // THE THIRD PROPERTY: THE SWEEP IS HOUSEKEEPING AND MUST NEVER COST THE
2672
2735
  // CALLER A HANDLE. By this line the revert has ALREADY SUCCEEDED ON DISK --
2673
2736
  // step 5's rename and directory fsync have returned, so the store file at
2674
2737
  // `storePath` IS the reverted image whatever happens next. Round 3 observed
@@ -2697,7 +2760,7 @@ export function revertTo(handle: AnnoStoreHandle, revision: number): AnnoStoreHa
2697
2760
  // thing in both of its controls rather than letting a green test imply a
2698
2761
  // behavioural proof it does not carry.
2699
2762
  //
2700
- // AND THE REOPEN IS NOW INSIDE THE SAME GUARANTEE (WR-17), WHICH IS WHERE IT
2763
+ // AND THE REOPEN IS NOW INSIDE THE SAME GUARANTEE, WHICH IS WHERE IT
2701
2764
  // BELONGED. The sentence above -- "a housekeeping failure never costs the
2702
2765
  // caller a handle" -- used to hold only for the branch that CANNOT fire. The
2703
2766
  // sweep call was guarded and is unreachable; the two `openStore` calls were
@@ -2739,14 +2802,14 @@ export function revertTo(handle: AnnoStoreHandle, revision: number): AnnoStoreHa
2739
2802
  );
2740
2803
  }
2741
2804
  //
2742
- // AND THE SWEEP'S RESULT IS BOUND RATHER THAN DISCARDED (WR-18), WHICH IS THE
2805
+ // AND THE SWEEP'S RESULT IS BOUND RATHER THAN DISCARDED, WHICH IS THE
2743
2806
  // ARM THE `catch` ABOVE CANNOT SEE. `reconcileSnapshotRing` does not throw
2744
2807
  // when its own `rollback` fails -- rethrowing there is forbidden by 28-11 P5,
2745
2808
  // because on this very call site it would convert a LANDED revert into a
2746
2809
  // caller-visible failure -- so it REPORTS the fact in `rollbackFailed`
2747
2810
  // instead. Reaching that state WITHOUT a throw is exactly why the existing
2748
2811
  // catch arm alone was not enough: `revertTo` would hand back a connection that
2749
- // may still hold the store's write lock, which is CR-07's reported symptom
2812
+ // may still hold the store's write lock, which is the same reported symptom
2750
2813
  // re-created on the revert path.
2751
2814
  //
2752
2815
  // THE REMEDY IS THE SAME BLOCK, REUSED RATHER THAN COPIED: close the
@@ -2951,7 +3014,7 @@ export function listComments(handle: AnnoStoreHandle): CommentRow[] {
2951
3014
  * the existing one's id and two ends. Before that refusal existed this comment
2952
3015
  * and `ScopeRow`'s made a claim the code did not honour, which is exactly the
2953
3016
  * shape prohibition 28-07 P3 forbids. Adjacency is NOT overlap: two scopes that
2954
- * merely touch at a boundary are two scopes, consistent with STORE-02's
3017
+ * merely touch at a boundary are two scopes, consistent with this store's own
2955
3018
  * treatment of ranges.
2956
3019
  *
2957
3020
  * A BYTE-IDENTICAL REPEAT IS AN ACCEPTED NO-OP reporting `changed: false`, and
@@ -2963,7 +3026,7 @@ export function listComments(handle: AnnoStoreHandle): CommentRow[] {
2963
3026
  * `AnnoWriteResult`'s own doc comment states that `changed` is the ONLY signal
2964
3027
  * distinguishing a no-op from a real edit -- and for scopes it could never say
2965
3028
  * no-op, so the module already had the policy and simply could not express it
2966
- * here. Second, Phase 29's success criterion 5 requires a repeated edit to
3029
+ * here. Second, the MCP surface's own success criterion requires a repeated edit to
2967
3030
  * SUCCEED reporting no change, so the surface this store mirrors does have the
2968
3031
  * policy after all. The repeat is therefore accepted rather than refused, and
2969
3032
  * the revision still advances by one, exactly like every other write entry
@@ -2984,7 +3047,7 @@ export function addScope(
2984
3047
  // read the existing row inside the transaction and return `false`. It has
2985
3048
  // to run before the overlap check, because a byte-identical scope
2986
3049
  // overlaps itself and would otherwise be refused rather than accepted as
2987
- // the no-op Phase 29's criterion 5 requires.
3050
+ // the no-op that surface's own criterion requires.
2988
3051
  const identical = db.prepare("select id from anno_scope where start = ? and end_inclusive = ?").get(start, endInclusive) as
2989
3052
  | { id: number }
2990
3053
  | undefined;
@@ -3034,10 +3097,10 @@ export function listScopes(handle: AnnoStoreHandle): ScopeRow[] {
3034
3097
  * `changed: false` when no scope has that span.
3035
3098
  *
3036
3099
  * WHY THIS EXISTS, and why it is not an omission being corrected quietly.
3037
- * `28-VERIFICATION.md`'s `WR-28` recorded that `addScope`'s overlap refusal had
3038
- * no inverse and carried the finding to Phase 29 in as many words, "which puts
3100
+ * A prior verification round recorded that `addScope`'s overlap refusal had
3101
+ * no inverse and carried the finding forward in as many words, "which puts
3039
3102
  * `addScope` on an agent-driven surface where a mistyped span is likelier".
3040
- * `28-REVIEW.md:1788-1814` spells out the consequence: one transposed end --
3103
+ * The review that raised it spells out the consequence: one transposed end --
3041
3104
  * `addScope($1000, $ffff)` -- makes every future scope from `$1000` upward
3042
3105
  * permanently unaddable, recoverable only through `revertTo` inside the
3043
3106
  * 32-revision ring, after which the mistake is permanent for the life of the
@@ -3091,6 +3154,196 @@ export function removeScope(
3091
3154
  return { revision, changed: result };
3092
3155
  }
3093
3156
 
3157
+ /**
3158
+ * Records a user-requested exclusion of `start..endInclusive`, with `reason`
3159
+ * stating WHY the user asked for it -- added at `SCHEMA_VERSION` 5.
3160
+ *
3161
+ * RECORDING AN EXCLUSION CHANGES NOTHING ABOUT WHICH BYTES THE EXPORT EMITS.
3162
+ * The exporter still walks this range's full byte span and emits a real
3163
+ * block, tagged with a visible marker comment, rather than a hole -- that is
3164
+ * this feature's whole invariant. An exporter implementation that skipped the
3165
+ * block on seeing an exclusion row would satisfy the word "exclude" and fail
3166
+ * the requirement outright: this table is a RECORD, never a filter, and the
3167
+ * store answers "what did the user record", never "should this range be
3168
+ * excluded".
3169
+ *
3170
+ * `reason` is validated through `assertCommentText()` -- the ONE comment-text
3171
+ * vocabulary this store has -- BEFORE the write opens, and an empty or
3172
+ * whitespace-only reason is refused with its own message: a `not null`
3173
+ * column satisfied by `""` records that something was excluded and loses WHY,
3174
+ * which is precisely the half of criterion 2 this record exists to carry.
3175
+ *
3176
+ * IDEMPOTENCE FIRST, inside the transaction and BEFORE the overlap check, the
3177
+ * same shape `addScope` uses: an identical repeat -- same extent, same reason
3178
+ * -- is an accepted NO-OP reporting `changed: false`. The SAME extent with a
3179
+ * DIFFERENT reason is REFUSED rather than silently overwritten -- the stored
3180
+ * reason is left exactly as it was, and the route to change it is to remove
3181
+ * the record with `removeExcludedRange` and add it again. Silently replacing
3182
+ * what somebody wrote and reporting success is the failure mode this store's
3183
+ * comment and label verbs already refuse.
3184
+ *
3185
+ * OVERLAP IS REFUSED using `addScope()`'s EXACT predicate --
3186
+ * `start <= ? and end_inclusive >= ?` with the two arguments TRANSPOSED, and
3187
+ * `order by id limit 1` so the message is reproducible -- so adjacency falls
3188
+ * out of the `>=` rather than a second rule: two exclusion records that
3189
+ * merely TOUCH at a boundary are disjoint and both accepted, and they stay
3190
+ * TWO records. The incoming record is NEVER trimmed or split; a caller
3191
+ * wanting a disjoint span reads `listExcludedRanges()` first, or removes the
3192
+ * conflicting record with `removeExcludedRange`.
3193
+ */
3194
+ export function addExcludedRange(
3195
+ handle: AnnoStoreHandle,
3196
+ args: { start: number | string; endInclusive: number | string; reason: string; baseRevision?: number },
3197
+ ): AnnoWriteResult {
3198
+ const start = parseStoreAddress(args.start, { what: "start" });
3199
+ const endInclusive = parseStoreAddress(args.endInclusive, { what: "endInclusive" });
3200
+ assertRangeShape(start, endInclusive, "byte");
3201
+
3202
+ const reason = assertCommentText(args.reason, { what: "exclusion reason" });
3203
+ if (reason.trim() === "") {
3204
+ throw new AnnoCommentError(
3205
+ `exclusion reason is empty or whitespace-only -- a "reason" column satisfied by an empty string records that something was excluded ` +
3206
+ `and loses WHY, which is precisely the half of criterion 2 this record exists to carry. Supply the reason the user gave.`,
3207
+ { reason: "empty reason" },
3208
+ );
3209
+ }
3210
+
3211
+ const { revision, result } = applyWrite(
3212
+ handle,
3213
+ (db) => {
3214
+ // IDEMPOTENCE FIRST, `addScope`'s own shape: read the existing row
3215
+ // inside the transaction and BEFORE the overlap check, because a
3216
+ // byte-identical exclusion overlaps itself and would otherwise be
3217
+ // refused rather than accepted as the no-op an identical repeat requires.
3218
+ const identical = db
3219
+ .prepare("select id, reason from anno_excluded_range where start = ? and end_inclusive = ?")
3220
+ .get(start, endInclusive) as { id: number; reason: string } | undefined;
3221
+ if (identical) {
3222
+ if (identical.reason === reason) return false;
3223
+ throw new AnnoRangeShapeError(
3224
+ `exclusion ${start}..${endInclusive} ($${start.toString(16).padStart(4, "0")}..$${endInclusive.toString(16).padStart(4, "0")}) ` +
3225
+ `is already recorded (id=${identical.id}) with a DIFFERENT reason -- the stored reason is left EXACTLY as it was. Refusing rather ` +
3226
+ `than silently overwriting what somebody wrote: remove the record with removeExcludedRange and add it again to change the reason.`,
3227
+ { start, endInclusive },
3228
+ );
3229
+ }
3230
+
3231
+ // TWO EXCLUSIONS OVERLAP IFF each starts at or before the other ends.
3232
+ // ADJACENCY FALLS OUT OF THE `>=`, `addScope()`'s exact predicate: an
3233
+ // existing exclusion ending at exactly `start - 1` fails
3234
+ // `end_inclusive >= start`, so touching is not overlapping. `order by id
3235
+ // limit 1` reports the FIRST conflicting row so the message is
3236
+ // reproducible.
3237
+ const overlapper = db
3238
+ .prepare("select id, start, end_inclusive from anno_excluded_range where start <= ? and end_inclusive >= ? order by id limit 1")
3239
+ .get(endInclusive, start) as { id: number; start: number; end_inclusive: number } | undefined;
3240
+ if (overlapper) {
3241
+ throw new AnnoRangeShapeError(
3242
+ `exclusion ${start}..${endInclusive} ($${start.toString(16).padStart(4, "0")}..$${endInclusive.toString(16).padStart(4, "0")}) ` +
3243
+ `overlaps the existing exclusion id=${overlapper.id} ${overlapper.start}..${overlapper.end_inclusive} ` +
3244
+ `($${overlapper.start.toString(16).padStart(4, "0")}..$${overlapper.end_inclusive.toString(16).padStart(4, "0")}) -- the incoming ` +
3245
+ `record is NOT trimmed and NOT split: supply a range disjoint from every existing exclusion. Two exclusions that merely TOUCH at a ` +
3246
+ `boundary are disjoint and both accepted. Read listExcludedRanges() first, or removeExcludedRange the conflicting record.`,
3247
+ { start, endInclusive },
3248
+ );
3249
+ }
3250
+
3251
+ db.prepare("insert into anno_excluded_range(start, end_inclusive, reason) values (?, ?, ?)").run(start, endInclusive, reason);
3252
+ return true;
3253
+ },
3254
+ { baseRevision: args.baseRevision },
3255
+ );
3256
+ return { revision, changed: result };
3257
+ }
3258
+
3259
+ /** Every recorded exclusion, in ascending `id` order -- insertion order,
3260
+ * matching `listScopes()` and `listRanges()`. A consumer needing address
3261
+ * order sorts it itself, because a second ordering in the store would be a
3262
+ * second answer to the same question. `anno_excluded_range` has no `bank`
3263
+ * column, in the same shape `listScopes()`'s own doc comment uses for the
3264
+ * same absence: an exclusion is a statement about the subject program, not a
3265
+ * memory view. */
3266
+ export function listExcludedRanges(handle: AnnoStoreHandle): ExcludedRangeRow[] {
3267
+ const rows = handle.db.prepare("select id, start, end_inclusive, reason from anno_excluded_range order by id").all() as {
3268
+ id: number;
3269
+ start: number;
3270
+ end_inclusive: number;
3271
+ reason: string;
3272
+ }[];
3273
+ return rows.map((row) => ({ id: row.id, start: row.start, endInclusive: row.end_inclusive, reason: row.reason }));
3274
+ }
3275
+
3276
+ /**
3277
+ * Removes the exclusion whose span is EXACTLY `start..endInclusive`, and
3278
+ * returns `changed: false` when NO exclusion overlaps that span at all --
3279
+ * the exact inverse of `addExcludedRange`, following `removeScope()`.
3280
+ *
3281
+ * THE SPAN MUST MATCH EXACTLY -- both ends, as stored. A record is never
3282
+ * trimmed, split, or partially removed: a span that PARTIALLY OVERLAPS an
3283
+ * existing record (but does not match it end-for-end) is REFUSED BY NAME
3284
+ * rather than silently ignored, because a partial removal would leave a
3285
+ * shape nothing downstream can express, while reporting success. This is
3286
+ * stricter than `removeScope()`, which reports a mismatched span as a plain
3287
+ * no-op -- an exclusion's reason makes a near-miss removal more dangerous to
3288
+ * treat as "nothing happened", since a caller who meant to clear the record
3289
+ * would otherwise walk away believing it gone. A caller that does not know
3290
+ * the stored span reads it from `listExcludedRanges()` first.
3291
+ *
3292
+ * REMOVING A SPAN THAT DOES NOT OVERLAP ANYTHING STORED IS AN ACCEPTED NO-OP
3293
+ * reporting `changed: false`, matching `removeScope`'s own direction: an
3294
+ * inverse that refuses when there is genuinely nothing to undo makes "undo
3295
+ * this" conditional on knowing whether it was ever done.
3296
+ */
3297
+ export function removeExcludedRange(
3298
+ handle: AnnoStoreHandle,
3299
+ args: { start: number | string; endInclusive: number | string; baseRevision?: number },
3300
+ ): AnnoWriteResult {
3301
+ const start = parseStoreAddress(args.start, { what: "start" });
3302
+ const endInclusive = parseStoreAddress(args.endInclusive, { what: "endInclusive" });
3303
+ // The SAME non-split shape check `addExcludedRange`/`removeScope` use, and
3304
+ // for the same reason: an exclusion is not a table, so the split-table
3305
+ // even-count rule must not apply to it.
3306
+ assertRangeShape(start, endInclusive, "byte");
3307
+
3308
+ const { revision, result } = applyWrite(
3309
+ handle,
3310
+ (db) => {
3311
+ const existing = db.prepare("select id from anno_excluded_range where start = ? and end_inclusive = ?").get(start, endInclusive) as
3312
+ | { id: number }
3313
+ | undefined;
3314
+ if (existing) {
3315
+ db.prepare("delete from anno_excluded_range where id = ?").run(existing.id);
3316
+ return true;
3317
+ }
3318
+
3319
+ // NO EXACT MATCH. Before reporting the ordinary "nothing to undo"
3320
+ // no-op, check whether the incoming span PARTIALLY overlaps a stored
3321
+ // record -- the same overlap predicate `addExcludedRange` uses. That
3322
+ // case is refused BY NAME rather than treated as a no-op, because the
3323
+ // caller plainly meant to remove something that exists and a silent
3324
+ // no-op would misreport the outcome.
3325
+ const overlapper = db
3326
+ .prepare("select id, start, end_inclusive from anno_excluded_range where start <= ? and end_inclusive >= ? order by id limit 1")
3327
+ .get(endInclusive, start) as { id: number; start: number; end_inclusive: number } | undefined;
3328
+ if (overlapper) {
3329
+ throw new AnnoRangeShapeError(
3330
+ `removeExcludedRange: ${start}..${endInclusive} ($${start.toString(16).padStart(4, "0")}..${endInclusive
3331
+ .toString(16)
3332
+ .padStart(4, "0")}) does not EXACTLY match the existing exclusion id=${overlapper.id} ${overlapper.start}..${overlapper.end_inclusive} ` +
3333
+ `($${overlapper.start.toString(16).padStart(4, "0")}..$${overlapper.end_inclusive.toString(16).padStart(4, "0")}), which it ` +
3334
+ `partially overlaps -- a record is never trimmed, split, or partially removed, because that would leave a shape nothing downstream ` +
3335
+ `can express while reporting success. Read listExcludedRanges() first to find the exact stored span.`,
3336
+ { start, endInclusive },
3337
+ );
3338
+ }
3339
+
3340
+ return false;
3341
+ },
3342
+ { baseRevision: args.baseRevision },
3343
+ );
3344
+ return { revision, changed: result };
3345
+ }
3346
+
3094
3347
  /**
3095
3348
  * Validates one project enum's variants mapping and returns it with its KEYS
3096
3349
  * VERBATIM.
@@ -3272,7 +3525,7 @@ export function listProjectEnums(handle: AnnoStoreHandle): ProjectEnumRow[] {
3272
3525
 
3273
3526
  /**
3274
3527
  * Associates ONE address with ONE project enum, so the address's operand is
3275
- * formatted through that enum's variants (`SCHEMA_VERSION` 3, D-15).
3528
+ * formatted through that enum's variants (`SCHEMA_VERSION` 3).
3276
3529
  *
3277
3530
  * THE ASSOCIATION IS BY `anno_enum.id`, NEVER BY NAME, and that is the whole
3278
3531
  * design of the table. `updateProjectEnum` renames an enum in place, keeping
@@ -3296,8 +3549,8 @@ export function listProjectEnums(handle: AnnoStoreHandle): ProjectEnumRow[] {
3296
3549
  *
3297
3550
  * Every argument is validated through `anno-types.ts`'s own assertions before
3298
3551
  * any SQL runs. `parseStoreAddress` in particular refuses an UNPREFIXED numeric
3299
- * string such as `"53280"` outright rather than guessing a base -- WR-22's
3300
- * recorded failure, in which a JSON `"1"` arrived verbatim and SQLite's column
3552
+ * string such as `"53280"` outright rather than guessing a base -- a recorded
3553
+ * failure in which a JSON `"1"` arrived verbatim and SQLite's column
3301
3554
  * affinity turned an argument error into a corruption refusal.
3302
3555
  */
3303
3556
  export function applyEnumUsage(
@@ -3406,18 +3659,276 @@ export function listEnumUsage(handle: AnnoStoreHandle): EnumUsageRow[] {
3406
3659
  }));
3407
3660
  }
3408
3661
 
3662
+ // ---------------------------------------------------------------------------
3663
+ // THE RUNTIME EVIDENCE TABLE (`anno_evid_exec`, `SCHEMA_VERSION` 4).
3664
+ // One raw shape used by all three functions below.
3665
+ // ---------------------------------------------------------------------------
3666
+
3667
+ /** One `anno_evid_exec` row exactly as the column names read on disk --
3668
+ * `snake_case`, matching every other raw-row shape in this module.
3669
+ *
3670
+ * A `type` ALIAS RATHER THAN AN `interface`, and that is load-bearing rather
3671
+ * than stylistic: `node:sqlite`'s `all()` returns `Record<string,
3672
+ * SQLOutputValue>[]`, and casting that to a NAMED `interface` fails TS's
3673
+ * type-assertion comparability check ("neither type sufficiently overlaps")
3674
+ * even though the shapes are identical -- a `type` alias to the same object
3675
+ * shape is accepted. Measured against this exact query shape during this
3676
+ * plan's own implementation. */
3677
+ type RawEvidExecRow = {
3678
+ id: number;
3679
+ image_sha256: string;
3680
+ argv_digest: string;
3681
+ seed: string;
3682
+ address: number;
3683
+ source_bank: string;
3684
+ };
3685
+
3686
+ /** `RawEvidExecRow` -> `EvidExecRow`, the one mapping site both read functions
3687
+ * below share, so the two never drift into disagreeing about the shape. */
3688
+ function toEvidExecRow(row: RawEvidExecRow): EvidExecRow {
3689
+ return {
3690
+ id: row.id,
3691
+ imageSha256: row.image_sha256,
3692
+ argvDigest: row.argv_digest,
3693
+ seed: row.seed,
3694
+ address: row.address,
3695
+ sourceBank: row.source_bank as EvidSourceBank,
3696
+ };
3697
+ }
3698
+
3699
+ /**
3700
+ * Inserts one or more runtime-execution observations for one run identity,
3701
+ * keyed `(imageSha256, argvDigest, seed, address, sourceBank)` -- a live A/B
3702
+ * measured `no-perturbation` from instrumentation, so there is deliberately
3703
+ * no `run_class` discriminator.
3704
+ *
3705
+ * EVERY FIELD IS VALIDATED BEFORE THE FIRST STATEMENT RUNS, and every
3706
+ * refusal is a named `AnnoTypeError`/`AnnoAddressError` carrying the
3707
+ * offending value and the valid domain -- this module's existing refusal
3708
+ * register, never a fresh one. A caller-supplied `imageSha256`/`argvDigest`
3709
+ * that is not exactly 64 lowercase hex characters, a `seed` that is empty,
3710
+ * an `address` outside `ADDRESS_MIN..ADDRESS_MAX`, or a `sourceBank` outside
3711
+ * the frozen three is refused BEFORE the write transaction opens, so a bad
3712
+ * argument never reaches SQL and never partially inserts the rest of the
3713
+ * batch.
3714
+ *
3715
+ * THE WHOLE INSERT IS ONE `applyWrite` CALLBACK (T-43-09): every observation
3716
+ * in `args.observations` is written -- or skipped -- inside the SAME
3717
+ * transaction that `commitTransaction` commits, so a kill mid-ingest leaves
3718
+ * the set fully committed or fully absent, never a partial row set.
3719
+ *
3720
+ * AN OBSERVATION ALREADY PRESENT IS SKIPPED, NOT RE-INSERTED: the existing row is selected first, by the full
3721
+ * unique key, and the insert only runs when it is absent. `changed` is
3722
+ * `false` exactly when every observation in this call was already present --
3723
+ * the same `changed`-is-the-only-no-op-signal contract `AnnoWriteResult`
3724
+ * states for every other write entry point in this module. `revision` still
3725
+ * advances by exactly one on every accepted call, no-op or not, for the same
3726
+ * reason.
3727
+ *
3728
+ * `insertedCount` IS COUNTED INSIDE THIS SAME TRANSACTION, never
3729
+ * derived from a separate read taken before `applyWrite` opens it: a caller
3730
+ * that wants "how many of these rows were actually new" must not be handed a
3731
+ * number computed from a `listExecObservations()` snapshot that a concurrent
3732
+ * writer to the SAME run identity could have moved past between that read
3733
+ * and this insert's own commit. Counting the per-row `existing`/insert
3734
+ * branch already taken above is the one place this number can be exact.
3735
+ */
3736
+ export interface InsertExecObservationsResult extends AnnoWriteResult {
3737
+ insertedCount: number;
3738
+ }
3739
+
3740
+ export function insertExecObservations(
3741
+ handle: AnnoStoreHandle,
3742
+ args: {
3743
+ imageSha256: unknown;
3744
+ argvDigest: unknown;
3745
+ seed: unknown;
3746
+ observations: readonly { address: unknown; sourceBank: unknown }[];
3747
+ baseRevision?: number;
3748
+ },
3749
+ ): InsertExecObservationsResult {
3750
+ const imageSha256 = assertRunIdentityDigest(args.imageSha256, "imageSha256");
3751
+ const argvDigest = assertRunIdentityDigest(args.argvDigest, "argvDigest");
3752
+ const seed = assertRunIdentitySeed(args.seed);
3753
+
3754
+ if (!Array.isArray(args.observations) || args.observations.length === 0) {
3755
+ throw new AnnoTypeError(`observations must be a non-empty array, got ${JSON.stringify(args.observations)}`, {
3756
+ dataType: args.observations,
3757
+ });
3758
+ }
3759
+ // VALIDATED IN FULL BEFORE THE FIRST STATEMENT, per this function's own
3760
+ // doc comment: a bad entry at index 9 must not leave entries 0..8 written.
3761
+ const parsedObservations = args.observations.map((obs) => ({
3762
+ address: parseStoreAddress((obs as { address: unknown }).address, { what: "address" }),
3763
+ sourceBank: assertEvidSourceBank((obs as { sourceBank: unknown }).sourceBank),
3764
+ }));
3765
+
3766
+ const { revision, result } = applyWrite(
3767
+ handle,
3768
+ (db) => {
3769
+ let insertedCount = 0;
3770
+ for (const obs of parsedObservations) {
3771
+ const existing = db
3772
+ .prepare("select id from anno_evid_exec where image_sha256 = ? and argv_digest = ? and seed = ? and address = ? and source_bank = ?")
3773
+ .get(imageSha256, argvDigest, seed, obs.address, obs.sourceBank) as { id: number } | undefined;
3774
+ if (existing) continue;
3775
+ db.prepare("insert into anno_evid_exec(image_sha256, argv_digest, seed, address, source_bank) values (?, ?, ?, ?, ?)").run(
3776
+ imageSha256,
3777
+ argvDigest,
3778
+ seed,
3779
+ obs.address,
3780
+ obs.sourceBank,
3781
+ );
3782
+ insertedCount++;
3783
+ }
3784
+ return insertedCount;
3785
+ },
3786
+ { baseRevision: args.baseRevision },
3787
+ );
3788
+ return { revision, changed: result > 0, insertedCount: result };
3789
+ }
3790
+
3791
+ /** One shape used by `listExecObservations`'s four fixed queries below. */
3792
+ type EvidRunIdentityFilter = { imageSha256: string; argvDigest: string; seed: string };
3793
+
3794
+ /**
3795
+ * Every runtime-execution observation, in ascending ADDRESS then `id` order,
3796
+ * with optional filters on `address` and on the full run identity. Never a
3797
+ * `select *` -- every column is named.
3798
+ *
3799
+ * A RUN-IDENTITY FILTER IS ALL THREE FIELDS TOGETHER OR NONE. A partial
3800
+ * identity (the seed alone, say) would silently widen the match to every
3801
+ * image/argv pair that happens to share it, which is not what "filter by run
3802
+ * identity" means -- refused BY NAME rather than accepted as a wider query
3803
+ * nobody asked for.
3804
+ */
3805
+ export function listExecObservations(
3806
+ handle: AnnoStoreHandle,
3807
+ opts: { address?: number | string; imageSha256?: unknown; argvDigest?: unknown; seed?: unknown } = {},
3808
+ ): EvidExecRow[] {
3809
+ const hasAddress = opts.address !== undefined;
3810
+ const identityFieldsGiven = [opts.imageSha256, opts.argvDigest, opts.seed].filter((v) => v !== undefined).length;
3811
+ if (identityFieldsGiven > 0 && identityFieldsGiven < 3) {
3812
+ throw new AnnoTypeError(
3813
+ "listExecObservations: a run-identity filter requires imageSha256, argvDigest AND seed together -- a partial identity would silently widen the match",
3814
+ { dataType: { imageSha256: opts.imageSha256, argvDigest: opts.argvDigest, seed: opts.seed } },
3815
+ );
3816
+ }
3817
+ const hasIdentity = identityFieldsGiven === 3;
3818
+
3819
+ const address = hasAddress ? parseStoreAddress(opts.address as number | string, { what: "address" }) : undefined;
3820
+ const identity: EvidRunIdentityFilter | undefined = hasIdentity
3821
+ ? {
3822
+ imageSha256: assertRunIdentityDigest(opts.imageSha256, "imageSha256"),
3823
+ argvDigest: assertRunIdentityDigest(opts.argvDigest, "argvDigest"),
3824
+ seed: assertRunIdentitySeed(opts.seed),
3825
+ }
3826
+ : undefined;
3827
+
3828
+ let rows: RawEvidExecRow[];
3829
+ if (address !== undefined && identity !== undefined) {
3830
+ rows = handle.db
3831
+ .prepare(
3832
+ "select id, image_sha256, argv_digest, seed, address, source_bank from anno_evid_exec where address = ? and image_sha256 = ? and argv_digest = ? and seed = ? order by address, id",
3833
+ )
3834
+ .all(address, identity.imageSha256, identity.argvDigest, identity.seed) as RawEvidExecRow[];
3835
+ } else if (address !== undefined) {
3836
+ rows = handle.db
3837
+ .prepare("select id, image_sha256, argv_digest, seed, address, source_bank from anno_evid_exec where address = ? order by address, id")
3838
+ .all(address) as RawEvidExecRow[];
3839
+ } else if (identity !== undefined) {
3840
+ rows = handle.db
3841
+ .prepare(
3842
+ "select id, image_sha256, argv_digest, seed, address, source_bank from anno_evid_exec where image_sha256 = ? and argv_digest = ? and seed = ? order by address, id",
3843
+ )
3844
+ .all(identity.imageSha256, identity.argvDigest, identity.seed) as RawEvidExecRow[];
3845
+ } else {
3846
+ rows = handle.db
3847
+ .prepare("select id, image_sha256, argv_digest, seed, address, source_bank from anno_evid_exec order by address, id")
3848
+ .all() as RawEvidExecRow[];
3849
+ }
3850
+
3851
+ return rows.map(toEvidExecRow);
3852
+ }
3853
+
3854
+ /**
3855
+ * Every distinct run identity with an `anno_evid_exec` row, its accumulated
3856
+ * observation count, and the `denominator` those counts are a fraction of.
3857
+ *
3858
+ * WHY A DENOMINATOR IS RETURNED AT ALL, AND WHY IT NEVER FORMS A PERCENTAGE
3859
+ * ITSELF. A bare count invites the reading "the rest is data" -- exactly the
3860
+ * soundness violation this table's own discipline forbids (see `RuntimeExecClass`'s own doc
3861
+ * comment in `anno-types.ts`). `denominator` is `ADDRESS_MAX - ADDRESS_MIN +
3862
+ * 1`, read from `anno-types.ts`'s own constants rather than the literal
3863
+ * `65536` -- a caller comparing a run's `observationCount` against it forms
3864
+ * its own fraction, and there is deliberately no rounding site in this
3865
+ * module to do that division for them (see this file's `toFixed` census in
3866
+ * this task's own acceptance criteria).
3867
+ */
3868
+ export function listObservedRuns(handle: AnnoStoreHandle): { runs: ObservedRunRow[]; denominator: number } {
3869
+ const rows = handle.db
3870
+ .prepare(
3871
+ "select image_sha256, argv_digest, seed, count(*) as observation_count from anno_evid_exec group by image_sha256, argv_digest, seed order by image_sha256, argv_digest, seed",
3872
+ )
3873
+ .all() as { image_sha256: string; argv_digest: string; seed: string; observation_count: number }[];
3874
+ return {
3875
+ runs: rows.map((row) => ({
3876
+ imageSha256: row.image_sha256,
3877
+ argvDigest: row.argv_digest,
3878
+ seed: row.seed,
3879
+ observationCount: row.observation_count,
3880
+ })),
3881
+ denominator: ADDRESS_MAX - ADDRESS_MIN + 1,
3882
+ };
3883
+ }
3884
+
3885
+ /**
3886
+ * Deletes every `anno_evid_exec` row for one run identity -- a bracket
3887
+ * reset. A run identity holding no rows returns `changed: false`
3888
+ * and is NOT an error: resetting an empty bracket is the ordinary thing,
3889
+ * matching `clearEnumUsage`'s own direction for the identical case.
3890
+ *
3891
+ * THIS IS THE MODULE'S FIFTH ROW-DELETING STATEMENT. `removeScope`'s doc
3892
+ * block (`anno-store.ts`) states the count as four; this one supersedes it.
3893
+ * The count is written in prose, deliberately without spelling the SQL
3894
+ * prefix a census greps for, so a `grep` over this module counts STATEMENTS
3895
+ * and not the sentences describing them. This statement runs inside the
3896
+ * write sequence's transaction, so a refusal raised anywhere in the
3897
+ * sequence rolls it back with everything else -- and a refusal here can
3898
+ * only come from validating the run-identity arguments themselves, since
3899
+ * deleting zero rows is success, not an error.
3900
+ */
3901
+ export function deleteExecObservationsForRun(
3902
+ handle: AnnoStoreHandle,
3903
+ args: { imageSha256: unknown; argvDigest: unknown; seed: unknown; baseRevision?: number },
3904
+ ): AnnoWriteResult {
3905
+ const imageSha256 = assertRunIdentityDigest(args.imageSha256, "imageSha256");
3906
+ const argvDigest = assertRunIdentityDigest(args.argvDigest, "argvDigest");
3907
+ const seed = assertRunIdentitySeed(args.seed);
3908
+
3909
+ const { revision, result } = applyWrite(
3910
+ handle,
3911
+ (db) => {
3912
+ const info = db.prepare("delete from anno_evid_exec where image_sha256 = ? and argv_digest = ? and seed = ?").run(imageSha256, argvDigest, seed);
3913
+ return Number(info.changes) > 0;
3914
+ },
3915
+ { baseRevision: args.baseRevision },
3916
+ );
3917
+ return { revision, changed: result };
3918
+ }
3919
+
3409
3920
  /**
3410
3921
  * Records ONE NON-DERIVABLE cross-reference.
3411
3922
  *
3412
3923
  * THIS IS THE C-5 RECONCILIATION, written down here for a reader of the code
3413
3924
  * rather than left in a plan. Two requirement texts look like they conflict:
3414
- * `STORE-05` requires cross-reference rows to carry their access kind from the
3925
+ * one requires cross-reference rows to carry their access kind from the
3415
3926
  * first write, while the cross-reference criterion requires references to be
3416
3927
  * DERIVED on every query and never cached on disk. Both hold at once, and this
3417
3928
  * entry point is where:
3418
3929
  *
3419
3930
  * * the table and its `access_kind` column exist from the first write (the
3420
- * `DDL` above), so `STORE-05` is satisfied structurally;
3931
+ * `DDL` above), so that requirement is satisfied structurally;
3421
3932
  * * the only rows ever written here are references that CANNOT be recovered
3422
3933
  * from the bytes -- hand-asserted, or resolved from something outside the
3423
3934
  * program image. The `COMPUTED_JUMP` case is exactly that: a computed