provident-storeroom 1.2.0 → 1.4.0

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.
@@ -38,12 +38,23 @@
38
38
  // ---------------------------------------------------------------------------------------------
39
39
  // `fail-surface.md §2` — THE KERNEL'S OWN CLOSED STATE SET, `8` MEMBERS AND NO NINTH.
40
40
  // ---------------------------------------------------------------------------------------------
41
+ // **⟶ AND THE SET OPENS TO `9` ON 2026-10-28 — `D-53`'s THIRD RULING, THE ARCHITECT'S WORD: *"Open the token
42
+ // set"*** (`strata/decisions.md` · `D-53`; `D-52`'s rule 3 is its measurement: **`KC-8` CROSSED AS TWO
43
+ // STATUSES** — a constraint refusal whose composition met the floor, and a failed save — **so neither arm was
44
+ // derivable from the log's terminal state**). **THE SENTENCE ABOVE STANDS BYTE-FOR-BYTE AND IS READ THROUGH
45
+ // THIS ONE: `9` MEMBERS AND NO TENTH, ON `14` SITES.** **THE APPENDED MEMBER IS `KC-9`, THE SAVE ARM'S OWN —
46
+ // `ConstraintSaveFailed`** — so a save failure and a constraint refusal cross with DIFFERENT statuses AND each
47
+ // is derivable from the log's terminal state: the save arm's ONE `outcome` entry carries `KC-9` and a constraint
48
+ // refusal's carries `KC-3`/`KC-5`/`KC-6`/`KC-8` (`KC-9`'s own row, `kernel-attempt.mjs`'s save arm, and
49
+ // `kernel-terminal.mjs`'s precedence clause: **the FLOOR DOES NOT OVERWRITE THIS TOKEN, which is what keeps the
50
+ // two arms apart where the save's own composition meets a floor code**).
41
51
  // `fail-surface.md §1.1` ITEM 1 (a) — THE ONE SPELLING, AND IT IS THE TOKEN: a refusal's `status` carries
42
- // `KC-1`…`KC-8` and NOTHING ELSE. The full names (`ConstraintViolated` …) are LABELS: they keep exactly
52
+ // `KC-1`…`KC-9` and NOTHING ELSE. The full names (`ConstraintViolated` …) are LABELS: they keep exactly
43
53
  // one declared use — the `name` column of `§2`'s table, and a citation beside a token — and NO clause
44
54
  // spells a member's VALUE as a name. `status` and `LogEntry.state` therefore read THE SAME TOKEN STRING,
45
- // related by IDENTITY (`§1.1` item 1 (c)). THE CENSUS DOES NOT MOVE: `8` members (`2 + 2 + 2 + 1 + 1`)
46
- // on `13` sites (`1 + 4 + 4 + 3 + 1`).
55
+ // related by IDENTITY (`§1.1` item 1 (c)). THE CENSUS MOVES BY THE RULING AND BY NOTHING ELSE:
56
+ // `8 → 9` members (`2 + 2 + 2 + 1 + 1 + 1`) on `13 → 14` sites (`1 + 4 + 4 + 3 + 1 + 1`, the last being the
57
+ // save arm's own, `kernel-attempt.mjs`).
47
58
  export const KC = {
48
59
  address: 'KC-1', // ConstraintAddressRefused
49
60
  arg: 'KC-2', // ConstraintArgRefused
@@ -53,9 +64,10 @@ export const KC = {
53
64
  repeat: 'KC-6', // ConstraintRepeat
54
65
  attemptOpen: 'KC-7', // ConstraintAttemptOpen
55
66
  rollbackFloor: 'KC-8', // ConstraintRollbackFloor
67
+ saveFailed: 'KC-9', // ConstraintSaveFailed
56
68
  };
57
69
 
58
- /** The eight tokens above, as the ONE list a member's own value is checked against — the kernel's own
70
+ /** The nine tokens above, as the ONE list a member's own value is checked against — the kernel's own
59
71
  * token is NEVER named as a store code (`fail-surface.md §1.1` item 1 (a); `§2`'s `KC-4` row). */
60
72
  export const KC_TOKENS = Object.values(KC);
61
73
 
@@ -217,6 +229,61 @@ export function markBodyThrow(thrown, address, log) {
217
229
  }
218
230
  }
219
231
 
232
+ /// **⟶ ADDED 2026-10-28 — `D-55` ITEM 1, THE ARCHITECT, VERBATIM: *"Include a thrown message in the message
233
+ /// log. Obviously."*** **THE MEASURED DEFECT (`DEF-AK`):** a constraint body that threw its own `Error`
234
+ /// crossed as the rank-`18` `ConstraintBodyThrew` whose `log` carried **only the attempt's `outcome` entry** —
235
+ /// the thrown value's own literal occurred **zero times** in it — while the crossed sentence said *"a
236
+ /// constraint body's throwable crosses as this refusal"*. **The commit's log now carries the throwable's own
237
+ /// form, written into the ONE `outcome` entry the terminal act appends at the frame that caught the body —
238
+ /// before the attempt is disposed of and before the record is handed off** (`kernel-attempt.mjs`'s `evaluate`;
239
+ /// no second entry and no fifth `kind` is minted).
240
+ ///
241
+ /// **WHAT THE FORM IS, AND WHY IT NAMES WHAT IT READ RATHER THAN A CLASS.** The value is the CALLER'S OWN —
242
+ /// it may be an `Error`, a string, a number, an object with no `message`, a `Proxy` — so the spelling is
243
+ /// built from **own data members only** (`ownMember`, `[[GetOwnProperty]]`, `D-35`'s read half) and from the
244
+ /// language's own `typeof`. **NO PROTOTYPE IS CONSULTED**: `instanceof`, `constructor.name` and
245
+ /// `Object.prototype.toString` all walk a chain a caller owns, and a `Proxy`'s trap would RUN here, on a
246
+ /// refusal path. **AND A CLASS IS NEVER CLAIMED**: *"an object whose own `message` member is the string …"*
247
+ /// is true of an `Error` and of a plain object alike, which is what the reading supports.
248
+ ///
249
+ /// **THE FORM IS NAMED AND NOT SILENTLY STRINGIFIED**: a thrown string is *"a string whose text is …"*, a
250
+ /// number *"a number whose value is …"*, a thrown `void 0` *"no value at all"*, an object without a `message`
251
+ /// *"an object carrying no own string `message` member"*. A consumer therefore reads WHAT KIND of value the
252
+ /// body threw beside whatever text it carried.
253
+ ///
254
+ /// **IT IS TOTAL, AND THE ONLY READ THAT COULD RUN CALLER CODE IS THE GUARDED ONE**: this frame invokes
255
+ /// nothing of the caller's — `ownMember` is `[[GetOwnProperty]]` and consults no chain — and the one trap it
256
+ /// can trip (a `Proxy`'s `getOwnPropertyDescriptor`) is inside a `try`, whose arm past it answers a sentence of
257
+ /// this frame's own. **NO `undefined` IS EVER PRINTED** (`native/contract.md §8.2` property (c); the `DEF-Q`
258
+ /// sweep reads every entry's message for it): a thrown `void 0` says *"no value at all"* and a missing member
259
+ /// is named as absent rather than spelled.
260
+ export function throwableForm(thrown) {
261
+ if (thrown === null) return '`null`';
262
+ const kind = typeof thrown;
263
+ if (kind === 'undefined') return 'no value at all (the body threw `void 0`)';
264
+ if (kind === 'string') return `a string whose text is ${JSON.stringify(thrown)}`;
265
+ if (kind === 'number') return `a number whose value is ${String(thrown)}`;
266
+ if (kind === 'boolean') return `a boolean whose value is ${String(thrown)}`;
267
+ if (kind === 'bigint') return `a bigint whose value is ${String(thrown)}`;
268
+ if (kind === 'symbol') return 'a symbol, which carries no text a log can print';
269
+ if (kind === 'function') return 'a function, which carries no message';
270
+ let message;
271
+ let name;
272
+ try {
273
+ message = ownMember(thrown, 'message');
274
+ name = ownMember(thrown, 'name');
275
+ } catch {
276
+ // A `Proxy` whose `getOwnPropertyDescriptor` trap throws cannot be read at all: this frame says so and
277
+ // runs nothing further (`D-35`: the refusal path invokes no caller code it was not already handed).
278
+ return 'an object this frame could not read, so its form is not printed';
279
+ }
280
+ const named = typeof name === 'string' && name.length > 0 ? ` named ${JSON.stringify(name)}` : '';
281
+ if (typeof message === 'string') {
282
+ return `an object${named} whose own \`message\` member is the string ${JSON.stringify(message)}`;
283
+ }
284
+ return `an object${named} carrying no own string \`message\` member`;
285
+ }
286
+
220
287
  /// **WHETHER A VALUE IS A REFUSAL THIS KERNEL BUILT** — the entry's crossing and this kernel's own repair
221
288
  /// arm both ask this and nothing else.
222
289
  ///
@@ -385,6 +452,37 @@ export function sameAddress(left, right) {
385
452
  return true;
386
453
  }
387
454
 
455
+ /**
456
+ * **⟶ ADDED 2026-10-29 — `isAddressPrefix`: THE PASS'S OWN MEMBERSHIP TEST, AND IT IS `D-56`'S SENTENCE IN
457
+ * ONE FUNCTION.** The architect, verbatim: *"A listener/constraint should fire when the descendants of its
458
+ * targeted address node change, because the descendants become part of the object that is read from that
459
+ * address"* (`strata/decisions.md` · `D-56`). **THE GROUND IS SEMANTIC AND NOT A PREFERENCE: the value read at
460
+ * a prefix CONTAINS its subtree, so a change at a descendant CHANGES what a read at the ancestor returns** —
461
+ * and a pair standing at the ancestor therefore **judges an object that moved.**
462
+ *
463
+ * **THE SET, STATED ONCE: a pair is asked when THE ACT'S ADDRESS IS AT OR BELOW THE PAIR'S ADDRESS** — *"the
464
+ * pair's address is a prefix of the act's address (equality included)"* (`D-56`). **So `isAddressPrefix(standing,
465
+ * act)` is `true` exactly where the pair stands AT the act's address OR AT ONE OF ITS ANCESTORS**, and `false`
466
+ * for a pair standing at a DESCENDANT — the unruled direction, which `D-56` requires to be MEASURED rather than
467
+ * implemented, and which this predicate therefore does not reach.
468
+ *
469
+ * **IT IS STRUCTURAL AND SEGMENT-WISE, WITH `string` AND `number` DISTINGUISHED, EXACTLY AS `sameAddress` IS**
470
+ * (`P: docs/pending.md · (M-3)` item (iv)): a pair at `['a', 0]` is NOT asked by an act at `['a', '0']`, so the
471
+ * widening changes WHICH DEPTHS are members and NOT how a segment is compared. **A SEGMENT COUNT OF ZERO IS A
472
+ * PREFIX OF EVERY ADDRESS** — the root pair's own reading, and the same one the trigger set's prefix rule gives.
473
+ */
474
+ export function isAddressPrefix(prefix, address) {
475
+ if (!Array.isArray(prefix) || !Array.isArray(address)) return false;
476
+ if (prefix.length > address.length) return false;
477
+ for (let i = 0; i < prefix.length; i += 1) {
478
+ const a = prefix[i];
479
+ const b = address[i];
480
+ if (typeof a !== typeof b) return false;
481
+ if (a !== b) return false;
482
+ }
483
+ return true;
484
+ }
485
+
388
486
  // ---------------------------------------------------------------------------------------------
389
487
  // THE KERNEL'S OWN CALLER-SIDE STATE — **THE INSTANCE OWNS IT, IN A SLOT ON THE INSTANCE ITSELF**, so
390
488
  // two stores never share a registry, this module retains no store of its own, and NOTHING STANDS AT
@@ -484,6 +582,11 @@ export function kernelFor(store) {
484
582
  live: new Map(),
485
583
  closed: new Map(),
486
584
  attempts: 0,
585
+ // **⟶ AND THE COMMIT'S FRAME, 2026-10-28 (`D-53`)**: the stack of attempts whose commit the kernel is, at
586
+ // this instant, invoking caller code inside. `K-2` reads its top and joins that attempt's list; a write
587
+ // reached with no frame standing OPENS a commit and starts its own list. It is the instance's own, exactly
588
+ // as `live`/`closed` are (`D-30`), so no two stores share a frame and no module-level stack exists.
589
+ frames: [],
487
590
  store,
488
591
  };
489
592
  // **THE SLOT IS DEFINED AND THE RECORD IS PUBLISHED IN THE SAME ACT** (`Object.defineProperty`, exactly as
@@ -613,16 +716,129 @@ export function ownMember(value, name) {
613
716
  return descriptor.value;
614
717
  }
615
718
 
719
+ // ---------------------------------------------------------------------------------------------
720
+ // **⟶ THE MESSAGE LOG BELONGS TO THE COMMIT, NOT TO THE ATTEMPT — `D-53`, 2026-10-28.**
721
+ //
722
+ // **THE ARCHITECT, VERBATIM: *"If it's part of the same commit, then it uses the same message log. If it opens a
723
+ // new commit, it uses the new commit's message log. Constraints/Repairs should always use the same commit.
724
+ // Listeners should open a new commit, because the triggering commit already passed constraints"***
725
+ // (`strata/decisions.md` · `D-53`). **A COMMIT IS THE UNIT OF WORK AND AN ATTEMPT IS THE CONSTRAINT-JUDGED WRITE
726
+ // INSIDE IT**, so a commit that holds several attempts — a check, its repair, the repair's own write, the re-run
727
+ // — holds ONE list, and every one of those attempts appends to it: **`attempt.log` IS the commit's list, carried
728
+ // by reference, and it is not a copy of anybody's.**
729
+ //
730
+ // **WHAT THAT DISSOLVES, SAID BECAUSE IT WAS ASKED AS A QUESTION.** `D-52`'s rule 2 asked where a nested
731
+ // attempt's report is merged into its host's, and how a merged entry keeps *"its own attempt identity"* when
732
+ // `seq` is *"the ordinal of the list it stands in"* and the six declared members leave no room for a seventh.
733
+ // **THERE WAS NEVER A MERGE TO PERFORM: a constraint body's write is part of the same commit, so its entries
734
+ // ARE this list's entries, the crossed report IS this list, no entry is renumbered and `seq` keeps its
735
+ // declaration because there is ONE list.** `DEF-AK`'s *"one inner refusal, two hosts"* is the measurement of it.
736
+ //
737
+ // **THE KEY IS A `Symbol.for` SPELLING ON THE COMMIT'S OWN RECORD, AND BOTH SIDES SPELL IT** (`A117` (D6)): the
738
+ // entry's terminal arm attaches this list to a failed save's crossing, and it holds the `CommitRecord` and
739
+ // nothing else — so the list travels ON the record under a symbol key that `Object.keys` and
740
+ // `Object.getOwnPropertyNames` read past, and the record keeps its FOUR declared members.
741
+ export const COMMIT_LOG = Symbol.for('provident-storeroom.commit-log');
742
+
743
+ // ---------------------------------------------------------------------------------------------
744
+ // **⟶ THE HANDOFF'S OWN SEAL — `D-55` ITEM 6, `DEF-AO`, ADDED 2026-10-28.** The architect, verbatim:
745
+ // *"Attempts don't log. This is what the commits are for. The commits have their log handed off to the client
746
+ // and are dropped after closing out."* **SO THE RECORD IS HANDED OFF ONCE, AT THE END OF THE COMMIT'S OWN
747
+ // PROCESSING, AND A RECORD THAT CHANGES AFTER THE HANDOFF IS NOT A RECORD.**
748
+ //
749
+ // **WHAT WAS MEASURED BEFORE THIS ACT EXISTED.** The drain amends the REPORT a pass already closed — it sets
750
+ // `outcome` and appends the refusal's entry to the report's list — and the list it appends to is THE COMMIT'S
751
+ // OWN, held by reference by every attempt of the commit, the crossed refusals included. A consumer that took
752
+ // the closed report (or a refusal carrying that list) before the drain therefore held a record that moved
753
+ // under it. **FREEZING AT THE SEAL MAKES THE HANDED-OFF RECORD FINAL**: the drain's amendment happens BEFORE
754
+ // this call (it is part of closing out), and nothing after it can append, renumber or re-member the list.
755
+ //
756
+ // **WHAT IT FREEZES, AND WHY THE LOG'S SEAL IS THE COMMIT'S.** `D-53` makes the message log THE COMMIT'S: an
757
+ // attempt's `log` is that one list, carried by reference, and every attempt of the commit appends to it — a
758
+ // constraint body's nested write, a repair's write, the re-run. **SO THE REPORT IS SEALED AT EVERY ATTEMPT'S
759
+ // HANDOFF AND THE LIST IS SEALED AT THE HANDOFF OF THE ATTEMPT WHOSE SCOPE OWNS IT** (the one whose commit is
760
+ // not an enclosing invocation's list, read off `commitLogInFlight`): freezing the list at a NESTED attempt's
761
+ // handoff would stop the enclosing attempt's own `append` and turn a lawful write into a raw host `TypeError`
762
+ // — measured, and the reason the `shared` argument exists. **THE LIST IS SEALED WITH ITS COMMIT**, which is
763
+ // what *"the commits have their log handed off to the client and are dropped after closing out"* says.
764
+ //
765
+ // **IT FREEZES TWO THINGS AND ONLY TWO**: the report's `log` array (where this attempt's scope owns it) and
766
+ // the report itself. **NO MEMBER MOVES, NO MEMBER IS ADDED, AND THE LIST'S IDENTITY IS UNCHANGED** — `D-53`'s
767
+ // *"the crossed report IS that list"* is a statement about WHICH array crosses, and the same array crosses
768
+ // (frozen): `K-5`, `K-4` and every refusal built from it read the same entries at the same `seq` ordinals.
769
+ // **A PASSING ATTEMPT'S REPORT IS SEALED TOO** — its log is handed off exactly once and drops with the commit.
770
+ export function sealHandoff(report, shared) {
771
+ if (report === null || typeof report !== 'object') return report;
772
+ // THE REPORT'S OWN `log` MEMBER, READ AS A MEMBER OF THE OBJECT THIS FRAME BUILT (`kernel-terminal.mjs`
773
+ // places the commit's list there and nothing else): an absent or non-array member is not this act's, and
774
+ // `refusalLog`'s own rule (`surface.md §2.5` item 4) is that a `log` is a list on every arm.
775
+ const log = report.log;
776
+ if (shared !== true && Array.isArray(log) === true) Object.freeze(log);
777
+ Object.freeze(report);
778
+ return report;
779
+ }
780
+
781
+ // **THE COMMIT'S FRAME — THE ONE STRUCTURE THAT MAKES "THE SAME COMMIT" A FACT RATHER THAN A CONVENTION.** It is
782
+ // a per-instance stack on the kernel record (`kernel.frames`, created by `kernelFor`), pushed by the kernel
783
+ // around each invocation IT makes of caller code that belongs to an attempt — a constraint body at a landing,
784
+ // at a registration's evaluation, at its re-run; a repair body — and popped in a `finally`.
785
+ //
786
+ // **`K-2` READS IT AND NOTHING ELSE DECIDES THE LOG**: a write the kernel reaches from inside such an
787
+ // invocation JOINS the innermost frame's list; a write reached from anywhere else — a caller's own top-level
788
+ // write, and A LISTENER BODY'S WRITE, which the ENTRY invokes at the terminal arm with no frame of this
789
+ // kernel's standing — OPENS a commit and therefore starts a list of its own (`D-53`: *"Listeners should open a new
790
+ // commit, because the triggering commit already passed constraints"* — what the listener saw was already
791
+ // approved, so what it does now is a NEW QUESTION and must not inherit the old verdict).
792
+ export function enterCommitFrame(kernel, log) {
793
+ appendOwn(kernel.frames, { log });
794
+ }
795
+
796
+ /** The pop, and it is a local index loop for the reason every other list surgery here is (`contract.md §2.1`'s
797
+ * clause of 2026-10-24 on `F7-1`): no `Array.prototype.pop` is read on a path a caller's prototype can reach. */
798
+ export function leaveCommitFrame(kernel) {
799
+ const kept = [];
800
+ for (let i = 0; i + 1 < kernel.frames.length; i += 1) appendOwn(kept, kernel.frames[i]);
801
+ kernel.frames = kept;
802
+ }
803
+
804
+ /** **THE LOG OF THE COMMIT THE KERNEL IS INSIDE, OR `null` WHERE IT IS INSIDE NONE** — the one read `K-2` takes
805
+ * to decide whether this write joins a commit or opens one. */
806
+ export function commitLogInFlight(kernel) {
807
+ return kernel.frames.length === 0 ? null : kernel.frames[kernel.frames.length - 1].log;
808
+ }
809
+
616
810
  // ---------------------------------------------------------------------------------------------
617
811
  // THE MESSAGE LOG (`kernel-surface.md §2.5` item 1; `PAR-C-4`). SIX MEMBERS, `seq` ASCENDING FROM
618
812
  // `1`, ONE KIND PER ENTRY: `constraint-check` | `repair-run` | `commit` | `outcome`.
619
- // ---------------------------------------------------------------------------------------------
620
- export function append(attempt, kind, id, opId, message, state) {
813
+ //
814
+ // **⟶ AND THE ENTRY IS NAMED BY AN ITERATION AND NOT BY TWO LOOSE IDENTITIES — 2026-10-28, `D-53`'s FIRST
815
+ // RULING: *"Both combined to identify which constraint iteration it is on"*.** **THE ARCHITECT'S ANSWER TO
816
+ // `D-52`'s rule 1, read with `D-53`'s own gloss: *"an entry carries the PAIR identity and the OP identity
817
+ // together, and the PAIR identifies WHICH CONSTRAINT of that pair's evaluations is being spoken about … The two
818
+ // columns are not alternatives — they are one identity"*.** **THE SUBJECT OF AN ENTRY IS THE ITERATION, AND AN
819
+ // ITERATION IS A PAIR AT AN OP** — which is why no single acted-upon object could supply both, and why the old
820
+ // `(attempt, kind, id, opId, message, state)` signature (two loose strings in two adjacent positions) could be
821
+ // fed one value where the other belonged.
822
+ //
823
+ // **SO THE SIGNATURE TAKES THE ITERATION AS ONE OBJECT AT ONE POSITION, and reads its two members as its OWN
824
+ // data members** (`D-35`'s read half: `[[GetOwnProperty]]` and never a `[[Get]]` that walks a caller's chain):
825
+ // * `iteration.pair` — the registry row (`{ addresses, id, constraint }`) the entry speaks about, or `null`
826
+ // where the kind names none (the `commit` entry names no pair, and the `outcome` entry names one only where
827
+ // the arm has one);
828
+ // * `iteration.op` — the journal op the iteration is judged at, or `null` where the kind names none.
829
+ // **`id` IS READ OFF THE PAIR AND `opId` OFF THE OP**: a caller cannot put an id in the op's place, because
830
+ // there is no place for one.
831
+ export function append(attempt, kind, iteration, message, state) {
832
+ const held = iteration !== null && typeof iteration === 'object' ? iteration : null;
833
+ const pair = held === null ? undefined : ownMember(held, 'pair');
834
+ const op = held === null ? undefined : ownMember(held, 'op');
835
+ const id = pair === undefined || pair === null || typeof pair.id !== 'string' ? null : pair.id;
836
+ const opId = op === undefined || op === null || typeof op.opId !== 'string' ? null : op.opId;
621
837
  appendOwn(attempt.log, {
622
838
  seq: attempt.log.length + 1,
623
839
  kind,
624
- id: id === undefined ? null : id,
625
- opId: opId === undefined ? null : opId,
840
+ id,
841
+ opId,
626
842
  message,
627
843
  state: state === undefined ? null : state,
628
844
  });
@@ -759,31 +975,57 @@ function opSpelling(value) {
759
975
  * replacement for `byAddress`'s index, declared at `contract.md §2.8` item 6 (`HL-DD-6`) and
760
976
  * `kernel-selection.md §3` item 1.
761
977
  *
762
- * **THE SET IS THE PAIRS WHOSE OWN ADDRESS IS THE ATTEMPT'S — selected by an EXACT-ADDRESS match and NEVER
763
- * widened to `triggered`'s prefix set** (`P: docs/pending.md · (AA-1)`, verbatim: *"the pass selects the rows
978
+ * **⟶ THE PREDICATE IS WIDENED FROM EXACT ADDRESS TO PREFIX, 2026-10-29 — `D-56`, AND THE CLAUSE BELOW STANDS
979
+ * BYTE-FOR-BYTE AS THE RULE THIS ONE SUPERSEDES.** `RCA-8(g)` ANNOTATE-BESIDE: **the paragraph that stood here
980
+ * read** *"THE SET IS THE PAIRS WHOSE OWN ADDRESS IS THE ATTEMPT'S — selected by an EXACT-ADDRESS match and NEVER
981
+ * widened to `triggered`'s prefix set"* (`P: docs/pending.md · (AA-1)`, verbatim: *"the pass selects the rows
764
982
  * whose address IS the triggering address, which PRESERVES the EXACT-ADDRESS RULE rather than widening it to
765
- * `triggered`'s prefix set"*; `contract.md §2.8` item 1).
983
+ * `triggered`'s prefix set"*; `contract.md §2.8` item 1), **and the architect has superseded it**, verbatim:
984
+ * *"A listener/constraint should fire when the descendants of its targeted address node change, because the
985
+ * descendants become part of the object that is read from that address"* (`strata/decisions.md` · `D-56`).
986
+ * **THE TWO CLAUSES `D-56` NAMES BY NUMBER ARE `HL-8` ITEM 1'S OWN SENTENCE AND
987
+ * `attempt-lifecycle.md §2.4` ITEM 5 (iv)'s EXACT-ADDRESS RULE — both superseded, both annotated in place, and
988
+ * neither rewritten.**
989
+ *
990
+ * **THE SET IS NOW THE PAIRS WHOSE OWN ADDRESS IS A PREFIX OF THE ATTEMPT'S — equality included — WHICH IS
991
+ * `isAddressPrefix(standing, address)` AND IS `D-56`'s ONE SENTENCE**: a pair at `['tabs']` IS asked for a
992
+ * delete at `['tabs','landingPage']`, a pair at `['tabs','landingPage']` IS asked for that same delete, and a
993
+ * pair at `['tabs','landingPage','x']` is NOT asked because the act did not change it (**the unruled direction,
994
+ * measured and not implemented** — `D-56`'s own terms). **THE REASON IS THE READ:** the value read at a prefix
995
+ * contains its subtree, so a descendant's change changes what the ancestor's read returns, and the ancestor pair
996
+ * **judges an object that moved.**
766
997
  *
767
- * **IT IS A SCAN, AND THE COST IS PRICED AND ACCEPTED** (`P: docs/pending.md · (AA-1)`, verbatim: *"selecting
768
- * rows by address is a scan over `kernel.pairs` … THAT GROUND WAS WRONG AND IS WITHDRAWN: the store names the
769
- * ADDRESS, and the kernel selects"*).
998
+ * **IT IS STILL A SCAN, AND THE COST IS STILL PRICED AND ACCEPTED** (`P: docs/pending.md · (AA-1)`, verbatim:
999
+ * *"selecting rows by address is a scan over `kernel.pairs` … THAT GROUND WAS WRONG AND IS WITHDRAWN: the store
1000
+ * names the ADDRESS, and the kernel selects"*) — **and `D-56` re-prices it not at all**: the scan reads one
1001
+ * prefix test per standing address instead of one equality test per standing address, on the same rows.
770
1002
  *
771
- * **AND THE RESULT IS MATERIALISED ONCE, WHICH IS WHAT IMPLEMENTS `CX-12`'s *"the pass's evaluated set is
772
- * FIXED WHEN THE PASS BEGINS"*** (`P: docs/pending.md · R-B3`'s snapshot clause; `contract.md §2.8` item 6):
773
- * the returned list is a fresh array, so a `K-1` that returns DURING the pass enters the NEXT pass's set and
774
- * no earlier one, and a pair PURGED mid-pass is still evaluated by the pass that began with it
775
- * (`kernel-selection.md §3` item 5).
1003
+ * **AND THE RESULT IS STILL MATERIALISED ONCE, WHICH IS WHAT IMPLEMENTS `CX-12`'s *"the pass's evaluated set is
1004
+ * FIXED WHEN THE PASS BEGINS"*** (`P: docs/pending.md · R-B3`'s snapshot clause; `contract.md §2.8` item 6;
1005
+ * `D-56`'s third deferrer, which changes WHAT is materialised and not WHEN): the returned list is a fresh array,
1006
+ * so a `K-1` that returns DURING the pass enters the NEXT pass's set and no earlier one, and a pair PURGED
1007
+ * mid-pass is still evaluated by the pass that began with it (`kernel-selection.md §3` item 5). **THE PASS READS
1008
+ * THIS LIST AND NEVER THE REGISTRY**, and **`D-56` says nothing that moves that**: the ancestors are members of
1009
+ * the SAME one snapshot, taken at the SAME instant.
776
1010
  *
777
- * **THE ORDER IS THE PAIR'S FIRST-REGISTRATION ORDER AT THE ADDRESS** — `attempt-lifecycle.md:193`'s *"clause
778
- * (ii)'s first-registration order"* — **and `kernel.pairs`' own `Map` iteration supplies it**, which is the
779
- * order the ruling says is preserved *"by the store's own returned order, not by an index's append order"*
780
- * (`P: docs/pending.md · M-3`, its closing sentence; `contract.md §2.8` item 6's `HL-DD-6`).
1011
+ * **THE ORDER IS STILL THE PAIR'S FIRST-REGISTRATION ORDER — `HL-8` ITEM 6's RULE SURVIVES `D-56` — AND THE
1012
+ * ANCESTORS INTERLEAVE INTO THAT ONE ORDER RATHER THAN FORMING A SECOND.** `attempt-lifecycle.md:193`'s *"clause
1013
+ * (ii)'s first-registration order"* **is supplied by `kernel.pairs`' own `Map` iteration**, which is the order
1014
+ * the ruling says is preserved *"by the store's own returned order, not by an index's append order"*
1015
+ * (`P: docs/pending.md · M-3`, its closing sentence; `contract.md §2.8` item 6's `HL-DD-6`). **THE WIDENING ADDS
1016
+ * NO ITERATION AND NO SORT**: `D-39`'s registration sequence is the `Map`'s insertion order over the pair rows,
1017
+ * the one loop below walks it once, and **a row is admitted or not by its own `addresses` member** — so an
1018
+ * ancestor registered FIRST is asked FIRST, an ancestor registered THIRD is asked THIRD, **interleaved with the
1019
+ * act's own pairs by the one sequence both stand in.** There is no "ancestors first", no "ancestors last", and
1020
+ * no second list: **one order, one list, one snapshot.** **EACH ELEMENT IS EVALUATED AT MOST ONCE** — the `break`
1021
+ * below is that rule, so a pair standing at N addresses of which two are prefixes of the act is reached once
1022
+ * (`contract.md §2.6` item 5).
781
1023
  */
782
1024
  export function pairsAtAddress(kernel, address) {
783
1025
  const out = [];
784
1026
  for (const [key, pair] of kernel.pairs) {
785
1027
  for (const standing of pair.addresses) {
786
- if (sameAddress(standing, address)) {
1028
+ if (isAddressPrefix(standing, address)) {
787
1029
  appendOwn(out, key);
788
1030
  break;
789
1031
  }
@@ -1,5 +1,5 @@
1
1
  // `D-13` (`docs/L1/kernel-subdivision.md` §1 part `9b`) — the registration act, its evaluation, and the registry's removal: bytes moved from `bindings/js/src/constraint-kernel.mjs`; no logic changed.
2
- import { KC, KERNEL_REFUSAL, markBodyThrow, refuse, inDomainAddress, inDomainId, pairRowAt, sameAddress, kernelFor, append, appendOwn, landedOp, pairsAtAddress, externalCall } from './kernel-foundations.mjs';
2
+ import { KC, KERNEL_REFUSAL, markBodyThrow, refuse, inDomainAddress, inDomainId, pairRowAt, sameAddress, kernelFor, append, appendOwn, landedOp, pairsAtAddress, enterCommitFrame, leaveCommitFrame } from './kernel-foundations.mjs';
3
3
 
4
4
  /// **⟶ ADDED 2026-10-27 — `D-46`'s NAME ON A REPAIR INVOCATION'S OWN ENTRY, SPELLED HERE AS
5
5
  /// `kernel-attempt.mjs` SPELLS IT.** **THE RULING, THE ARCHITECT, VERBATIM: *"A constraint's repair needs to stay
@@ -31,6 +31,19 @@ function repairInCommit(store, commit, run) {
31
31
  if (typeof channel === 'function') return channel(run, commit);
32
32
  return run();
33
33
  }
34
+
35
+ /// **⟶ AND THE CONSTRAINT BODY'S INVOCATION AT THE REGISTRATION INSTANT NAMES THE REGISTRATION'S OWN COMMIT —
36
+ /// `D-53`, 2026-10-28: *"Constraints/Repairs should always use the same commit"*.** `D-46` named the commit for
37
+ /// the repair alone; **`D-53` states the invariant both kinds of caller-authored body obey, and a CONSTRAINT BODY
38
+ /// IS A CHECK: the commit the registering verb opened (`addConstraint`'s bracket, `docs/specs/storeroom-js/
39
+ /// surface.md §4.1`) is the commit it is asked inside, so a verb it calls JOINS that window and never opens a
40
+ /// second.** **A LISTENER BODY STILL NAMES NOTHING**: the entry invokes it at the terminal arm, where *"the
41
+ /// triggering commit already passed constraints"*, so what it does is a new question (`D-53`).
42
+ function constraintInCommit(store, commit, run) {
43
+ const channel = store !== null && typeof store === 'object' ? store[EXTERNAL_CALL] : undefined;
44
+ if (typeof channel === 'function') return channel(run, commit);
45
+ return run();
46
+ }
34
47
  // ---------------------------------------------------------------------------------------------
35
48
  // `K-1` `register(store, address, id, constraint) -> Handle`
36
49
  // ---------------------------------------------------------------------------------------------
@@ -247,6 +260,13 @@ function withdrawRegistration(kernel, address, id, fresh, home) {
247
260
  * invocation below is NOT wrapped — a throwing body reaches the caller as the very value it threw.
248
261
  */
249
262
  function registrationEvaluation(kernel, store, address, id, constraint) {
263
+ // **⟶ THE REGISTRATION'S OWN COMMIT AND ITS OWN ONE LIST — `D-53`, 2026-10-28.** A registration is a commit
264
+ // (`addConstraint`'s and `constraintOn`'s brackets open one, and `3-bis` (a) already declares *"the
265
+ // `CommitRecord` of the registration act"*), so the record is built FIRST and the list beside it: **a verb the
266
+ // body calls is part of this commit, its entries are this list's entries, and the synthetic attempt a repair
267
+ // needs (state `3`) is an attempt of this same commit** — it appends to this list and mints no second one.
268
+ const commitRecord = { attempt: null, op: null, triggered: [], pending: [] };
269
+ const commitLog = [];
250
270
  // **⟶ AMENDED 2026-10-26 (`DEF-S`; the architect's ruling, verbatim: *"Expand the returned error set for
251
271
  // both S/N"*): THE BODY'S THROW IS MARKED AT THE FRAME THAT CAUGHT IT, and no attempt stands at this
252
272
  // instant (`null`), so the mark carries the `[]` its clause declares. **THE THROWABLE'S IDENTITY IS
@@ -255,16 +275,23 @@ function registrationEvaluation(kernel, store, address, id, constraint) {
255
275
  // unmarked throw is still the caller's own (`markBodyThrow`, `kernel-foundations.mjs`).
256
276
  let outcome;
257
277
  const context = registrationContext(store, address, null);
278
+ // **⟶ AND THE BODY IS INVOKED WITH THE REGISTRATION'S COMMIT NAMED — `D-53`, 2026-10-28.** The clause this
279
+ // replaces read *"the body is invoked with nothing named … so a verb it calls OPENS ITS OWN COMMIT"*
280
+ // (`D-45`, `D-46` item `1`), and **`D-53`'s invariant reverses it for constraints: *"Constraints/Repairs should
281
+ // always use the same commit"*** — a constraint body is asked, and the check it is asked by is this commit.
282
+ // **THE FRAME IS PUSHED FOR EXACTLY THE INVOCATION AND POPPED IN A `finally`**, so a nested write appends to this
283
+ // registration's list and a throwable leaves no frame standing.
284
+ // **THE CONTEXT IS BUILT ABOVE THE INVOCATION** (`kernel-surface.md §2.3` item 1), so the store read the context
285
+ // owes is this verb's own act and not the body's.
286
+ enterCommitFrame(kernel, commitLog);
258
287
  try {
259
- // **⟶ AND THE BODY IS INVOKED WITH NOTHING NAMED, 2026-10-27 (`D-45`, `D-46` item `1`).** A constraint body
260
- // is EXTERNALLY-SUPPLIED FUNCTION code entering the surface **and it has no commit in hand to name** — its
261
- // five-member context carries none (`kernel-surface.md §2.3` item 1) — so **a verb it calls OPENS ITS OWN
262
- // COMMIT** rather than joining the registering verb's (*"It should be opening a new commit by default of
263
- // entering at the surface"*). **THE CONTEXT IS BUILT ABOVE THE INVOCATION**, so the store read the context
264
- // owes is this verb's own act and not the body's.
265
- outcome = externalCall(store, () => constraint(context));
288
+ outcome = constraintInCommit(store, commitRecord, () => constraint(context));
266
289
  } catch (body) {
267
- throw markBodyThrow(body, address, null);
290
+ // **AND THE MARK CARRIES THIS COMMIT'S LIST** (`markBodyThrow`'s second slot): a registration-time body that
291
+ // threw after its own verbs appended carries those entries to the crossing, where `[]` used to be.
292
+ throw markBodyThrow(body, address, commitLog);
293
+ } finally {
294
+ leaveCommitFrame(kernel);
268
295
  }
269
296
  // THE OUTCOME IS `PAR-C-5`'S CLOSED THREE-ARM SET, read as the landing pass reads it: `true` is the pass
270
297
  // arm; an object carrying `message` is the violation arm (its `repair`, where present, the repair the
@@ -277,9 +304,10 @@ function registrationEvaluation(kernel, store, address, id, constraint) {
277
304
  : null;
278
305
  // STATE 2 — THE STATE VIOLATES IT AND NO REPAIR IS REGISTERED. Nothing is attempted and no attempt is
279
306
  // opened; the refusal carries the store's code and the row's own slots.
280
- if (repair === null) return registrationRefusal(address, id, []);
281
- // STATE 3 — THE REPAIR IS ATTEMPTED, on the synthetic attempt `3-bis` (a) declares.
282
- return registrationAttempt(kernel, store, { address, id, repair });
307
+ if (repair === null) return registrationRefusal(address, id, commitLog);
308
+ // STATE 3 — THE REPAIR IS ATTEMPTED, on the synthetic attempt `3-bis` (a) declares — **the attempt of THIS
309
+ // commit, holding THIS list by reference** (`D-53`).
310
+ return registrationAttempt(kernel, store, { address, id, repair, log: commitLog, commit: commitRecord });
283
311
  }
284
312
 
285
313
  /// **THE FIVE-MEMBER EVALUATION CONTEXT** (`kernel-surface.md §2.3` item 1's clause of 2026-10-17), built
@@ -392,11 +420,14 @@ function registrationAttempt(kernel, store, pair) {
392
420
  tier: null,
393
421
  value: undefined,
394
422
  // `commit` — THE REGISTRATION ACT'S OWN `CommitRecord` (`3-bis` (a)): this kernel's four members, with
395
- // no write's op and no write's triggered set, because a registration lands no `K-2` of its own.
396
- commit: { attempt: null, op: null, triggered: [], pending: [] },
423
+ // no write's op and no write's triggered set, because a registration lands no `K-2` of its own. **⟶ IT IS THE
424
+ // RECORD THE EVALUATION ALREADY BUILT AND NAMED (`D-53`), not a second one**, so the commit the constraint
425
+ // body was asked inside and the commit its repair joins are ONE.
426
+ commit: pair.commit,
397
427
  write: null,
398
428
  ops: [],
399
- log: [],
429
+ // **AND THE LIST IS THAT COMMIT'S OWN, BY REFERENCE** (`D-53`).
430
+ log: pair.log,
400
431
  ran: [],
401
432
  appliedRepairs: [],
402
433
  rollback: [],
@@ -411,9 +442,12 @@ function registrationAttempt(kernel, store, pair) {
411
442
  // pair is appended to BOTH `ran` and `appliedRepairs` (`3-bis` (b): the pair JOINS both members — of the
412
443
  // SYNTHETIC attempt, and of no other). **THE PAIR ENTERS `ran` BEFORE THE REPAIR RUNS**, which is that
413
444
  // arm's own ordering and is what makes the repeat arms reachable at the re-run below.
414
- append(attempt, 'constraint-check', pair.id, null, `the evaluation of ${pair.id}`);
445
+ // **⟶ AND THE TWO ENTRIES ARE NAMED BY THE ITERATION — `D-53`'s first ruling.** This arm names no OP (`3-bis`
446
+ // (a)'s synthetic attempt has landed nothing when these are appended): the pair stands, the op is the declared
447
+ // `null` absence, and the `repair-run` entry's `opId` is rewritten below where the repair lands one.
448
+ append(attempt, 'constraint-check', { pair, op: null }, `the evaluation of ${pair.id}`);
415
449
  appendOwn(attempt.ran, key);
416
- append(attempt, 'repair-run', pair.id, null, 'the pair\'s repair was attempted at registration');
450
+ append(attempt, 'repair-run', { pair, op: null }, 'the pair\'s repair was attempted at registration');
417
451
 
418
452
  let result;
419
453
  const repairContext = registrationRepairContext(store, address, pair.id, attempt.commit);
@@ -428,6 +462,8 @@ function registrationAttempt(kernel, store, pair) {
428
462
  // that context at `write`'s fourth position names the same commit by its own hand, and the two spellings
429
463
  // cannot disagree (the entry's position wins where it is supplied, and the invocation's name is the fallback
430
464
  // where it is not — `state.mjs`'s `openCommit`).
465
+ // **AND THE FRAME STANDS FOR THIS INVOCATION TOO (`D-53`)**: the repair's verbs append to this commit's list.
466
+ enterCommitFrame(kernel, attempt.log);
431
467
  result = repairInCommit(store, attempt.commit, () => pair.repair(repairContext));
432
468
  } catch (body) {
433
469
  // A REPAIR WHOSE BODY THROWS IS A FAILED REPAIR AND THE REGISTRATION IS REFUSED — the caller's act is
@@ -437,6 +473,8 @@ function registrationAttempt(kernel, store, pair) {
437
473
  // that expected the body's own value out of `addConstraint` has read a route no clause declares.
438
474
  closeRegistrationAttempt(kernel, attempt);
439
475
  return registrationRefusal(address, pair.id, attempt.log);
476
+ } finally {
477
+ leaveCommitFrame(kernel);
440
478
  }
441
479
 
442
480
  const ok = result !== null && typeof result === 'object' && result.ok === true;
@@ -483,11 +521,18 @@ function registrationRerun(kernel, attempt, produced) {
483
521
  // **THE ADDRESS IS IN HAND ONE FRAME ABOVE the `byAddress.get` call that stood here**, and the store→kernel
484
522
  // channel is `record` alone (`A117` (D6) forbids a fifth member).
485
523
  //
486
- // **AND THE SELECTION IS AN EXACT-ADDRESS MATCH, NEVER A PREFIX ONE**: the pass's set is the pairs WHOSE OWN
487
- // ADDRESS IS THE ATTEMPT'S, which preserves `attempt-lifecycle.md §2.4` item 5 (iv)'s exact-address rule
488
- // rather than widening it to the store's prefix-selected `triggered` (`P: docs/pending.md · (AA-1)`;
489
- // `contract.md §2.8` item 1). **EACH ELEMENT IS EVALUATED AT MOST ONCE**, so a pair standing at N addresses
490
- // whose two addresses both match is reached once (`contract.md §2.6` item 5).
524
+ // **AND THE SELECTION IS A PREFIX MATCH ON A PAIR'S OWN ADDRESS, AS OF 2026-10-29 (`D-56`).** `RCA-8(g)`
525
+ // ANNOTATE-BESIDE: **the sentence that stood here read** *"the pass's set is the pairs WHOSE OWN ADDRESS IS THE
526
+ // ATTEMPT'S, which preserves `attempt-lifecycle.md §2.4` item 5 (iv)'s exact-address rule rather than widening
527
+ // it to the store's prefix-selected `triggered`"* (`P: docs/pending.md · (AA-1)`; `contract.md §2.8` item 1) —
528
+ // **and `D-56` supersedes that rule**: *"A listener/constraint should fire when the descendants of its targeted
529
+ // address node change, because the descendants become part of the object that is read from that address"*.
530
+ // **SO THE SET HERE IS `pairsAtAddress`'s AND MOVES WITH IT** — the pairs whose own address is a PREFIX of the
531
+ // attempt's, equality included — because a registration's re-run is not a second kind of pass and mints no
532
+ // second membership rule. **THE WIDENING IS THE FUNCTION'S AND NOT THIS MODULE'S**: the call below is byte-for-
533
+ // byte what it was, and the registration instant inherits `D-56` by CALLING the one selection function.
534
+ // **EACH ELEMENT IS EVALUATED AT MOST ONCE**, so a pair standing at N addresses whose two addresses are both
535
+ // prefixes of the act is reached once (`contract.md §2.6` item 5).
491
536
  const list = pairsAtAddress(kernel, attempt.address);
492
537
  for (const key of list) {
493
538
  const pair = kernel.pairs.get(key);
@@ -500,17 +545,26 @@ function registrationRerun(kernel, attempt, produced) {
500
545
  attempt: attempt.ref,
501
546
  op: produced,
502
547
  };
548
+ // **⟶ AND THE RE-RUN'S BODIES ARE OF THIS COMMIT TOO — `D-53`.** The clause this replaces read *"this body is
549
+ // invoked with nothing named too … the third of the three that invoke a caller-authored constraint body"*
550
+ // (`D-45`): **the three frames name the attempt's commit now, and the registration's re-run names the same
551
+ // one its repair joined.** The context is built ABOVE the invocation.
552
+ enterCommitFrame(kernel, attempt.log);
503
553
  try {
504
- // **⟶ AND THIS BODY IS INVOKED WITH NOTHING NAMED TOO, 2026-10-27 (`D-45`, `D-46` item `1`)** — the
505
- // re-run's frame is the third of the three that invoke a caller-authored constraint body, and it names no
506
- // commit exactly as the registration evaluation and the landing's pass do. The context is built ABOVE the
507
- // invocation.
508
- outcome = externalCall(attempt.store, () => pair.constraint(context));
554
+ outcome = constraintInCommit(attempt.store, attempt.commit, () => pair.constraint(context));
509
555
  } catch (body) {
510
556
  // **⟶ ADDED 2026-10-26 (`DEF-S`, the same ruling): a body thrown at the synthetic attempt's re-run is
511
557
  // marked at this frame too — the mark carries the attempt's own log and address, and the entry's
512
558
  // crossing converts a marked value only** (`markBodyThrow`, `kernel-foundations.mjs`).
559
+ // **⟶ AND NEITHER THIS FRAME NOR THE REGISTRATION-TIME EVALUATION ABOVE APPENDS A THROWABLE ENTRY, 2026-10-28
560
+ // — `DEF-AK`'s RECORDED ARM IS THE LANDING'S.** The re-run appends no `constraint-check` entry for the
561
+ // evaluation it threw in (the pair's own function ran at the registration instant), and no `outcome`
562
+ // entry stands because this throwable propagates before any terminal act — so there is no entry of the
563
+ // closed four this frame could honestly extend, and no fifth `kind` is minted for it. The registration
564
+ // instant's `log` is `surface.md §2.5` item 4's declared `[]` and stays it (`DEF-S`'s own cell reads it).
513
565
  throw markBodyThrow(body, attempt.address, attempt.log);
566
+ } finally {
567
+ leaveCommitFrame(kernel);
514
568
  }
515
569
  if (outcome === true) continue;
516
570
  attempt.terminalArm = attempt.appliedRepairs.includes(key) ? 'KC-5' : 'KC-6';
@@ -49,14 +49,27 @@ export function terminal(kernel, attempt, status, message) {
49
49
  // THE COMPOSITION'S OWN FLOOR, TAKEN OFF THE MEMBER IT WAS DECIDED ON (`composition-and-states.md
50
50
  // §2.6` item 5 (b)): a still-owed op's call meeting either floor code makes the arm `KC-8` WHATEVER
51
51
  // the pass's own arm was, and nothing is restored (item 6 (a), (c)).
52
- const finalStatus = floor === null ? status : KC.rollbackFloor;
53
- const finalMessage = floor === null
54
- ? message
55
- : `the per-op rollback met the store's own ${floor.code} at ${floor.opId}`;
52
+ //
53
+ // **⟶ AND THE SAVE ARM'S OWN TOKEN IS NOT OVERWRITTEN BY IT — `D-53`'s THIRD RULING, 2026-10-28.** **THE FLOOR
54
+ // BELONGS TO THE COMPOSITION AND `KC-8` KEEPS ITS DECLARED MEANING; what it may not do is make a SAVE FAILURE
55
+ // and a CONSTRAINT REFUSAL end at the same token**, which is the contradiction `D-52` measured (*"`KC-8` crosses
56
+ // as both statuses"*). **SO THE PRECEDENCE IS ONE SENTENCE: THE FLOOR GOVERNS EVERY ARM BUT THE SAVE'S, and the
57
+ // save's arm keeps `KC-9`, its own message, and the floor's own sentence beside it** — so the caller can still
58
+ // read, from the ONE `outcome` entry, both what failed and what the composition then met.
59
+ const floorGoverns = floor !== null && status !== KC.saveFailed;
60
+ const finalStatus = floorGoverns ? KC.rollbackFloor : status;
61
+ const finalMessage = floorGoverns
62
+ ? `the per-op rollback met the store's own ${floor.code} at ${floor.opId}`
63
+ : floor === null
64
+ ? message
65
+ : `${message} — the per-op rollback then met the store's own ${floor.code} at ${floor.opId}`;
56
66
  // THE `outcome` ENTRY — appended ONCE, and `state` is THE ARM'S ONE TOKEN: the floor is answered IN THIS
57
67
  // SAME ENTRY where the attempt would also have had a failure arm, and NO SECOND ENTRY IS APPENDED
58
68
  // (`kernel-surface.md §2.5` item 1's kind table; `§2.6` item 6 (a), (b)).
59
- append(attempt, 'outcome', null, attempt.ops[attempt.ops.length - 1].opId, finalMessage, finalStatus);
69
+ // **⟶ AND ITS ITERATION NAMES NO PAIR AND THE ATTEMPT'S LAST OP — `D-53`'s first ruling**, so the two columns
70
+ // travel as one identity wherever an op exists and read `null` where one does not ("an `outcome` names them
71
+ // where they exist").
72
+ append(attempt, 'outcome', { pair: null, op: attempt.ops[attempt.ops.length - 1] }, finalMessage, finalStatus);
60
73
  // THE DISCARD RULE, AT ITS ONE OBSERVABLE (`attempt-lifecycle.md §2.4` item 8 (i) (5), read with
61
74
  // `kernel-surface.md §2.1` item 2's discarded-set clause; `A: docs/decisions.md` · `A112`): where the
62
75
  // attempt's own write FAILED the registered constraints at its landing and could not be repaired, its