@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-types.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  //
4
4
  // The ONE place that writes down the annotation store's data-type vocabulary,
5
5
  // its range row shape, and every validator the store runs before a caller's
6
- // argument is allowed anywhere near SQL (STORE-01).
6
+ // argument is allowed anywhere near SQL.
7
7
  //
8
8
  // ---------------------------------------------------------------------------
9
9
  // WHY THIS FILE EXISTS
@@ -58,8 +58,8 @@
58
58
  // path lands outside the root once symbolic links are followed, and that
59
59
  // is a filesystem question that no string comparison can answer -- the
60
60
  // earlier pure-string version accepted a symlinked subdirectory and let a
61
- // store file be created outside the workspace root (`28-VERIFICATION.md`
62
- // gap 3 / `28-REVIEW.md` CR-03, reproduced). Every OTHER export is still
61
+ // store file be created outside the workspace root, confirmed by a live
62
+ // symlink reproduction. Every OTHER export is still
63
63
  // a pure function of its arguments and still unit-testable with no file
64
64
  // on disk. The exception is named here, in `storePathWithinWorkspace`'s
65
65
  // own doc comment, and in `anno-types.test.ts`'s mutable-state assertion
@@ -68,7 +68,7 @@
68
68
  // `parseStoreAddress()` takes an integer, a `$hex` string and a
69
69
  // `0x`/`0X` string, and refuses `"1024"`. This is a REAL, user-visible
70
70
  // divergence from `stock-address.ts:155-160`, which accepts the bare
71
- // decimal form AS DECIMAL under its own decision `D-04` and says so at
71
+ // decimal form AS DECIMAL and says so at
72
72
  // `:89-105`. The reason the store diverges: a mis-based address written
73
73
  // into the store is PERSISTENT and silently wrong -- every later reader
74
74
  // inherits it -- whereas a mis-based memory read is transient and the
@@ -92,26 +92,28 @@
92
92
  // sanitized or quoted."
93
93
  // 8. NEVER restate the eleven auto-generated-name prefixes here. They live in
94
94
  // exactly one place, `anno-coverage.ts`'s `AUTO_NAME_PREFIX_RE`, and
95
- // `EXPORT-02` names the exact failure a short reimplementation causes: a
96
- // five-prefix copy silently under-counts, which breaks the
95
+ // a short reimplementation causes the exact failure this project has
96
+ // already measured: a five-prefix copy silently under-counts, which breaks the
97
97
  // `routine-queue-walker` skill's backlog construction while every test
98
98
  // keeps passing. The store separates the two namespaces with its label
99
99
  // `kind` field, not with a name pattern.
100
100
  // 9. NEVER build a mnemonic-to-access-kind classifier here. Nothing derivable
101
101
  // is stored (see `anno-store.ts`'s `putXref`), `OpcodeEntry` carries no
102
- // access/reads/writes field at all, and `REQUIREMENTS.md` records the
103
- // analysis built on such a field as deferred with a named trigger. A
102
+ // access/reads/writes field at all, and analysis built on such a field is
103
+ // deliberately deferred until a named future consumer exists: the field
104
+ // is stored because it is free now and unrecoverable later, but no
105
+ // shipped caller reads it yet. A
104
106
  // classifier written now would have no caller and no way to be wrong
105
107
  // observably.
106
- // 10. NEVER measure comment length in code units. `String.length` counts UTF-16
107
- // code units, so a multi-byte comment passes a code-unit check and then
108
+ // 10. NEVER measure comment length in code units. `String.length` counts
109
+ // 16-bit code units, so a multi-byte comment passes a code-unit check and then
108
110
  // exceeds the byte bound on disk. `assertCommentText()` measures with a
109
111
  // `TextEncoder`.
110
112
  import { existsSync, lstatSync, readlinkSync, realpathSync } from "node:fs";
111
113
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
112
114
 
113
115
  import { OPCODES } from "./disasm-opcodes.ts";
114
- import { ViceError, type ViceErrorOptions } from "./vice.ts";
116
+ import { ViceError, type ViceErrorOptions } from "./vice-errors.ts";
115
117
 
116
118
  /**
117
119
  * The on-disk schema version every store file carries in `anno_meta`. A
@@ -133,26 +135,26 @@ import { ViceError, type ViceErrorOptions } from "./vice.ts";
133
135
  *
134
136
  * * TWO STORES IN ONE DIRECTORY SHARED ONE RING under the same
135
137
  * `r<revision>.db` filenames, so `revertTo` on one store restored the
136
- * OTHER store's whole database, silently and with no error (CR-01).
138
+ * OTHER store's whole database, silently and with no error.
137
139
  * * RENAMING THE CONTAINING DIRECTORY invalidated every persisted absolute
138
140
  * path at once, after which `retainedRevisions()` reported none and the
139
- * next accepted write's prune destroyed the entire revert history (CR-03).
141
+ * next accepted write's prune destroyed the entire revert history.
140
142
  *
141
143
  * Version 2 drops `anno_snapshot.path` -- there is no persisted string left for
142
144
  * a second namespace to disagree with -- and derives the location from the
143
145
  * handle at every read and every delete via `snapshotDirFor()`.
144
146
  *
145
147
  * A VERSION-1 STORE IS REFUSED, NOT UPGRADED, and the reason is that the
146
- * version-1 ring's OWNERSHIP is not recoverable: CR-01 means two stores may
148
+ * version-1 ring's OWNERSHIP is not recoverable: two stores may
147
149
  * both have written into `<dir>/snapshots`, and nothing recorded which file
148
150
  * belonged to which store. Any migration would have to guess, attributing one
149
- * store's history to another -- CR-01 again with a new cause and no test
150
- * watching. The legacy directory is therefore left on disk untouched: never
151
+ * store's history to another -- the same failure shape recurring with a new
152
+ * cause and no test watching. The legacy directory is therefore left on disk untouched: never
151
153
  * adopted, never migrated, never deleted, so the bytes stay recoverable by
152
154
  * hand.
153
155
  *
154
156
  * ---------------------------------------------------------------------------
155
- * VERSION 3, 2026-08-29 (D-15) -- AND THE COST IS NAMED HERE RATHER THAN LEFT
157
+ * VERSION 3, 2026-08-29 -- AND THE COST IS NAMED HERE RATHER THAN LEFT
156
158
  * IN A PLANNING DIRECTORY, because a version number whose rationale lives
157
159
  * somewhere else is a number the next reader has no way to weigh.
158
160
  *
@@ -182,8 +184,158 @@ import { ViceError, type ViceErrorOptions } from "./vice.ts";
182
184
  * exist, and it would put new code into a module six hardening rounds went
183
185
  * into. Stating the difference is the point: this one is a choice, not an
184
186
  * impossibility.
187
+ *
188
+ * ---------------------------------------------------------------------------
189
+ * VERSION 4, 2026-09-10 -- THE DECISION IS `reaffirm-refusal`, AND
190
+ * THE FACTUAL BASIS IS TRANSCRIBED HERE RATHER THAN LEFT IN A PLANNING
191
+ * DIRECTORY, for the same reason VERSION 3's paragraph gives.
192
+ *
193
+ * WHAT THE BUMP BUYS: `anno_evid_exec`, the durable, run-identity-keyed table
194
+ * that records ONE fact per row -- an address was OBSERVED EXECUTING during a
195
+ * specific run -- so a later session can query accumulated runtime evidence
196
+ * instead of re-running the program. See `anno-store.ts`'s `DDL` and its
197
+ * `insertExecObservations` / `listExecObservations` / `listObservedRuns` /
198
+ * `deleteExecObservationsForRun`.
199
+ *
200
+ * THE OPTION SELECTED, BY NAME: `reaffirm-refusal`. The strict-equality
201
+ * refusal inside `openStore` stays exactly as VERSION 3 left it -- a
202
+ * version-3 store does not open under this `SCHEMA_VERSION`, no migration arm
203
+ * is written, and the single-witness property (one comparison site, no
204
+ * second write of `anno_meta.schema_version`) is unchanged.
205
+ *
206
+ * THE FACTUAL CHECK, RUN 2026-09-10, SCOPE ONE DEVELOPMENT MACHINE:
207
+ * 1. `find / -xdev -name '*.annostore'`, with `.git` directories excluded --
208
+ * empty. The broader `find "$HOME" -name '*.annostore'` (closing the `-xdev`
209
+ * mount-boundary gap) found five hits, all inside a dated (2026-08-27)
210
+ * scratch probe cache directory, with
211
+ * fixture-pattern names (`t-half`, `t-zero`, `t-tail-100`, `p`, `proj`)
212
+ * and one file zero bytes -- synthetic test fixtures, not a real
213
+ * project's store.
214
+ * 2. `git log --oneline --diff-filter=A -- '*.annostore'` -- empty. No
215
+ * `.annostore` has ever been added to this repository's history.
216
+ * 3. Release tags versus when the store landed: `anno-store.ts` was added
217
+ * by `4c9cea3c` (2026-08-27). `v0.7.0` (2026-09-01) and `v0.8.0`
218
+ * (2026-09-06) BOTH POSTDATE that landing and the VERSION 3 bump
219
+ * itself. **THIS CORRECTS THE VERSION 3 PARAGRAPH ABOVE**: its stated
220
+ * justification -- "the last release tag (`v0.5.0`, 2026-08-25)
221
+ * PREDATES the store entirely -- so no tagged release has ever shipped
222
+ * a store at all" -- was true when written (2026-08-29) and is STALE
223
+ * now. Two tagged releases since then could, in principle, have shipped
224
+ * a working store to a real user.
225
+ * 4. Nothing outside that dated scratch/probe cache directory, and nothing
226
+ * tracked in git, was found on this machine.
227
+ *
228
+ * THE RE-AFFIRMATION IS MADE DESPITE THAT CORRECTION, on the strength of the
229
+ * observed NULL RESULT (zero real stores found), not on the strength of the
230
+ * now-stale "no release ever shipped one" framing. What changed since VERSION
231
+ * 3 is the *possibility* of a field store, not an observation of one, and the
232
+ * two directions' costs are asymmetric in reversibility: re-affirming refusal
233
+ * STAYS REVERSIBLE -- a migration arm can be added later, at version 5,
234
+ * against real evidence -- while shipping a mutating open path now adds a
235
+ * half-reasoned migration to a module that also has a corruption-refusal
236
+ * path, exactly where silent corruption lives, inside a plan whose actual job
237
+ * is an evidence table rather than a schema-migration redesign. House style
238
+ * settles the shape too: this project's standing pattern for a mismatched or
239
+ * missing external state is DETECT, THEN REFUSE BY NAME WITH THE REMEDY IN
240
+ * THE MESSAGE (`CLAUDE.md`'s "external tools are never auto-installed"
241
+ * constraint states the same discipline for a different kind of missing
242
+ * state) -- `openStore`'s refusal message is extended to name that remedy
243
+ * explicitly and to say the refused file is left untouched, so a user who
244
+ * hits it is not left inferring either fact.
245
+ *
246
+ * THE FACTUAL BASIS IS ONE MACHINE ONLY, and the limit travels with the
247
+ * decision: this is not a global guarantee that no field store exists --
248
+ * exactly the scope VERSION 3's own paragraph already accepted once.
249
+ *
250
+ * THE VERSION 1 PARAGRAPH ABOVE IS STILL THE PRECEDENT THIS IS MEASURED
251
+ * AGAINST, and this refusal is, again, a CHOICE and not an impossibility:
252
+ * version 4 COULD be given a migration arm and deliberately is not, for the
253
+ * reasons above.
254
+ *
255
+ * THE REVERSAL CONDITION, RECORDED SO THIS DOES NOT QUIETLY HARDEN INTO
256
+ * PRECEDENT: a migration arm at version 5 is justified if a real, non-scratch
257
+ * `.annostore` file is found in the field -- a user's own project, a bug
258
+ * report attaching one, or a support request -- or if a documented
259
+ * distribution channel is shown to have shipped a version-3 or version-4
260
+ * store to users who have not yet upgraded past it. Absent that evidence, the
261
+ * refusal stays the default.
262
+ *
263
+ * ---------------------------------------------------------------------------
264
+ * VERSION 5, 2026-09-11 -- THE DECISION IS `reaffirm-refusal`, THE
265
+ * SAME OPTION VERSION 4 SELECTED, AND THE FACTUAL BASIS IS TRANSCRIBED HERE
266
+ * RATHER THAN LEFT IN A PLANNING DIRECTORY, for the same reason every prior
267
+ * version's paragraph gives.
268
+ *
269
+ * WHAT THE BUMP BUYS: `anno_excluded_range`, the durable record of a
270
+ * user-requested exclusion -- its extent AND the reason the user gave -- so
271
+ * the export can emit the range's real bytes tagged with a visible marker
272
+ * comment instead of a hole, which is this feature's whole invariant: an
273
+ * exclusion is a RECORDED, VISIBLE fact, never a silent drop. See
274
+ * `anno-store.ts`'s `DDL` and its three verbs, `addExcludedRange` /
275
+ * `listExcludedRanges` / `removeExcludedRange`.
276
+ *
277
+ * THE OPTION SELECTED, BY NAME: `reaffirm-refusal`. The strict-equality
278
+ * refusal inside `openStore` stays EXACTLY as VERSION 4 left it -- this bump
279
+ * does not touch that comparison site at all: a version-4 store does not open
280
+ * under this `SCHEMA_VERSION`, no migration arm is written, and the
281
+ * single-witness property (one comparison site, no second write of
282
+ * `anno_meta.schema_version`) is unchanged.
283
+ *
284
+ * THE FACTUAL CHECK, RUN 2026-09-11, SCOPE ONE DEVELOPMENT MACHINE -- the
285
+ * same three checks VERSION 4 ran, re-run rather than inherited, with the
286
+ * REAL results transcribed as of today:
287
+ * 1. `find "$HOME" -name '*.annostore'`, with `.git` directories excluded --
288
+ * SIX hits, not VERSION 4's five. The same five fixture-pattern files
289
+ * inside the dated (2026-08-27) scratch probe cache (`t-half`, `t-zero`,
290
+ * `t-tail-100`, `p`, `proj`) are still present, UNCHANGED. The SIXTH is
291
+ * NEW since VERSION 4's check: `.c64-re-tools/phase45-scratch/
292
+ * tracer.annostore`, inside THIS repository's own gitignored scratch
293
+ * directory, timestamped 2026-09-10 -- its path and name identify it as
294
+ * this project's own tracer/scratch fixture, not a real consuming project's
295
+ * store. Six real files, zero real stores.
296
+ * 2. `git log --oneline --diff-filter=A -- '*.annostore'` -- still empty.
297
+ * No `.annostore` has ever been added to this repository's tracked
298
+ * history.
299
+ * 3. Release tags versus when the store landed: `anno-store.ts` was added
300
+ * by `4c9cea3c` (2026-08-27). Since VERSION 4's check, ONE MORE tagged
301
+ * release has been cut -- `v0.9.0` (2026-09-10) -- joining `v0.7.0`
302
+ * (2026-09-01) and `v0.8.0` (2026-09-06). All THREE now postdate the
303
+ * store's landing and VERSION 3's original bump, so three tagged
304
+ * releases, not two, could in principle have shipped a working store to
305
+ * a real user. No result differs in KIND from VERSION 4's; the count of
306
+ * possible-exposure releases has grown by one, consistent with time
307
+ * having passed rather than with any new evidence of a real store.
308
+ *
309
+ * THE RE-AFFIRMATION IS MADE ON THE SAME BASIS VERSION 4 USED: the observed
310
+ * NULL RESULT (zero real, non-scratch stores found across six candidate
311
+ * files), not on a claim that no release could have shipped one. The new
312
+ * sixth file changes nothing about that null result -- it is this project's
313
+ * OWN scratch output, in a directory this project's own tooling writes to and
314
+ * that ships nothing.
315
+ *
316
+ * WHY A NEW TABLE WAS CHOSEN OVER A NULLABLE `excluded`/`exclusion_reason`
317
+ * COLUMN PAIR ON `anno_range`, recorded because it is the one part of this
318
+ * decision that is structural rather than stylistic: an exclusion's extent is
319
+ * chosen by the USER and has no reason to coincide with a typed range's
320
+ * boundaries, so a column on `anno_range` would force `retype()`'s carve to
321
+ * split, duplicate, or lose an exclusion's reason every time a range boundary
322
+ * moved underneath it -- `retype()` knows nothing about exclusions today and
323
+ * a column would force it to. A separate table keeps the user's own words
324
+ * independent of the store's typing churn entirely. This also happens to
325
+ * match the project's existing preference (`anno_evid_exec` at VERSION 4 over
326
+ * widening `anno_range`), but that precedent is the WEAKER of the two
327
+ * arguments -- the structural one above is the one that would still hold even
328
+ * if this project had no such precedent.
329
+ *
330
+ * THE REVERSAL CONDITION, RECORDED SO THIS DOES NOT QUIETLY HARDEN INTO
331
+ * PRECEDENT: a migration arm at version 6 is justified if a real, non-scratch
332
+ * `.annostore` is found in the field -- a user's own project, a bug report
333
+ * attaching one, or a support request -- or if a documented distribution
334
+ * channel is shown to have shipped a version-4 or version-5 store to users
335
+ * who have not yet upgraded past it. Absent that evidence the refusal stays
336
+ * the default.
185
337
  */
186
- export const SCHEMA_VERSION = 3;
338
+ export const SCHEMA_VERSION = 5;
187
339
 
188
340
  /** The 6510's address space, inclusive at both ends. */
189
341
  export const ADDRESS_MIN = 0x0000;
@@ -193,8 +345,7 @@ export const ADDRESS_MAX = 0xffff;
193
345
  * (`anno-store.ts`'s `snapshotDirFor()` -- a sibling named after the store
194
346
  * FILE, not the fixed `<dir>/snapshots` version 1 used) may hold before the
195
347
  * oldest is pruned. Declared here because the bound is a property of the
196
- * store's format; the pruning that enforces it belongs to the revert surface
197
- * (`STORE-04`). */
348
+ * store's format; the pruning that enforces it belongs to the revert surface. */
198
349
  export const MAX_SNAPSHOT_REVISIONS = 32;
199
350
 
200
351
  /**
@@ -301,7 +452,8 @@ export type LabelKind = (typeof LABEL_KINDS)[number];
301
452
  * is written down, and its provenance needs stating precisely so a later reader
302
453
  * does not over-trust it: these four spellings are CITED from an external
303
454
  * analyser's reference documentation, flowed through this project's own research
304
- * notes, and fixed by `STORE-05`'s requirement text. They are NOT read from any
455
+ * notes, and fixed by this project's own requirement that cross-reference rows
456
+ * carry their access kind. They are NOT read from any
305
457
  * code in this repository, and no comment may present them as verified project
306
458
  * vocabulary.
307
459
  *
@@ -370,7 +522,7 @@ export interface CommentRow {
370
522
  }
371
523
 
372
524
  /**
373
- * One stored comment that a retype has just made FALSE (STORE-03).
525
+ * One stored comment that a retype has just made FALSE.
374
526
  *
375
527
  * The caller needs all four facts to act on the report without a second query:
376
528
  * WHERE the comment is, WHAT it says, WHICH grade fired, and WHICH data type
@@ -389,7 +541,7 @@ export interface ContradictedComment {
389
541
  }
390
542
 
391
543
  /**
392
- * One entry-address pairing of a split table (STORE-03, CR-10).
544
+ * One entry-address pairing of a split table.
393
545
  *
394
546
  * `pairs[i]` is the two ADDRESSES whose bytes form entry `i`, in table order:
395
547
  * the first-half address and its second-half partner. `entryCount` is
@@ -402,8 +554,8 @@ export interface SplitEntryPairs {
402
554
  }
403
555
 
404
556
  /**
405
- * One surviving fragment of a split table that a partial overwrite left behind
406
- * (CR-10). `entryPairs` is what that fragment reads NOW -- not what the addresses
557
+ * One surviving fragment of a split table that a partial overwrite left behind.
558
+ * `entryPairs` is what that fragment reads NOW -- not what the addresses
407
559
  * in it used to be paired with.
408
560
  */
409
561
  export interface SplitTableSurvivor {
@@ -415,7 +567,7 @@ export interface SplitTableSurvivor {
415
567
 
416
568
  /**
417
569
  * What one accepted partial overwrite of a split table COST, reported as data on
418
- * a successful `setDataType()` result (STORE-03, CR-10). Documented in the same
570
+ * a successful `setDataType()` result. Documented in the same
419
571
  * register as `ContradictedComment` above, and for the same reason: a caller
420
572
  * needs every fact it would otherwise have to re-query for.
421
573
  *
@@ -464,6 +616,23 @@ export interface ScopeRow {
464
616
  endInclusive: number;
465
617
  }
466
618
 
619
+ /** One user-requested exclusion as the store holds it, added at
620
+ * `SCHEMA_VERSION` 5. Both ends are INCLUSIVE, matching every
621
+ * other range-shaped row this store persists. There is no `bank` column,
622
+ * because an exclusion is a statement about a span of the SUBJECT PROGRAM the
623
+ * user asked to leave out of the rebuild -- not a memory view -- and there is
624
+ * no confidence, verdict, grade or severity column, because this row holds
625
+ * WHAT THE USER ASKED FOR and nothing the tool concluded about it. A
626
+ * judgement column here would be the exact "tool is the decider" shape
627
+ * this project's own 2026-09-10 requirement rewording removed: the store answers "what did the
628
+ * user record", never "should this range be excluded". */
629
+ export interface ExcludedRangeRow {
630
+ id: number;
631
+ start: number;
632
+ endInclusive: number;
633
+ reason: string;
634
+ }
635
+
467
636
  /** One project-local enum as the store holds it. `variants` is keyed by the
468
637
  * numeric-string forms the schema names -- decimal, `0x`/`$` hex, `0b`/`%`
469
638
  * binary -- and its values are variant names. */
@@ -476,7 +645,7 @@ export interface ProjectEnumRow {
476
645
 
477
646
  /**
478
647
  * One enum usage as the store holds it: the association between ONE address
479
- * and ONE project enum, added at `SCHEMA_VERSION` 3 (D-15).
648
+ * and ONE project enum, added at `SCHEMA_VERSION` 3.
480
649
  *
481
650
  * `enumId` IS WHAT THE STORE PERSISTS; `enumName` is resolved through the join
482
651
  * at read time and is never a second on-disk copy of the name. A row that
@@ -505,6 +674,80 @@ export interface XrefRow {
505
674
  bank: number | null;
506
675
  }
507
676
 
677
+ /**
678
+ * The three memory regions `memmapshow` reports an execute observation
679
+ * against, `SCHEMA_VERSION` 4. This is the ONE place this
680
+ * vocabulary is written down.
681
+ *
682
+ * ALL THREE ARE INCLUDED DELIBERATELY, not for symmetry: `AccessFlags`
683
+ * (`textmon-memmap.ts`) carries `read`/`write`/`execute` as three
684
+ * INDEPENDENT bits per region, and dropping any one of the three regions
685
+ * here would discard a genuine positive observation the wire already
686
+ * reported -- `io.execute` is exactly as real a fact as `ram.execute`.
687
+ */
688
+ export const EVID_SOURCE_BANKS = Object.freeze(["ram", "rom", "io"] as const);
689
+
690
+ /** One of the three evidence source banks. */
691
+ export type EvidSourceBank = (typeof EVID_SOURCE_BANKS)[number];
692
+
693
+ /**
694
+ * The runtime evidence layer's own classification of an address, `SCHEMA_VERSION`
695
+ * 4. This is a TYPE-LEVEL control, not a runtime check: the union has
696
+ * exactly two members and NO `"data"` member exists for a caller to return,
697
+ * mistakenly or otherwise.
698
+ *
699
+ * THIS IS THE SAME DISCIPLINE `textmon-memmap.ts` STATES FOR ITSELF, carried
700
+ * forward rather than re-derived: that module's own header forbids adding
701
+ * "a field, key, label or enum member anywhere in this module or its answer
702
+ * types that classifies an address as DATA on the strength of never having
703
+ * been observed" (`textmon-memmap.ts:32-38`). `BlockClass`'s three-valued
704
+ * union (`"code" | "data" | "undefined"`, `block-class.ts`) is the byte-derived
705
+ * classifier's own vocabulary and is DELIBERATELY NOT REUSED here: that union
706
+ * exists to name a byte-derived guess, and this one exists to name only what
707
+ * was actually witnessed executing. `"unobserved"` means exactly "no
708
+ * execution was ever recorded at this address" -- never "this address is
709
+ * data" -- and no code path in this layer may treat the two as
710
+ * interchangeable (see the ban on deriving `data` from absence, Pitfall 3 in
711
+ * this phase's research).
712
+ */
713
+ export type RuntimeExecClass = "code" | "unobserved";
714
+
715
+ /**
716
+ * One runtime-execution observation as the store holds it (`anno_evid_exec`,
717
+ * `SCHEMA_VERSION` 4). Keyed by the bare run-identity triple
718
+ * `(imageSha256, argvDigest, seed)` plus `address` plus `sourceBank` -- there is
719
+ * no `run_class` discriminator column: a live A/B measured `no-perturbation`
720
+ * from instrumentation at anchor hit depths 10 and 50, so a run-class
721
+ * distinction would encode a difference nothing observed.
722
+ *
723
+ * A row here asserts exactly ONE fact: this address was observed executing,
724
+ * in this source bank, during this run. There is no `bank`-reserved column
725
+ * (unlike the annotation tables) and no nullable column -- every field is a
726
+ * fact this table is licensed to assert, or the row does not exist.
727
+ */
728
+ export interface EvidExecRow {
729
+ id: number;
730
+ imageSha256: string;
731
+ argvDigest: string;
732
+ seed: string;
733
+ address: number;
734
+ sourceBank: EvidSourceBank;
735
+ }
736
+
737
+ /**
738
+ * One distinct run identity's accumulated observation count, as
739
+ * `listObservedRuns()` reports it. `observationCount` is a COUNT, never a
740
+ * percentage or rate -- see `listObservedRuns`'s own doc comment in
741
+ * `anno-store.ts` for the denominator this count is a fraction of (a count
742
+ * with no denominator invites the reading "the rest is data").
743
+ */
744
+ export interface ObservedRunRow {
745
+ imageSha256: string;
746
+ argvDigest: string;
747
+ seed: string;
748
+ observationCount: number;
749
+ }
750
+
508
751
  /** What `resolveSplitTargets()` returns: the entry count, the resolved 16-bit
509
752
  * targets in table order, and whether this layout produces cross-references at
510
753
  * all. Nothing here is ever written to disk. */
@@ -586,7 +829,7 @@ export interface AnnoRevisionArgumentErrorOptions {
586
829
  * path is built, so nothing has been read and nothing has been written.
587
830
  *
588
831
  * WHY THIS IS NOT `AnnoStoreCorruptError`, WHICH IS THE WHOLE REASON THE CLASS
589
- * EXISTS (WR-22). Until this class existed, `revertTo(handle, "0001")` matched
832
+ * EXISTS. Until this class existed, `revertTo(handle, "0001")` matched
590
833
  * revision 1's pointer row through SQLite's INTEGER affinity on a bound TEXT
591
834
  * operand, while `snapshotPathFor` built `r0001.db` from the raw string -- so
592
835
  * the two disagreed and the caller was told its snapshot was "not a readable
@@ -840,7 +1083,8 @@ export interface AnnoCommentGradeErrorOptions {
840
1083
  *
841
1084
  * It is NEVER correct to swallow the original and treat the comment as ungraded:
842
1085
  * that would quietly exempt a malformed comment from contradiction reporting,
843
- * which is the same silent un-documenting `STORE-03` exists to prevent.
1086
+ * which is the same silent-un-documenting failure this store's contradiction
1087
+ * reporting exists to prevent.
844
1088
  */
845
1089
  export class AnnoCommentGradeError extends AnnoStoreError {
846
1090
  comment?: string;
@@ -974,7 +1218,7 @@ const MAX_SYMLINK_HOPS = 40;
974
1218
  * Does the path ENTRY `p` exist -- that is, does this NAME exist in its
975
1219
  * directory?
976
1220
  *
977
- * THIS IS THE WHOLE OF `CR-04`, in two sentences. `existsSync` answers a
1221
+ * THE WHOLE OF THE DISTINCTION, IN TWO SENTENCES. `existsSync` answers a
978
1222
  * different question: "does this path RESOLVE to something?", which follows
979
1223
  * symbolic links and therefore reports `false` for a dangling one. `lstat`
980
1224
  * answers "does this NAME exist?", which does not follow the link. The two
@@ -984,8 +1228,7 @@ const MAX_SYMLINK_HOPS = 40;
984
1228
  *
985
1229
  * `throwIfNoEntry: false` makes the ABSENT case a value rather than an
986
1230
  * exception, so the caller has one branch instead of a `try` around a
987
- * predicate. That option suppresses `ENOENT` AND NOTHING ELSE, which is the
988
- * whole of `WR-12`.
1231
+ * predicate. That option suppresses `ENOENT` AND NOTHING ELSE.
989
1232
  *
990
1233
  * REVERSED 2026-08-28, and the reversal is the record rather than a deletion
991
1234
  * (this module's header discipline, 28-07 P3). The premise that was RIGHT and
@@ -1005,7 +1248,7 @@ const MAX_SYMLINK_HOPS = 40;
1005
1248
  * * `ELOOP` -- a symlink cycle in an ANCESTOR position, where the kernel
1006
1249
  * refuses at `lstat` before the manual hop counter below ever runs.
1007
1250
  *
1008
- * `28-REVIEW.md` WR-12 has the before/after transcript. The `try` restores the
1251
+ * The `try` restores the
1009
1252
  * family WITHOUT restoring the old blindness: the absent case is still a value
1010
1253
  * and still one branch, and everything else is a decision naming both the entry
1011
1254
  * the walk stopped on and the path being confined.
@@ -1052,8 +1295,7 @@ function pathEntryExists(p: string, resolved: string): boolean {
1052
1295
  * /tmp/annosym-XXXX/ws/p.annostore`) and `openStore` created the store file
1053
1296
  * OUTSIDE the workspace root (`A) file created OUTSIDE workspace: true`);
1054
1297
  * separately, a dangling DIRECTORY link was accepted at the predicate
1055
- * (`B dangling dir -> ACCEPTED`). `28-VERIFICATION.md` gap 2 / `28-REVIEW.md`
1056
- * CR-04. The walk now stops on `pathEntryExists`, which is `lstat` and does not
1298
+ * (`B dangling dir -> ACCEPTED`). The walk now stops on `pathEntryExists`, which is `lstat` and does not
1057
1299
  * follow the link.
1058
1300
  *
1059
1301
  * THE DANGLING STOPPING ENTRY IS RESOLVED BY HAND, because nothing else will:
@@ -1162,8 +1404,8 @@ function realpathOfNearestExisting(p: string): string {
1162
1404
  * * The PATH must be a real path, because `resolve()` normalises `..` but
1163
1405
  * does NOT follow symbolic links. The pure-string version accepted a
1164
1406
  * symlinked subdirectory inside the workspace and the store file was
1165
- * created outside the root (`28-REVIEW.md` CR-03, reproduced by the phase
1166
- * verifier). A confinement check has to compare what the filesystem will
1407
+ * created outside the root, confirmed by a live symlink reproduction.
1408
+ * A confinement check has to compare what the filesystem will
1167
1409
  * actually do.
1168
1410
  * * The ROOT must go through the SAME walk, for two independent reasons. A
1169
1411
  * workspace root that does not exist is a legitimate input -- the pinned
@@ -1212,8 +1454,7 @@ export function storePathWithinWorkspace(path: string, workspaceRoot: string): s
1212
1454
  * WHY THIS IS A SEAM RATHER THAN AN INLINE `relative()` AT ITS ONE CALL SITE.
1213
1455
  * The spelling this returns goes into a **compared** artifact: the memory
1214
1456
  * map's banner is re-rendered and diffed BYTE FOR BYTE by
1215
- * `checkRenderedMemoryMap()`. The defect it closes (`CR-01`, gap 1 in
1216
- * `.planning/phases/29-the-mcp-surface/29-VERIFICATION.md`) is that machine
1457
+ * `checkRenderedMemoryMap()`. The defect it closes is that machine
1217
1458
  * identity leaked into that content comparison: the banner recorded the
1218
1459
  * absolute realpaths, so the same store, the same sidecar and the same
1219
1460
  * rendered file reported `drifted` as soon as the checkout sat at a different
@@ -1300,6 +1541,37 @@ export function assertAccessKind(value: unknown): XrefAccessKind {
1300
1541
  return assertMember(value, XREF_ACCESS_KINDS, "access kind");
1301
1542
  }
1302
1543
 
1544
+ /** Narrows an unvalidated argument to an `EvidSourceBank`, or throws
1545
+ * `AnnoTypeError` carrying the offending value and all three valid members. */
1546
+ export function assertEvidSourceBank(value: unknown): EvidSourceBank {
1547
+ return assertMember(value, EVID_SOURCE_BANKS, "source bank");
1548
+ }
1549
+
1550
+ /** Exactly 64 lowercase hex characters -- the shape `argvDigest()`
1551
+ * (`capture-predicate.ts`) and a sha256 image hash both already produce.
1552
+ * Refuses anything else BY NAME, including a correctly-shaped but
1553
+ * UPPERCASE digest, rather than lower-casing it silently: two callers
1554
+ * disagreeing on case is exactly the kind of divergence a store write must
1555
+ * not launder into agreement. */
1556
+ export function assertRunIdentityDigest(value: unknown, what: string): string {
1557
+ if (typeof value === "string" && /^[0-9a-f]{64}$/.test(value)) {
1558
+ return value;
1559
+ }
1560
+ throw new AnnoTypeError(
1561
+ `${what} ${JSON.stringify(value)} is not exactly 64 lowercase hex characters -- expected a sha256 hex digest such as argvDigest() or an image hash produces`,
1562
+ { dataType: value },
1563
+ );
1564
+ }
1565
+
1566
+ /** A non-empty string. `seed` is not a digest and carries no shape beyond
1567
+ * that -- this store's run-identity composite treats it as an opaque token. */
1568
+ export function assertRunIdentitySeed(value: unknown): string {
1569
+ if (typeof value === "string" && value.length > 0) {
1570
+ return value;
1571
+ }
1572
+ throw new AnnoTypeError(`seed ${JSON.stringify(value)} is not a non-empty string`, { dataType: value });
1573
+ }
1574
+
1303
1575
  /** The legal-identifier shape, quoted from the schema's own sentence
1304
1576
  * (`anno-tools.ts:246-251`): "starts with a letter or underscore, followed by
1305
1577
  * letters/digits/underscores only". Used for label names and for project-enum
@@ -1573,7 +1845,8 @@ export function resolveSplitTargets(bytes: Uint8Array | readonly number[], dataT
1573
1845
  *
1574
1846
  * A resolver and a writer that each kept their own copy of this arithmetic could
1575
1847
  * disagree about what an entry IS, and the disagreement would be silent: both
1576
- * copies produce legal, decodable rows. That is CR-10's whole class, so the rule
1848
+ * copies produce legal, decodable rows. That is exactly the class of bug a
1849
+ * second implementation would risk, so the rule
1577
1850
  * has one home. `anno-types.test.ts`'s worked-arithmetic pin is the control --
1578
1851
  * changing the couples here to an interleaved `[2i, 2i + 1]` reddens it.
1579
1852
  *