provident-storeroom 1.3.0 → 1.5.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.
- package/README.guide.md +501 -8
- package/README.md +40 -5
- package/RELEASE-NOTES.md +63 -1
- package/index.d.ts +54 -18
- package/index.mjs +121 -33
- package/package.json +6 -6
- package/src/entry/constraints.mjs +365 -46
- package/src/entry/delivery.mjs +83 -4
- package/src/entry/kernel-attempt.mjs +150 -9
- package/src/entry/kernel-commit.mjs +169 -3
- package/src/entry/kernel-foundations.mjs +349 -31
- package/src/entry/kernel-registration.mjs +87 -12
- package/src/entry/kernel-terminal.mjs +49 -3
- package/src/entry/state.mjs +87 -0
package/README.guide.md
CHANGED
|
@@ -30,8 +30,10 @@ preflight REFUSES when these two lines do not read what it computed** (`scripts/
|
|
|
30
30
|
one check, read-only). **THE TWO LINES ARE THIS GUIDE'S OPERATIVE FIGURES; every dated paragraph below is the
|
|
31
31
|
reading of its own date and is read through them.**
|
|
32
32
|
|
|
33
|
-
PUBLISH-GUIDE-VERSION-LINE: THE VERSION THIS GUIDE DESCRIBES IS `1.
|
|
34
|
-
PUBLISH-GUIDE-CUT-STATE:
|
|
33
|
+
PUBLISH-GUIDE-VERSION-LINE: THE VERSION THIS GUIDE DESCRIBES IS `1.5.0`.
|
|
34
|
+
PUBLISH-GUIDE-CUT-STATE: NOT STATED -- this document describes the behaviour at the version line above, and the cut-state is the release's to state, not a shipped document's (`D-57`).
|
|
35
|
+
|
|
36
|
+
**⟶ AND THE CUT-STATE SENTENCE ABOVE IS WITHDRAWN, 2026-10-28 — THE CUT-STATE IS NOT A DOCUMENT'S TO STATE.** `RCA-8(g)` ANNOTATE-BESIDE, and the architect's ruling verbatim (`strata/decisions.md` · `D-57`): *"Drop the cut-state."* **The two marked lines above stand BYTE-FOR-BYTE as the reading of their own date and are read THROUGH this clause — not one byte of either is deleted — and the sentence this clause withdraws is the SECOND of them, the one that claims to say whether the release this guide travels in is cut.** **THE REASON IS MEASURED, THREE TIMES OVER, AND IT IS STRUCTURAL RATHER THAN A MISTAKE: A SHIPPED DOCUMENT IS SEALED BEFORE THE TAG THAT MAKES ITS CUT-STATE TRUE**, so every release packed before its tag ships a document denying itself, and a guard running in that release's own preflight can only ever confirm *"not cut"* — `1.2.0` shipped *"NOT CUT"*, `1.3.0` shipped *"`v1.3.0` DOES NOT EXIST"*, and the change list's head was stale within minutes of the tag that falsified it. **SO THE CUT-STATE SENTENCE IS DROPPED AS A CLAIM AND THE VERSION LINE STAYS**, because the version line is the ONE figure a tarball can be right about: the manifest knows it and travels beside it. **`scripts/publish.sh --figures` GENERATES AND ASSERTS THE VERSION LINE AND NOTHING ELSE** — the cut-state generation and its assertion are removed in the same act, so a tag set that moves can no longer redden this guide — **and the same one line is asserted in THREE shipped documents: this guide, `README.md` beside it, and the change list** (`DEF-AC`'s remaining demand, *"guard the release notes too"* and *"`README.md` still declares no version at all"*). **THE TAG SET IS NOT DELETED AND NOT DENIED: it is a RECORD OF RELEASES held in the change list, PAST TENSE — a list of what has been cut — and never a claim about the current one.** **AND WHAT THIS CLAUSE DOES NOT SAY, SAID PLAINLY BECAUSE A READER WILL LOOK FOR IT: it does not say whether the version this guide prints is cut.** That is not a document's to state; the record of what has been cut is in `RELEASE-NOTES.md`, and the registry is the registry.
|
|
35
37
|
|
|
36
38
|
**⟶ CORRECTED 2026-10-26 — THE VERSION THIS GUIDE DESCRIBES IS `1.0.0`, NOT `0.5.0`, AND THE SENTENCE ABOVE STANDS
|
|
37
39
|
BYTE-FOR-BYTE AS THE READING OF THE DAY THIS FILE WAS WRITTEN AND IS READ THROUGH THIS CLAUSE** (`RCA-8(g)`
|
|
@@ -69,6 +71,8 @@ measurements on a locally packed, installed artefact, and is still not witnessed
|
|
|
69
71
|
|
|
70
72
|
**⟶ AND THE FIGURES MOVE TO THE PREPARED RELEASE, 2026-10-28 — THE ELEVEN SITES READ `1.3.0`, AND `1.3.0` IS PREPARED, NOT CUT.** `RCA-8(g)` ANNOTATE-BESIDE: **the eight dated paragraphs above stand BYTE-FOR-BYTE and are read THROUGH this clause — so do their readings of `0.5.0`, `1.0.0`, `1.0.1`, `1.1.0` and `1.2.0`; each was true of the tree it was read on.** **THE MANIFEST AT ITS OWN BYTES THIS DATE: `bindings/js/package.json`'s `version` is `"1.3.0"` and all five `optionalDependencies` pins are `1.3.0` with it** — the six figures move together and never singly (§0 item 1; `PUB-DD-10`) — **and the five platform manifests' own `version` fields read `1.3.0` with them, which is eleven sites and one rebuild; the rebuilt artefact is stamped from that manifest, so `version()` answers `1.3.0`.** **SO THE SITES ARE RE-READ, NOT RE-SPELLED:** this section's version line, §2.2's five exact pins, §8.1's *"an installed `0.5.0`"* and §11's manifest table all read `1.3.0`; **every `0.5.0`, `1.0.0`, `1.0.1`, `1.1.0` and `1.2.0` this guide prints is the figure of its writing date and reads `1.3.0` from here on.** **AND WHAT THIS CLAUSE DOES NOT CLAIM, SAID FIRST BECAUSE IT IS THE EASIER THING TO GET WRONG: `1.3.0` IS PREPARED AT THOSE ELEVEN FIGURES AND THE TAG IS NOT CUT — `git tag --list` carries `v1.0.0` · `v1.0.1` · `v1.1.0` · `v1.2.0` and no `v1.3.0`, and the registry carries no `1.3.0`; so nothing here says the release is cut, and the tag is not cut until the architect cuts it.** **AND THE TREE THIS GUIDE DESCRIBES IS TEN COMMITS PAST `v1.2.0`** — `git describe` reads `v1.2.0-10-g14787b5` and `git rev-list --count v1.2.0..HEAD` reads `10`, one commit past the nine the paragraph above reads — **so the two behaviour changes and the four smaller items `§1.3.0` of the change list declares are the CONTENT of `1.3.0`, and `1.3.0` is the version that carries them once it is cut; the `1.2.0` tarball carries neither, and `1.3.0` is the version this guide describes from here on.** **THE ONE THING THAT IS STILL UNMOVED IS THE ONE THING NO PASS WITNESSED, AND IT IS NOT A FIGURE:** no `npm install` was run against the registry, so a registry-installed `1.3.0` is described here from the manifest and from measurements on a locally packed, installed artefact, and is still not witnessed as a registry download. **NO OTHER CLAIM OF THIS GUIDE MOVES: the `files` allowlist's seven rows (§11's `2026-10-28` clause (i)), the packed set's `20` bare / `21` assembled (§11's clause (ii)), the twenty-status set (§11's clause (iv)), the fourteen own members and every API clause stand exactly as they stood.** (`D-41` item `1`; the change list's `§1.3.0`; `docs/specs/storeroom-publish/contract.md §2.3` item `3` — the byte-for-byte guide stage this correction travels by; `scripts/publish.sh`'s eleven cells and its two generated figures.)
|
|
71
73
|
|
|
74
|
+
**⟶ AND THE ELEVEN VERSION SITES READ `1.5.0`, 2026-10-29 — THE PREPARED FIGURE, AND EVERY `1.3.0` THIS GUIDE PRINTS READS `1.5.0` FROM HERE ON.** `RCA-8(g)` ANNOTATE-BESIDE: **the nine dated paragraphs above stand BYTE-FOR-BYTE and are read THROUGH this clause — so do their readings of `0.5.0`, `1.0.0`, `1.0.1`, `1.1.0`, `1.2.0` and `1.3.0`; each was true of the tree it was read on.** **THE MANIFEST AT ITS OWN BYTES THIS DATE: `bindings/js/package.json`'s `version` is `"1.5.0"`, all five `optionalDependencies` pins are `1.5.0` with it, and the five `bindings/js/platforms/*/package.json` `version` fields read `1.5.0` with them — ELEVEN CELLS, ONE VERSION, moved TOGETHER in this act and never singly (§0 item 1; `PUB-DD-10`) — and `npm run build` stamps the artefact from that manifest, so `version()` answers `1.5.0` at the built artefact and at a package installed from it.** **AND THE RELEASE THIS CHAIN SKIPPED IS NAMED RATHER THAN SILENTLY ABSORBED: `1.4.0` read `1.4.0` at those same eleven cells and was cut from them — the tag `v1.4.0` names a tree whose `bindings/js/package.json` reads `"version": "1.4.0"` at its own bytes.** **SO THE SITES ARE RE-READ, NOT RE-SPELLED:** this section's version line, §2.2's five exact pins, §8.1's *"an installed `0.5.0`"* — itself already corrected to `1.0.0`, `1.0.1`, `1.1.0`, `1.2.0` and `1.3.0` — and §11's manifest table all read `1.5.0`. **AND THE TAG SET IS A RECORD OF RELEASES AND NOT A CLAIM ABOUT THIS TREE:** `git tag --list` answers `v1.0.0` · `v1.0.1` · `v1.1.0` · `v1.2.0` · `v1.3.0` · `v1.4.0`, and `git describe` reads `v1.4.0-27-ga3312b7` — **so this clause states the version and NOT whether it is cut, because the cut-state is not a document's to state** (`D-57`; the clause above).
|
|
75
|
+
|
|
72
76
|
**AND ONE THING A READER OF THE INSTALLED PACKAGE SHOULD KNOW BEFORE THE CITATIONS: WHEN THIS DOCUMENT IS AT
|
|
73
77
|
`node_modules/provident-storeroom/README.guide.md`, IT IS THIS GUIDE.** **Its ONE authored home is
|
|
74
78
|
`docs/guides/package-use.md` in the repository, and the release's assembly stages it byte-for-byte at the packaging
|
|
@@ -417,7 +421,8 @@ is a set-position class refusal, not a tier.**
|
|
|
417
421
|
leaves every other tier's version at that key untouched** (`write-and-read.md §4.4`, arm `D-1`). **A `delete` with
|
|
418
422
|
no tier removes every version at the target and all downstream of each; a delete of the empty address empties the
|
|
419
423
|
register; and a delete that removes nothing is a SUCCESS — it answers `tiers: []` rather than throwing**
|
|
420
|
-
(`write-and-read.md §4.3`, `§4.4` arms `D-2`, `D-3` and `D-5`).
|
|
424
|
+
(`write-and-read.md §4.3`, `§4.4` arms `D-2`, `D-3` and `D-5`). **⟶ AND A `delete` THAT REMOVES SOMETHING NOW PASSES CONSTRAINTS** — see §7.8; a delete that removes
|
|
425
|
+
nothing is not an edit and runs no pass.
|
|
421
426
|
|
|
422
427
|
### §5.4 Undo is bounded by the journal, and `rollback` is not a single-op undo
|
|
423
428
|
|
|
@@ -460,6 +465,14 @@ guide's constraint story that is not yet observable.
|
|
|
460
465
|
4. **WHEN IT REFUSES AND NO ATTEMPT STANDS, `log` IS ONE `constraint-check` ENTRY — NOT `[]`.** The `log` member is normally *"the attempt's own message log"*, and `[]` is its value where a failure's cause has no attempt; **a reversal's judgement is one of those causes, and here the refusal AUTHORS ITS OWN RECORD instead of reporting an absence:** `log.length === 1`, `log[0].kind === 'constraint-check'`, `log[0].id` the refusing pair's own id, `log[0].state` `null`, `log[0].opId` the reversal's own op id or `null`. **So do not read an empty array as *"nothing failed"* at this arm, and do not read this one entry as an attempt's history — it is the refusal's.** **⟶ AND FROM 2026-10-28 *"the attempt's own message log"* IS READ THROUGH `D-53` AS THE COMMIT'S OWN LIST** (§8.0a above): at this arm no attempt and no commit stands, which is what makes `[]` the wrong value here and one authored entry the right one.
|
|
461
466
|
**AND ONE THING ABOUT A FAILING REPAIR, WHICH IS THE SAME SHAPE AND IS DECLARED RATHER THAN DISCOVERED: THE FLOOR CALL RESTORES THE ADDRESS THE ATTEMPT WROTE; AN ADDRESS A REPAIR LANDED AT BESIDE IT IS NOT RESTORED.** `D-40`'s sentence — *"a failed attempt leaves the store exactly as it found it"* — is measured true of the attempt's own address and false of a beside address: the store's rollback scope is the addressed subtree at or after the named op's own position, so a repair that writes elsewhere leaves THAT value standing after the failed attempt. **If you need the beside address's pre-attempt value, keep it yourself: the store's own floor call does not put it back** (`docs/specs/storeroom-js/contract.md §2.6`'s clause of 2026-10-26, which is where the four narrowings, the `log` arm and this spatial scope are declared; `E2E-C-9` cell 6 in `tests/e2e/blind-constraints-v2.mjs` is the measured reach).
|
|
462
467
|
|
|
468
|
+
**⟶ AND THE ONE SENTENCE THAT WAS MISSING, DECLARED HERE IN THE TERMS YOUR OWN REVIEW ASKED FOR, 2026-10-28 — `D-55` · `DEF-AN`, THE ARCHITECT, VERBATIM: *"A constraint-refused edit rolls back to the previous safe state, with all edits in the commit undone. Specify this."*** `RCA-8(g)` ANNOTATE-BESIDE: **every clause of §5.4 above and below stands BYTE-FOR-BYTE and is read THROUGH this one; no rank, no status, no member and no promise moves — what this clause adds is the statement of WHAT THE ROLLBACK COVERS.** **THE RULE, IN ONE SENTENCE: a refused edit rolls the commit back to the state it opened at — every edit the commit made is undone, including writes that succeeded before the refusal.** **THE REASON, STATED SO YOU CAN REASON WITH IT: the restore names the mark the commit cached at its OUTERMOST OPENING** (§5.4's closing clause above), **and a commit is one unit of work, so the mark is not the refused op's own — it is where the WHOLE commit began.** **A write, a repair or a delete that landed EARLIER IN THE SAME COMMIT is undone WITH the refusal**, because the commit that carried it is undone; **that is WHY the mark is the outermost opening's and not the nearest one.** **WHAT STILL STANDS: a refused edit moves no state and mints no code beyond the one declared refusal; an edit in a DIFFERENT commit is not touched** — a listener body's own write opens its own commit (`D-53`), so it is a unit of its own and is not undone by yours. **MEASURED ON THIS CHECKOUT, 2026-10-28:** a write that succeeds inside a commit whose listener then reverses and is refused leaves the store at the state the commit opened at — the successful landing gone with the reversal — and the pair is asked at that state (`tests/e2e/discard-witness.mjs`'s `DW-10.5`).
|
|
469
|
+
|
|
470
|
+
**⟶ AND THAT ONE SENTENCE IS CORRECTED, 2026-10-28 — `D-63`: A REFUSED EDIT ROLLS BACK TO THE LAST VALIDATED STATE, NOT TO THE STATE THE COMMIT OPENED AT.** `RCA-8(g)` ANNOTATE-BESIDE: **the whole paragraph above stands BYTE-FOR-BYTE and is read THROUGH this clause; no rank, no status, no member and no promise moves — what moves is the STATE a refusal restores, and the sentence that names it.** **THE RULING, THE ARCHITECT, VERBATIM:** *"After the constraint sweep ends in a valid state, bump the restore mark. The passing constraint state marks the new next valid state."*
|
|
471
|
+
|
|
472
|
+
- **OLD → NEW, SO YOU CAN TELL WHICH BEHAVIOUR YOUR VERSION HAS.** **OLD:** *"a refused edit rolls the commit back to the state it opened at — every edit the commit made is undone, including writes that succeeded before the refusal."* **NEW:** *"a refused edit rolls the commit back to THE LAST VALIDATED STATE — every edit the commit made AFTER the last constraint sweep that passed is undone, and every edit that sweep APPROVED stands."* **THE RESTORE IS TOLD A POSITION AND THE POSITION MOVES FORWARD**: it starts where the commit opened and it moves to the store's own journal position at every constraint sweep that ends valid, **before the listeners are delivered.** **So `"the state it opened at"` is now the answer to exactly one case — a commit whose constraint sweep never passed — and it is today's behaviour there and stays.**
|
|
473
|
+
- **WHAT YOU WILL SEE, IN THE THREE SHAPES THAT MATTER TO A CONSUMER.** **(1) An edit that PASSED its checks STANDS even if a later step in the same commit is refused** — you asked for it, the constraints approved it, and the refusal is about what came after. **(2) A REPAIR'S WRITE GOES WITH THE WRITE**: a repair runs *during* the check, so if the check then refuses, the repair's edit is undone with yours. **(3) A LISTENER'S WRITE SURVIVES YOUR REFUSAL**: a listener is delivered *after* every constraint has passed, so its write is a later, discrete act and the restore of yours does not reach it — it is still there afterwards, and you should not treat a refusal as having undone it.
|
|
474
|
+
- **AND THE `DEF-AN` MEASUREMENT ABOVE STILL STANDS, FOR THE REASON IT STANDS.** That drive's pair refuses on the FIRST check, so that commit never reaches a passing sweep, nothing moves the mark, and the store returns to the state the commit opened at — **which is why the reading is unchanged and the rule that produced it is not.** The `D-63` arms are driven beside it in `tests/e2e/discard-witness.mjs` section `W12` (`DW-12.1`–`DW-12.4`), and `tests/e2e/in-commit-restart.test.mjs`, `tests/e2e/direct-pass-restart.test.mjs`, `tests/e2e/downstream-claims.test.mjs` and `tests/e2e/def-ae-restore-mark.test.mjs` carry the re-derived readings.
|
|
475
|
+
|
|
463
476
|
**⟶ AND WHAT THE STORE HOLDS AFTER A REFUSED REVERSAL, DECLARED HERE BECAUSE `1.2.0`'s ONE BEHAVIOUR CHANGE IS THIS AND NO SHIPPED DOCUMENT SAID IT UNTIL NOW.** `RCA-8(g)` ANNOTATE-BESIDE: **every clause of §5.4 above stands BYTE-FOR-BYTE and is read THROUGH this one; this clause adds the declaration the release owed and moves no promise it prints.**
|
|
464
477
|
|
|
465
478
|
- **THE FACT: WHEN A REVERSAL IS REFUSED, THE COMPENSATING ACT PUTS THE STORE BACK AT THE MARK THE COMMIT CACHED WHEN IT OPENED — the journal's LAST VALID POSITION as it stood when that commit began (an op id, or the FLOOR where the journal held no entry then) — and never at whatever the journal's head happens to be when the refusal is built.** **Every address the reversal MOVED is read again afterwards and counted as restored only where it stands exactly as it stood before the reversal — the same value at the same tier, or absent where it was absent — and the refusal's one log entry says so address by address**, naming each address that could not be put back with what stands there instead and what it was owed (§8.1 row `20`; `js/contract.md §2.6`'s clause of 2026-10-27). **A reversal taken AT REST makes the two marks describe the same state** — it appends no journal entry — **so this is where the store stands either way at the surface.**
|
|
@@ -554,6 +567,23 @@ instance and not a function you hand to the store.**
|
|
|
554
567
|
2. **`triggered` membership is decided by address and by nothing else:** a handle appears exactly where **its
|
|
555
568
|
listener's address is a PREFIX of the triggering address** (`surface.md §2.3` item 5). **So a listener bound at
|
|
556
569
|
a strict descendant of a deleted address does NOT fire.**
|
|
570
|
+
|
|
571
|
+
**⟶ AND THE SECOND ARM IS `TOUCHED`, 2026-10-29 — `D-66` AND `D-67`, AND THE SENTENCE ABOVE IS READ THROUGH
|
|
572
|
+
THIS ONE.** `RCA-8(g)` ANNOTATE-BESIDE: **that sentence was the build's rule until this date, and the
|
|
573
|
+
architect measured what it costs** — verbatim: *"Does the listener get fired when the value is purged by a
|
|
574
|
+
higher-persistence write that doesn't include it? Because that's the actual problem"* (`strata/decisions.md`
|
|
575
|
+
· `D-67`). **THE ANSWER WAS NO, AND THAT WAS THE DEFECT.** **So a handle now appears where its address is a
|
|
576
|
+
PREFIX of the triggering address (`D-56`, unmoved) OR WHERE THE ACT TOUCHED IT (`D-66`)** — the touched set
|
|
577
|
+
being the paths the act's value contains together with the whole subtree of every version that act PURGED,
|
|
578
|
+
**a purge at a higher persistence included, which is the case the prefix rule could not reach.** **A LISTENER
|
|
579
|
+
BOUND AT A STRICT DESCENDANT OF A DELETED ADDRESS DOES FIRE**: the cascade removed its node, so the value a
|
|
580
|
+
read there returns changed. **AND A CONSTRAINT IS REACHED BY THE SAME ONE PREDICATE** (`D-69`: a registration
|
|
581
|
+
is a handle and a body, and a pair has both), **so `triggered` names both kinds in one list and the kernel
|
|
582
|
+
filters it — an element that is a constraint's `id` is ASKED, a listener's is DELIVERED.** **MEASURED
|
|
583
|
+
2026-10-29 on this checkout** (`tests/e2e/discard-witness.mjs`'s `DW-14.1`/`DW-14.2`/`DW-14.3`): a listener at
|
|
584
|
+
`['a','b','c']` fires once for a write at `['a','b']` that writes `c`; a `file`-tier write that purges a node
|
|
585
|
+
with a `mem` shadow fires the listener bound at the purged path **and** asks the constraint bound there, while
|
|
586
|
+
the pair at an address neither value names is asked nothing.
|
|
557
587
|
3. **A registration made during a firing window is not invoked during that window.** A delivery's recipient set
|
|
558
588
|
is read from the array as it stands when the window opens (`surface.md §2.4`, the delivery-window clause).
|
|
559
589
|
4. **One invocation per distinct handle.** A handle bound at two addresses that both match fires ONCE
|
|
@@ -566,6 +596,11 @@ BOUND at, never the address the write targeted**, and the two are told apart: **
|
|
|
566
596
|
the write's address** (`index.d.ts`, `TriggerPair`; `held-listener/contract.md §2.4` items 1 and 7; `PAR-HL-19`–`PAR-HL-21`).
|
|
567
597
|
**There is no parallel `triggeredAddresses` array and none may be added.**
|
|
568
598
|
|
|
599
|
+
**★ ⟶ AND WHAT THAT ONE ARGUMENT DOES NOT CARRY, STATED BESIDE IT 2026-10-29: it names the ACT's address and
|
|
600
|
+
never what MOVED under it — so a container listener that must know which position moved diffs the collection
|
|
601
|
+
itself** (§7.9's item 4, where the reason is given: the act's address is the store's own datum, and which
|
|
602
|
+
position moved is the caller's question).
|
|
603
|
+
|
|
569
604
|
- **`write` answers `{op, watched, triggered}`** (`index.d.ts`, `WriteRecord`). **`op` is the store's own journal
|
|
570
605
|
record and not a number** — `{opId, kind, address, tier, before, after, seq, compacted}` — and **the op's
|
|
571
606
|
`opId` is a string** and is the token `rollback` accepts (`index.d.ts`, `JournalOp`). **`watched` carries
|
|
@@ -581,10 +616,206 @@ the write's address** (`index.d.ts`, `TriggerPair`; `held-listener/contract.md
|
|
|
581
616
|
- **The kernel's own `pending` list carries the same pair element as `triggered`**, because `pending` is seeded as
|
|
582
617
|
a clone of the store's returned set (`index.d.ts`, `CommitRecord`; `held-listener/contract.md §2.4` item 3).
|
|
583
618
|
|
|
619
|
+
**⟶ AND THAT BULLET IS THE ONE THAT MOVED, 2026-10-29 — `D-56`: A PASSING WRITE NOW EMPTIES `pending`.** `RCA-8(g)`
|
|
620
|
+
ANNOTATE-BESIDE: **every clause above stands BYTE-FOR-BYTE and is read THROUGH this one; the record's four members,
|
|
621
|
+
the seeding rule and the by-identity check-off do not move — what moves is HOW MUCH of the seeded set the pass takes
|
|
622
|
+
out of it.** **THE MECHANISM, IN ONE SENTENCE: `pending` is seeded whole from the store's `triggered` set and every
|
|
623
|
+
handle the pass EVALUATES is checked off by identity, so when the pass's set and the store's set are the SAME SET the
|
|
624
|
+
residue is EMPTY.** **`AT `1.4.0`'s PREDECESSOR** the pass selected only the pairs standing at the act's EXACT
|
|
625
|
+
address, the store's `triggered` set was already the wider PREFIX set, and the difference was the residue you saw: a
|
|
626
|
+
pair at `['window','tabs']` fired for a write at `['window','tabs','landingPage']` (a listener, `§6.4`) and was
|
|
627
|
+
never asked by the pass — so its handle stayed in `pending` and the delivery ran it. **NOW the pass's set IS the
|
|
628
|
+
`triggered` set** (`§7.6`'s clause of 2026-10-29, `D-56`), **so on a PASSING write every handle in the returned set is
|
|
629
|
+
checked off, `record.pending` reads `[]`, and the delivery's residue is empty.** **MEASURED `1` → `0` on a passing
|
|
630
|
+
write.** **WHAT THAT MEANS FOR YOU, SAID PLAINLY: `pending` IS NO LONGER A LIST OF "WHAT I STILL OWE A DELIVERY TO" —
|
|
631
|
+
IT IS THE LIST OF HANDLES THE PASS DID NOT EVALUATE, and on a passing write there are none.** **A non-empty `pending`
|
|
632
|
+
therefore means one of two things and only two**: the write FAILED (the record's `pending` is then the whole returned
|
|
633
|
+
set, because a failing arm checks nothing off), or a handle stands in `triggered` that stands at none of the pass's
|
|
634
|
+
addresses — **which, from this date, is the DESCENDANT direction `D-56` does not cover.** **Read `triggered` for what
|
|
635
|
+
fired and `pending` for what the pass left; on a passing write they are no longer the same list, and `pending` is the
|
|
636
|
+
shorter one.** (`D-56`; `index.d.ts`'s `CommitRecord`; §7.6's prefix-set clause; §7.8's ancestor clause.)
|
|
637
|
+
|
|
638
|
+
**⟶ AND THE SENTENCE THAT SAYS A PASSING WRITE EMPTIES `pending` IS RE-MEASURED ONE ARM FURTHER, 2026-10-29 — THE PASS CHECKS OFF WHAT IT ASKED, AND A LISTENER'S HANDLE IS THE DELIVERY'S.** `RCA-8(g)` ANNOTATE-BESIDE: **the clause above stands BYTE-FOR-BYTE and is read THROUGH this one — so does its *"on a PASSING write every handle in the returned set is checked off, `record.pending` reads `[]`"*, which is exactly true of a fired set made of CONSTRAINTS and is not true of a fired set that also carries a LISTENER.** **MEASURED 2026-10-29 ON THIS CHECKOUT, THROUGH THE PUBLISHED ENTRY, BOTH ARMS PRINTED:** a pair at `['anc']` with a passing write at `['anc','child']` — `K-2`'s record carries `['anc-pair']` and after the pass it carries `[]` ✓ **the `1 → 0` the clause above measured**; the same write with a listener also bound at `['anc','child']` — `K-2`'s record carries `['anc-pair','watch-child']` and **after the pass it carries `['watch-child']`**, because **the pass evaluates CONSTRAINT ROWS and a listener's handle is the DELIVERY's work and never the pass's.** **SO READ IT PRECISELY: ON A PASSING WRITE `pending` HOLDS EXACTLY THE HANDLES THE PASS DID NOT EVALUATE — NO CONSTRAINT AT AN ADDRESS IT ASKED, AND EVERY LISTENER THE DELIVERY WILL FIRE** — **and `[]` is that member's value when nothing but constraints was fired.** **NO MEMBER MOVES AND NO FIGURE OF THE CLAUSE ABOVE IS WITHDRAWN: this is its reading, one arm wider.** (`D-56`; `D-69`; `tests/e2e/discard-witness.mjs`'s `DW-14.4`; §7.6's prefix-set clause.)
|
|
639
|
+
|
|
640
|
+
**⟶ AND THE COMPOSITION IS NOW MEASURED, 2026-10-29 — `D-69`, AND EVERY SENTENCE ABOVE STANDS BYTE-FOR-BYTE.**
|
|
641
|
+
`RCA-8(g)` ANNOTATE-BESIDE: **the clause above reasons about the member's SIZE; this one states what the member now
|
|
642
|
+
CARRIES, because `D-69` moved the store's set as well as the pass's.** **THE STORE NOW NAMES A CONSTRAINT STANDING AT
|
|
643
|
+
A TOUCHED DESCENDANT** — `D-66`'s criterion, computed where the per-tier versions live (`D-67`) — **so `pending` is
|
|
644
|
+
seeded with that `id` where it was not before, and the pass then ASKS it and CHECKS IT OFF like any other element.**
|
|
645
|
+
**MEASURED, BEFORE → AFTER, on one drive** (`tests/e2e/discard-witness.mjs`'s `DW-14.4`): a write whose pair stands at a
|
|
646
|
+
TOUCHED DESCENDANT and whose listener stands at an ancestor — at `K-2`'s return the member carries **2** elements
|
|
647
|
+
(the constraint's `id` AND the listener's handle) where the prefix-only composition carried **1**, and after the pass
|
|
648
|
+
it carries **1** (the listener's handle, which is the DELIVERY's work and never the pass's) — **so the composition
|
|
649
|
+
moves AT `K-2` and the delivered residue is unmoved.** **WHAT THAT MEANS FOR YOU, SAID PLAINLY: a `pending` entry is
|
|
650
|
+
either a listener the delivery still owes (the residue) or a constraint the pass has not reached (the failing arm's
|
|
651
|
+
whole set) — and on a passing write the pass asks EVERY element the store named that IT HOLDS A ROW FOR, so the
|
|
652
|
+
residue's only remaining constraint is one it holds no row for.** **THAT ONE STATE IS REACHABLE AND MEASURED**
|
|
653
|
+
(`tests/e2e/discard-witness.mjs`'s `DW-14.3`): **a registration whose own constraint REFUSED it at the registration
|
|
654
|
+
instant** — `ConstraintRefusedAtRegistration`, state `2` of `contract.md §7.3` (b) — **leaves the store-side entry
|
|
655
|
+
standing and NO row in the kernel's registry**, so the store's `triggered` names that handle, the pass asks nothing
|
|
656
|
+
for it, and it stays in the residue; **it fires NOTHING, silently, and its body's one run is the registration's own
|
|
657
|
+
evaluation** (`surface.md §2.4` rule 1). (`D-69`; `D-66`; `D-67`; `kernel-surface.md §2.1` item 2's `A112` reading.)
|
|
658
|
+
|
|
584
659
|
**And one store behaviour that shows up in the records if you use integer addresses: a delete of an indexed
|
|
585
660
|
version of an array container RENUMBERS the surviving anchors in container order from `0`, each keeping its tier
|
|
586
661
|
and its node** (`write-and-read.md §4.4`'s renumbering clause).
|
|
587
662
|
|
|
663
|
+
**★ AND WHICH KIND YOU ARE IS THE QUESTION THIS SURFACE LEAVES YOU, 2026-10-29 — `D-70`: IF YOUR CALLBACK CHANGES
|
|
664
|
+
THE STORE IT IS A CONSTRAINT OR A REPAIR (§7), AND IF IT TELLS THE WORLD IT IS A LISTENER.** **§7.9 states the three
|
|
665
|
+
rules, each with its reason, and they govern what you register and where — because this surface's other kind is one
|
|
666
|
+
section away and the two are NOT interchangeable: a listener at a derived index is a registration on a value the
|
|
667
|
+
store never wrote, and a listener that writes has no loop guard to bound it.** (`D-70`; §7.9.)
|
|
668
|
+
|
|
669
|
+
### §6.5 ★ A LISTENER'S OUTCOME IS NOT YOURS — its own commit, and its refusal cannot fail your write
|
|
670
|
+
|
|
671
|
+
**★ THE RULING, IN THE ARCHITECT'S OWN SENTENCE** (`strata/decisions.md` · `D-71`, verbatim): *"A listener is
|
|
672
|
+
independent of the commit record of the triggering caller. The caller does not need to know about the outcome of
|
|
673
|
+
the listeners."* **AND THE CONSEQUENCES, STATED BECAUSE EACH IS SOMETHING YOU WILL OTHERWISE GUESS AT:**
|
|
674
|
+
|
|
675
|
+
1. **A LISTENER RUNS IN ITS OWN COMMIT** (`D-45`), **AND ITS OUTCOME — PASSED OR REFUSED — IS THAT COMMIT'S**, not
|
|
676
|
+
yours.
|
|
677
|
+
2. **THE TRIGGERING CALLER RECEIVES ITS OWN OUTCOME AND NOTHING ABOUT THE LISTENER'S.** **Your record is your
|
|
678
|
+
write's** — `op`, `watched`, `triggered` (`§6.4`) — **and it carries no member for what a listener's own
|
|
679
|
+
commit did.**
|
|
680
|
+
3. **A LISTENER'S REFUSAL DOES NOT FAIL THE WRITE THAT FIRED IT.** **A listener is a SIDE EFFECT for external
|
|
681
|
+
code, and a side effect's failure is the side effect's** — your write already passed every constraint and
|
|
682
|
+
already answered before the delivery ran.
|
|
683
|
+
4. **AND BECAUSE OF THAT, THE ONLY THING A CHAIN OF LISTENER WRITES EXPOSES UPWARD IS ITS COST** — depth, and
|
|
684
|
+
whatever the listener itself does with what its own call returns. **Which is why the missing loop guard is the
|
|
685
|
+
asymmetry that matters** (`§7.9` item 2): **nothing else about the chain is observable, so nothing else can be
|
|
686
|
+
bounded.**
|
|
687
|
+
|
|
688
|
+
**MEASURED 2026-10-29 ON THIS CHECKOUT, THROUGH THE PUBLISHED ENTRY — AND THE MEASUREMENT IS THE POINT, BECAUSE
|
|
689
|
+
THIS IS A CLAIM ABOUT WHAT CROSSES AND IT MUST BE PRINTED RATHER THAN ASSUMED.** One store: a listener at `['a']`
|
|
690
|
+
whose body writes `['b'] = 'bad'`, and a constraint at `['b']` that refuses `'bad'` with
|
|
691
|
+
`{message: 'b may not be bad'}`. **WHAT THE DRIVE PRINTED, RAW:**
|
|
692
|
+
|
|
693
|
+
- **THE CALLER'S `store.write(['a'], 'mem', 1)` ANSWERED NORMALLY** — `{op: {opId: 'op-1', kind: 'write', address:
|
|
694
|
+
['a'], tier: 'mem', before: {present: false}, after: {present: true, value: 1}, seq: 1, compacted: false},
|
|
695
|
+
watched: [['a']], triggered: [{handle: 'watch-a', address: ['a']}]}` — **no refusal, no status and no `log`
|
|
696
|
+
member: nothing of the listener's outcome crossed.**
|
|
697
|
+
- **THE LISTENER'S OWN WRITE WAS REFUSED, INSIDE THE LISTENER** — caught there as
|
|
698
|
+
`status: 'ConstraintPassRefused'`, `message: 'b may not be bad'`, own members `['status', 'message', 'log']`
|
|
699
|
+
— **and that refusal was the listener's to catch; the call above never saw it.**
|
|
700
|
+
- **THE STORE AFTER THE CALL: `['a']` reads `1`, AND `['b']` READS ABSENT (`AddressNotFound`)** — the listener's
|
|
701
|
+
own commit restored itself to where it opened (`§5.4`), and the caller's landing stands.
|
|
702
|
+
- **THE CONTROL, WHICH IS WHAT MAKES THE MEASUREMENT MEAN ANYTHING: the same caller writing `['b'] = 'bad'
|
|
703
|
+
DIRECTLY is REFUSED, same `ConstraintPassRefused`, same message.** **So the constraint was armed throughout,
|
|
704
|
+
and what differs is only WHAT CROSSES — a caller's own refusal crosses, and a listener's does not.**
|
|
705
|
+
|
|
706
|
+
**WHAT THAT MEANS FOR YOU, SAID PLAINLY: YOU CANNOT READ A LISTENER'S SUCCESS FROM ANY RETURN VALUE.** **No
|
|
707
|
+
member, no log entry and no status on your record says a listener's own write landed; the only way your code
|
|
708
|
+
learns it is if the listener body tells you out of band.** **That is the design and not a gap, and it is the
|
|
709
|
+
reason the next subsection's chain is bounded by cost alone.** (`D-71`; `D-45`; `§6.4`'s record members; `§7.9`.)
|
|
710
|
+
|
|
711
|
+
**AND THE ONE THING INDEPENDENCE DOES NOT COVER, MEASURED SO IT IS NOT INFERRED: A LISTENER THAT THROWS IS NOT A
|
|
712
|
+
LISTENER THAT IS REFUSED, AND ITS THROW CROSSES ALL THE WAY UP.** **MEASURED 2026-10-29, on this checkout, through
|
|
713
|
+
the published entry:** with `hA` at `['a']` writing `['x']` and `hN` at `['x']` throwing
|
|
714
|
+
`new Error('boom-in-nested-listener')`, the run printed `["A", "nested throws", "A caught Error"]` **and the OUTER
|
|
715
|
+
`store.write(['a'], 'mem', 1)` THREW that `Error`** — the delivery's own rule that *"a throwing listener body is
|
|
716
|
+
not caught, not retried and not reported"* (`docs/specs/storeroom-js/contract.md §2.7` item 4) **read through the
|
|
717
|
+
nesting's synchronous stack.** **SO THE INDEPENDENCE IS THE COMMIT'S: a refusal is the nested commit's and cannot
|
|
718
|
+
cross, while a raw throw is not a commit outcome and does cross, because the call is on the same stack and nothing
|
|
719
|
+
catches it on the way up — and the caller's write is then never answered at all.** **If you want a listener's
|
|
720
|
+
failure to stay the listener's, catch it in the listener's own body.**
|
|
721
|
+
|
|
722
|
+
### §6.5a ★ THE ASYMMETRY IS THE DESIGN — a refusal is CONTAINED, a throw is NOT, and the reason is whose code failed
|
|
723
|
+
|
|
724
|
+
**★ THE RULING, THE ARCHITECT, VERBATIM** (`strata/decisions.md` · `D-73`): *"Keep the throw so that the client can
|
|
725
|
+
log errors cleanly"* — **and it completes `§6.5`: the independence stands, and the THROW is the one thing it does not
|
|
726
|
+
cover.** **STATE THE TWO HALVES AS A PAIR, BECAUSE THE ASYMMETRY IS THE POINT:**
|
|
727
|
+
|
|
728
|
+
- **A LISTENER'S REFUSAL IS CONTAINED.** **Its own commit is refused, YOUR write answers its own record, and the
|
|
729
|
+
refused value never lands** — **because a refusal is a JUDGEMENT ABOUT DATA, and the data had its chance.**
|
|
730
|
+
- **★ A LISTENER'S THROW PROPAGATES TO YOU.** **YOUR write throws the listener's error** — **because a throw is an
|
|
731
|
+
ERROR IN THE CLIENT'S CODE, and code that swallows errors cannot be debugged.** **A CONTAINED THROW IS AN ERROR
|
|
732
|
+
NOBODY CAN LOG.**
|
|
733
|
+
|
|
734
|
+
**AND THE REASON IS WHERE THE CODE RUNS: a listener is where YOUR handler runs** — **the screen update, the render,
|
|
735
|
+
the external call** — **so when that code fails, the failure belongs to you, at the point you can see it.**
|
|
736
|
+
**CONTAINMENT WOULD PRODUCE THE OPPOSITE: "your write succeeded and nothing happened" — the least debuggable outcome
|
|
737
|
+
a library can produce.**
|
|
738
|
+
|
|
739
|
+
**★ AND THE CONSEQUENCE, STATED SO IT IS NOT A SURPRISE: A LISTENER THAT THROWS LEAVES YOUR WRITE UNANSWERED.** **You
|
|
740
|
+
see the listener's error instead of your own record, and that is your signal to log and decide.** **MEASURED
|
|
741
|
+
2026-10-29 ON THIS CHECKOUT, THROUGH THE PUBLISHED ENTRY, BOTH HALVES PRINTED RAW:**
|
|
742
|
+
|
|
743
|
+
- **THE THROW.** One listener at `['a']` throwing `new Error('boom-in-listener')`. **`store.write(['a'], 'mem', 1)`
|
|
744
|
+
THREW**, and what it threw **IS the listener's own `Error` — identity `===` true**, `{name: 'Error', message:
|
|
745
|
+
'boom-in-listener'}` — **and the write ANSWERED NOTHING** (`undefined`; no record was produced). **The data had
|
|
746
|
+
already landed (`read(['a'])` answered `1`), because the commit resolved before its delivery ran; what the throw
|
|
747
|
+
takes away is the ANSWER, not the write.**
|
|
748
|
+
- **THE REFUSAL, as the control.** One listener at `['a']` whose body writes `['b'] = 'bad'`, against a constraint
|
|
749
|
+
at `['b']` refusing `'bad'`. **The listener's own write was refused INSIDE the listener** — `{status:
|
|
750
|
+
'ConstraintPassRefused', message: 'b may not be bad', log: […] }`, own members `["status","message","log"]` —
|
|
751
|
+
**`store.write(['a'], 'mem', 1)` RETURNED ITS OWN RECORD** with **no status and no `log`**, and `['b']` read
|
|
752
|
+
**ABSENT**. **The same caller writing `['b'] = 'bad'` directly WAS refused with the same status — so the
|
|
753
|
+
constraint was armed throughout, and the difference is only WHAT CROSSES.**
|
|
754
|
+
|
|
755
|
+
**WHAT THAT MEANS FOR YOU, AND IT IS THE SHAPE TO WRITE: A LISTENER BODY LOGS ITS OWN ERRORS AND DECIDES WHETHER TO
|
|
756
|
+
RETHROW.** **YOUR WRITE IS THE ONE PLACE THE LISTENER'S FAILURE SURFACES.** **Catch in the body to keep a failure
|
|
757
|
+
inside the listener** (`§6.5`'s closing sentence) **— and let it out to hand the error to the caller that can log
|
|
758
|
+
it.** **There is no middle arm: nothing in your record reports a listener's throw afterwards, because after a throw
|
|
759
|
+
there is no record — only the error.** (`D-73`; `D-71`; `D-45`; `docs/specs/storeroom-js/contract.md §2.7`'s clause
|
|
760
|
+
of the same date; `§6.6`'s item 5, where a DEPTH-TWO throw's destination is measured.)
|
|
761
|
+
|
|
762
|
+
**★ AND THE PRACTICAL HAZARD THIS ASYMMETRY MAKES REAL, WRITTEN HERE SO YOU MEET IT IN A TEST RATHER THAN IN PRODUCTION: THE WRITE'S DATA LANDED AND ITS ANSWER DID NOT — SO A BLIND RETRY RE-APPLIES A WRITE THAT ALREADY SUCCEEDED.** `RCA-8(g)` ANNOTATE-BESIDE: **every clause of §6.5a above stands BYTE-FOR-BYTE and is read THROUGH this one; no ruling, no measurement and no member moves — what this clause adds is the one consequence of the measured arm that a caller's ERROR HANDLER will otherwise get wrong.** **IT FOLLOWS FROM THE MEASUREMENT ABOVE AND NEEDS NO NEW ONE:** *"the data had already landed (`read(['a'])` answered `1`), because the commit resolved before its delivery ran; what the throw takes away is the ANSWER, not the write"* — **so `try { store.write(...) } catch { retry() }` runs that write a SECOND time, and the store has no way to tell the two apart: there is no commit identity on your side, no idempotency key and no record, because after a throw there is no record** (`§6.5a`'s closing sentence). **AND THE ERROR IS YOURS AND NOT THE PACKAGE'S SHAPE:** it carries **no depth, no handle, no address** (`§6.6` item 5) — a throw at depth two arrives as the same `Error` object a depth-one throw would — **so a retry policy cannot key on where the chain broke, and a listener body's own `catch` is the only containment arm there is.** **WHAT TO WRITE INSTEAD: catch in the body, decide whether to rethrow, and make the write your retry re-issues one whose landing you can recognise** — an `op` you kept from an earlier answer, a value you wrote, or a read before you write. (`D-73`; `D-71`; the `§6.5a` measurement; `§6.6` item 5; `docs/specs/storeroom-js/contract.md §2.7` item 4.)
|
|
763
|
+
|
|
764
|
+
### §6.6 ★ DELIVERY NESTS — every window is a synchronous call on one thread, and the stack is the only place its order exists
|
|
765
|
+
|
|
766
|
+
**★ THE SHAPE, IN ONE STACK** (`strata/decisions.md` · `D-72`). **A commit resolves and its delivery begins; a
|
|
767
|
+
listener that writes opens a commit of its own (`D-45`), which runs its own pass and then ITS delivery — so
|
|
768
|
+
windows are live at once, NESTED, every level a synchronous call on one thread:**
|
|
769
|
+
|
|
770
|
+
```text
|
|
771
|
+
outer write → its commit → its pass → its delivery → listener A → A's write (a NEW commit) → A's pass → A's delivery → listener B → …
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
**SO "INTERLEAVING" IS NOT CONCURRENCY — IT IS NESTED SUSPENSION: `A`'s entire chain runs to completion before the
|
|
775
|
+
window that fired it invokes its next listener.** **The outer loop is suspended, not interleaved.**
|
|
776
|
+
|
|
777
|
+
**FOUR PROPERTIES ARE REAL, AND NOT ONE OF THEM IS DECLARED ANYWHERE UP THE STACK — so each is stated here:**
|
|
778
|
+
|
|
779
|
+
1. **DEPTH IS UNBOUNDED** — no counter, no cap and no instrument (`D-70`'s absence; `§7.9` item 2). **The stack
|
|
780
|
+
grows until your code stops calling.**
|
|
781
|
+
2. **★ THE CROSS-WINDOW ORDER IS REAL AND UNDECLARED.** **Within one window the order is unspecified and the list
|
|
782
|
+
is a set** (`DD-37`; `§6.4`) — **but across windows the nesting imposes one: a nested chain precedes the
|
|
783
|
+
window's next listener.** **The machine provides that guarantee and no clause promises it — so a consumer
|
|
784
|
+
relying on the opposite relies on nothing.** **MEASURED 2026-10-29, on this checkout, through the published
|
|
785
|
+
entry:** listeners `hA` and `hB` at `['a']` and `hC` at `['x']`, where `hA`'s body writes `['x']`, printed
|
|
786
|
+
`["A", "C (nested commit)", "A-end", "B"]` — **the whole nested chain ran inside `A`'s own call, before `B` was
|
|
787
|
+
invoked, and `A`'s code resumed after it.**
|
|
788
|
+
3. **★ RE-ENTRANCY IS UNAVOIDABLY OPEN.** **A listener that writes at the address it was delivered for fires
|
|
789
|
+
itself again**, because the store's trigger set is computed per act. **Nothing detects it, and nothing is
|
|
790
|
+
meant to** — that is the loop the missing guard leaves open, and the concrete shape of *"no instrument you can
|
|
791
|
+
read"* (`D-70`).
|
|
792
|
+
4. **AND NOTHING ABOVE SEES BELOW** (`D-71`; `§6.5`). **A chain that fails at depth three is indistinguishable, to
|
|
793
|
+
the original caller, from one that never ran:** the original record is its own, and the depth-three refusal is
|
|
794
|
+
that nested commit's.
|
|
795
|
+
|
|
796
|
+
5. **★ AND A THROW AT DEPTH TWO REACHES THE ORIGINAL CALLER — THE PROPERTY THIS NESTING LEFT UNDECLARED, NOW MEASURED
|
|
797
|
+
AND DECIDED.** **A FIFTH PROPERTY JOINS THE FOUR ABOVE.** (`RCA-8(g)` ANNOTATE-BESIDE: **the four stand
|
|
798
|
+
BYTE-FOR-BYTE as the reading of their own date, and item 4's *"nothing above sees below"* is unmoved for
|
|
799
|
+
OUTCOMES; what this item decides is the THROW's destination, which `D-73` alone rules.**) **THE REASON IS THE
|
|
800
|
+
STACK'S: a throw is not a commit outcome, so no nested commit contains it, and every level of this chain is a
|
|
801
|
+
synchronous call with nothing catching in between** (`§6.5a`). **MEASURED 2026-10-29 ON THIS CHECKOUT, THROUGH
|
|
802
|
+
THE PUBLISHED ENTRY:** `hA` at `['a']` whose body writes `['x']`, and `hN` at `['x']` throwing
|
|
803
|
+
`new Error('boom-in-nested-listener')`. **THE TRACE WAS `["A enters", "N throws", "A: its own write THREW
|
|
804
|
+
Error{name:'Error',message:'boom-in-nested-listener'} (=== nested error: true)", "A leaves"]`, AND THE ORIGINAL
|
|
805
|
+
`store.write(['a'], 'mem', 1)` THREW THE SAME `Error` — identity `===` true — AND ANSWERED NOTHING.** **SO WHERE
|
|
806
|
+
IT SURFACES IS: FIRST AT `A`'s OWN `write(['x'], …)` INSIDE THE LISTENER, THEN — `A` NOT HAVING CAUGHT IT — OUT
|
|
807
|
+
THROUGH THE OUTER DELIVERY INTO THE ORIGINAL CALLER.** **YES: A DEPTH-TWO THROW REACHES THE ORIGINAL CALLER, AS
|
|
808
|
+
THE SAME ERROR OBJECT AT EVERY HOP, AND LEAVES THE ORIGINAL WRITE UNANSWERED.** **AND THE STOP IS YOURS TO PLACE:
|
|
809
|
+
with `A`'s body catching instead, the measured trace was `["A enters", "N throws", "A caught Error{…} (===
|
|
810
|
+
nested error: true)", "A leaves"]` and the original write ANSWERED ITS OWN RECORD with `threw: false`** — **so
|
|
811
|
+
the DEPTH is invisible in the error unless the listener adds it, and a caller that must know where a chain failed
|
|
812
|
+
annotates the throw itself.** (`D-73`; `D-72`; `§6.5a`; `docs/specs/storeroom-js/contract.md §2.7`'s clause of
|
|
813
|
+
the same date.)
|
|
814
|
+
|
|
815
|
+
**WHAT THAT MEANS FOR YOU, IN ONE SENTENCE: if you write a listener that writes, you have moved the order of your
|
|
816
|
+
own effects out of the record and into the call stack, and the only thing you can still hold is the depth you
|
|
817
|
+
yourself refuse to exceed.** (`D-72`; `D-71`; `D-70`; `D-45`; `DD-37`; `§7.9`.)
|
|
818
|
+
|
|
588
819
|
---
|
|
589
820
|
|
|
590
821
|
## §7 · Constraining: the pair, the handle, and the two routes
|
|
@@ -751,6 +982,12 @@ body.**
|
|
|
751
982
|
|
|
752
983
|
**⟶ AND THE PASS ASKS EXACTLY THE PAIRS STANDING AT THE ADDRESS IT WRITES, 2026-10-28 — `D-50`'S CONSEQUENCE, WHICH NO CLAUSE ABOVE SPELLS OUT.** `RCA-8(g)` ANNOTATE-BESIDE: **every clause of §7.6 above stands BYTE-FOR-BYTE and is read THROUGH this one; this clause adds one declaration and moves nothing it prints.** **THE RULE: A WRITE'S CONSTRAINT PASS JUDGES THE PAIRS STANDING AT THE ADDRESS IT WRITES — the kernel's own `pairsAtAddress` reads the pair record at that one address — SO A PAIR AT A DIFFERENT ADDRESS IS NOT ASKED.** **If a constraint cares about another location it READS it** (`D-50`, the architect, verbatim: *"If a constraint needs to look for the data at a given location, it can read()"*). **MEASURED 2026-10-28, on this checkout, through the published entry, in a two-address drive:** a pair `b1` registered at `['b']`; **a write at `['a']` asked `b1` `0` times**; **the same write at `['b']` asked it once**, and the value the body was handed was the one that write returned. **So an action one address away is INVISIBLE to your pair** — it was never asked at the address the action moved — **and the body's own `store.read` is the only thing that makes it visible.** **That is the whole reason this section tells you to read what you care about: the pass will not ask a pair that does not stand where you wrote.** (`held-listener/contract.md §2.8` item `1`, `HL-8`: *"the pass selects the rows whose address IS the triggering address"*, preserving the EXACT-ADDRESS rule rather than widening it; `D-50`; §7.1–§7.5's declarations; the pass's own scan at `bindings/js/src/entry/kernel-foundations.mjs:782`.)
|
|
753
984
|
|
|
985
|
+
**⟶ AND THE PASS'S SET IS NOW A PREFIX SET, 2026-10-29 — `D-56`, WHICH SUPERSEDES THE CLAUSE ABOVE.** `RCA-8(g)` ANNOTATE-BESIDE: **the paragraph above stands BYTE-FOR-BYTE as the rule of the build that printed it, and it is READ THROUGH this one.** **THE ARCHITECT, VERBATIM** (`strata/decisions.md` · `D-56`): *"A listener/constraint should fire when the descendants of its targeted address node change, because the descendants become part of the object that is read from that address"*. **SO A PAIR AT `['tabs']` IS ASKED FOR A DELETE AT `['tabs','landingPage']`** — the value read at `['tabs']` CONTAINS the subtree the delete moved, so your pair judges an object that moved — **and a pair at `['tabs','landingPage']` is still asked for that same delete.** **WHAT IS STILL NOT ASKED IS THE OTHER DIRECTION**, which the ruling does not cover: a delete at `['tabs']` does not ask the pair at `['tabs','landingPage']`, and the object that pair judges then reads as ABSENT. **THE MEMBERSHIP RULE, STATED ONCE: your pair is asked when THE ACT'S ADDRESS IS AT OR BELOW YOUR PAIR'S ADDRESS.** **AND WHAT YOUR PAIR IS HANDED DID NOT MOVE**: the context carries THE ACT'S `address`, `tier` and `value` — so a pair standing at an ancestor is handed the value that landed AT THE ACT'S ADDRESS (absence, for a delete), **not** the object at its own. **A PAIR THAT JUDGES ITS OWN ADDRESS READS IT** (`§7.6`'s `read()` rule, which `D-56` leaves standing — it is no longer NECESSARY for an ancestor's interest in a descendant, and it is still correct for the pair's own object). **MEASURED 2026-10-29, on this checkout, through the published entry** (`tests/e2e/discard-witness.mjs`'s `DW-10.13`/`DW-10.15`/`DW-10.16`): a pair at `['window','tabs']` and a delete at `['window','tabs','landingPage']` — **the pair IS asked (2 asks: its registration's and the pass's), its refusal crosses, and the delete rolls back** (`window.tabs` reads `{"landingPage":{"active":true},"settings":{"active":false}}` again); a pair AT the act's own address is asked once by the pass, unchanged; a pair at the act's DESCENDANT is asked `0` times and its address reads ABSENT afterwards.
|
|
986
|
+
|
|
987
|
+
**⟶ AND THE DESCENDANT DIRECTION IS RULED TOO, 2026-10-29 — `D-66`, AND THE TWO CLAUSES ABOVE STAND BYTE-FOR-BYTE AND ARE READ THROUGH THIS ONE.** `RCA-8(g)` ANNOTATE-BESIDE: **this clause completes the membership rule; it moves no clause above it and no figure of this section.** **THE ARCHITECT, VERBATIM** (`strata/decisions.md` · `D-66`): *"A pair at a descendant should only be asked if the change also affects that descendant, ex. write(['a', 'b'], {'c': "foo", 'd': "bar"} should trigger at ['a'], ['a', 'b'], ['a', 'b', 'c'], and ['a', 'b', 'd'], but not ['a', 'b', 'e']"*. **SO THE MEMBERSHIP RULE IS NOW: your pair is asked when THE ACT CHANGES THE VALUE AT YOUR PAIR'S ADDRESS — which means the act's address is at or below yours (`D-56`, unmoved), OR your pair's address is one the act TOUCHED.** **WHAT "TOUCHED" MEANS, IN THE TWO VERBS YOU DRIVE:** **a `write` touches exactly the paths its value contains** — write `{c:'foo', d:'bar'}` at `['a','b']` and the pairs at `['a','b','c']` and `['a','b','d']` ARE asked, while the pair at `['a','b','e']` is NOT, **because the write did not touch `e`** (it stands neither before nor after the act) — and a value with a nested object touches its own sub-paths by the same rule one level down, an array its own indices, a scalar nothing below itself; **a `delete` touches the WHOLE SUBTREE at its address**, because a descendant of a deleted node is ABSENT after the act and was not before, **which is a change to what a read there returns** — so a delete at `['window','tabs']` DOES ask a pair standing at `['window','tabs','settings']`. **AND WHAT YOUR PAIR IS STILL NOT ASKED FOR IS A DESCENDANT THE ACT DID NOT TOUCH**, which is the whole of the distinction: it is not "descendant", it is "touched". **ONE THING YOUR PAIR IS HANDED, ON EVERY ARM**: the context carries THE ACT'S `address`, `tier` and `value` — so a pair standing at a touched descendant is handed the value the act landed AT THE ACT'S ADDRESS (absence, for a delete), **not** the object at its own; **a pair that judges its own address READS it** (`§7.6`'s own rule, which stands). **MEASURED 2026-10-29, on this checkout, through the published entry** (`tests/e2e/discard-witness.mjs`'s `DW-13.1`/`DW-13.2`/`DW-13.3` and `DW-10.16`): the ruling's own drive asks `['a']`, `['a','b']`, `['a','b','c']` and `['a','b','d']` and NOT `['a','b','e']`; a nested value asks its own sub-paths and not a sibling it did not write; a delete at `['window','tabs']` asks the pair at `['window','tabs','settings']`; **and the pass's set is still ONE snapshot, FIXED WHEN THE PASS BEGINS and in ONE registration order across the ancestors, the act's own address and the touched descendants** (a pair registered during a pass enters the next one and no earlier one).
|
|
988
|
+
|
|
989
|
+
**⟶ AND A WRITE'S TOUCHED SET IS NOW BOTH VALUES', 2026-10-29 — `D-66` READ SYMMETRICALLY, AND THE CLAUSE ABOVE STANDS BYTE-FOR-BYTE AND IS READ THROUGH THIS ONE.** `RCA-8(g)` ANNOTATE-BESIDE: **the one sentence this clause supersedes is that clause's bullet *"a `write` touches exactly the paths its value contains"*, named here rather than overwritten; the `delete` bullet and every other figure of this section are unmoved.** **THE GAP, MEASURED BEFORE IT WAS CLOSED: A WRITE THAT REPLACES A NODE DROPS THE SUB-PATHS THE OLD VALUE CARRIED.** Write `{c: 1}` over `{c:{x:1}}` and the path `['a','b','c','x']` stands PRESENT before the act and ABSENT after it — **a change to what a read there returns** — **and `x` is in neither the written value's path set nor the word "descendant", so the pair standing there went unasked.** **THE CRITERION THAT CLAUSE ALREADY NAMES DECIDES IT WITHOUT A NEW RULE** — *"your pair is asked when the act changes the value at your pair's address"* — **and a path that DISAPPEARS changed as surely as a path that APPEARS.** **SO THE WRITE ARM IS SYMMETRIC: your pair is asked when the act touched the value it WROTE *or* the value it REPLACED** (`op.before.value`, the state the store's own journal carries), **and the touched set is the union of the two path sets** — a path in both being yours once, not twice. **THE `delete` ARM DOES NOT MOVE**: the whole subtree it removed, exactly as the clause above states it. **AND A FIRST WRITE AT AN ADDRESS HAS NO BEFORE VALUE AND NEEDS NONE**: `op.before` is present on every op and answers `present: false` where nothing stood, so that write's union is the written value's path set exactly and nothing throws. **`['a','b','e']` IS STILL NOT ASKED**, for the reason the clause above gives: `e` stands in neither value, so neither half of the union touches it. **MEASURED 2026-10-29, ON THIS CHECKOUT, THROUGH THE PUBLISHED ENTRY** (`tests/e2e/discard-witness.mjs`'s `DW-13.5` and `DW-13.6`, with `DW-13.1`–`DW-13.4` still green): a write of `{c:1}` over `{c:{x:1}}` at `['a','b']` now ASKS the pair at `['a','b','c','x']` and does NOT ask `['a','b','c','z']` or `['a','b','y']`, which stand in neither value; the first write at an empty `['f']` asks `['f','g']` and `['f','g','q']` and not `['f','h']`; **and the pass's set is still ONE snapshot, FIXED WHEN THE PASS BEGINS, in ONE registration order.** (`D-66`; `D-56`; §7.6's touched-set clause of the same date; `bindings/js/src/entry/kernel-foundations.mjs`'s `touchedAddresses`.)
|
|
990
|
+
|
|
754
991
|
### §7.7 ★ The check is BOUNDED — THREE restarts per terminal act, and past the budget it fails closed
|
|
755
992
|
|
|
756
993
|
**★ THE LIMIT, DECLARED HERE BECAUSE NO CLAUSE OF THIS GUIDE SAID IT: A CONSTRAINT CHECK RESTARTS AT MOST THREE TIMES PER TERMINAL ACT.** The bound is **`RESTART_LIMIT` = `3`**, one exported constant at **`bindings/js/src/entry/constraints.mjs:1366`**. **THE BUDGET IS SPENT AND NOT RESET by an action raised inside a restart** — a body that reverses again inside a restart's own ask spends the SAME budget and not a fresh one — **AND PAST IT THE CHECK FAILS CLOSED IN THE ONE FORM:** the write throws the same plain `{status, message, log}` it throws for a refused pass (§8), with `status` **`'ConstraintPassRefused'`** and **ONE log entry** whose message says the budget was spent rather than that a pair refused a state it never judged. **The store is restored** — the request's own watch records what is owed — so the terminal act leaves no unjudged effect standing (`D-42` item `3`; `D-41` item `4`).
|
|
@@ -766,6 +1003,215 @@ body.**
|
|
|
766
1003
|
|
|
767
1004
|
**WHAT DOES NOT SPEND THE BUDGET**, so a lawful in-commit action costs at most one round and no more: **a request whose watch shows no moved address is consumed and asks nothing** — it can neither loop nor exhaust the budget — and **the count belongs to the PASS's own scope**, so a nested pass (a listener body's own write) has a scope and a budget of its own, exactly as it has its own commit (`js/contract.md §2.6`'s clause of 2026-10-27, item `4`).
|
|
768
1005
|
|
|
1006
|
+
**⟶ AND WHEN TWO OF YOUR LISTENERS EACH REVERSE IN ONE COMMIT, THE CHECK JUDGES THE STATE THE COMMIT STANDS AT — BOTH RAISES, NOT THE LAST ONE, 2026-10-28 — `D-55` ITEM 6 · `DEF-AP`, MEASURED.** `RCA-8(g)` ANNOTATE-BESIDE: **every clause of §7.7 above stands BYTE-FOR-BYTE and is read THROUGH this one; the bound, the budget's own arm and `D-42`'s window rule do not move.** **WHAT WAS MEASURED ON `1.3.0`:** two listeners each reversing inside ONE commit left **only the LAST raise judged** — the earlier listener's violation was asked **zero** times, **registration order decided which raise was lost**, and when the survivor's judgement passed **the write returned normally with the violation standing**. **THE ROW'S OWN ESCAPE — declare that an overwritten raise may leave a violation unjudged — IS UNAVAILABLE**, because §5.4's safe-state rule is the promise. **WHAT THE PACKAGE DOES: the request's watch is MERGED to the EARLIEST reading of each address** — the state the commit opened at — **so the restarted check sees every address the WHOLE commit moved, whichever raise named it, and asks the pairs at the state the commit STANDS at.** **A raise is therefore never lost to a later one in the same commit**, and the write that used to answer normally now refuses. (`tests/e2e/discard-witness.mjs`'s `DW-10.7`: both pairs are asked at the drain, the write refuses `ConstraintPassRefused`, the whole commit is undone; under a revert of the merge the earlier pair is asked ZERO times and the write ANSWERS.)
|
|
1007
|
+
|
|
1008
|
+
### §7.8 ★ A `delete` IS AN EDITING VERB — it passes constraints, and its listeners run AFTER them
|
|
1009
|
+
|
|
1010
|
+
**★ FROM 2026-10-29 A `delete` RUNS A CONSTRAINT PASS OF ITS OWN** (`D-55` item `3`, verbatim: *"A delete is an
|
|
1011
|
+
editing verb and must pass constraints. This includes all of the relevant commit and message logic."*). **`D-38`
|
|
1012
|
+
said the same for `rollback`** (item `4`: *"A reverse is an editing verb and must be treated as above"*). **So the
|
|
1013
|
+
three editing verbs — `write`, `delete`, `rollback` — all ask the pairs, and only a `read` does not (§7.5).**
|
|
1014
|
+
|
|
1015
|
+
**What you see, in order, when you call `store.delete(addr, tier?)`:**
|
|
1016
|
+
|
|
1017
|
+
1. **the delete lands** — the versions are removed exactly as they always were (`§5.3`);
|
|
1018
|
+
2. **the pairs standing AT THE DELETED ADDRESS are asked against THE STATE THE DELETE LEFT** — the address's
|
|
1019
|
+
absence, with the delete's own journal op handed to the pair as `context.op`, so a constraint can tell a
|
|
1020
|
+
delete from a write and refuse one;
|
|
1021
|
+
3. **every one of them passes ⇒ you get the record** — `{ address, tiers, triggered, op }` (`§3.3`), unchanged;
|
|
1022
|
+
**one refuses ⇒ you get the ordinary refusal**: the same `{status, message, log}` every constraint refusal
|
|
1023
|
+
throws (§8), `status: 'ConstraintPassRefused'`, its `log` naming the pair and carrying the kernel's token in
|
|
1024
|
+
the terminal entry's `state`, **and the delete is UNDONE together with every edit the delete's own attempt made** (a repair's write included)
|
|
1025
|
+
— the store stands at the state the commit opened at;
|
|
1026
|
+
4. **and the listeners fire AFTER step 2** — so a listener fired by a delete observes a JUDGED state, never one a
|
|
1027
|
+
constraint is about to refuse. **A refused delete fires no listener at all.**
|
|
1028
|
+
|
|
1029
|
+
```js
|
|
1030
|
+
store.write(['window','tabs'], 'mem', { landingPage: { active: true } });
|
|
1031
|
+
store.addConstraint({ address: ['window','tabs','landingPage'], id: 'landing-page-must-stand' },
|
|
1032
|
+
(ctx) => (ctx.op !== null && ctx.op.kind === 'delete'
|
|
1033
|
+
? { message: 'the landing page is not removable' }
|
|
1034
|
+
: true));
|
|
1035
|
+
|
|
1036
|
+
store.delete(['window','tabs','landingPage']);
|
|
1037
|
+
// throws { status: 'ConstraintPassRefused', message: 'the landing page is not removable', log: [ … ] }
|
|
1038
|
+
// log[0] = { kind: 'constraint-check', id: 'landing-page-must-stand', opId: 'op-3', state: null, … }
|
|
1039
|
+
// log[1] = { kind: 'outcome', id: null, opId: 'op-3', state: 'KC-3', … }
|
|
1040
|
+
store.read(['window','tabs','landingPage']); // { active: true } — the delete was undone
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
**The pair is asked with `context.value` = the AFTER-state (absence), `context.tier` = the delete's tier or `null`,
|
|
1044
|
+
and `context.attempt` = the attempt's own identity.** **A pair at an ANCESTOR address is not asked by the delete's
|
|
1045
|
+
pass** — the pass keeps the write's exact-address rule and not the trigger set's prefix widening — even though
|
|
1046
|
+
such a listener DOES fire for the delete (`§6.4`). **And a delete that removes nothing is not an edit**: `tiers: []`,
|
|
1047
|
+
no pass, no refusal, no delivery (`§5.3`).
|
|
1048
|
+
|
|
1049
|
+
**⟶ AND THE SECOND OF THOSE THREE SENTENCES IS CORRECTED, 2026-10-29 — `D-56`: THE PAIR AT AN ANCESTOR IS ASKED, AND
|
|
1050
|
+
THE DELETE AT `['window','tabs','landingPage']` IS ASKED OF THE PAIR AT `['window','tabs']`.** `RCA-8(g)`
|
|
1051
|
+
ANNOTATE-BESIDE: **the paragraph above stands BYTE-FOR-BYTE as the rule of the build that printed it and is read
|
|
1052
|
+
THROUGH this clause; the AFTER-state, the `tier`, the attempt identity, the `discarded` witness, the "removes nothing
|
|
1053
|
+
mints nothing" rule and the four steps above are all unmoved.** **THE ARCHITECT, VERBATIM: *"A listener/constraint
|
|
1054
|
+
should fire when the descendants of their targeted address node change, because the descendants become part of the
|
|
1055
|
+
object that is read from that address"*** (`D-56`) — **so the delete's pass asks the pairs standing at the deleted
|
|
1056
|
+
address AND the pairs standing at every ANCESTOR of it, because the value read at an ancestor CONTAINS the subtree the
|
|
1057
|
+
delete moved.** **WHAT YOU SEE, ON THE EXACT TREE `§6.4` USES:** with a pair registered at `['window','tabs']` and a
|
|
1058
|
+
**`delete(['window','tabs','landingPage'])`**, **the pair IS asked** — and **its refusal CROSSES and the delete ROLLS
|
|
1059
|
+
BACK**, so `window.tabs` reads `{"landingPage":{"active":true},"settings":{"active":false}}` again. **MEASURED
|
|
1060
|
+
2026-10-29 on this checkout through the published entry** (`tests/e2e/discard-witness.mjs`'s
|
|
1061
|
+
`DW-10.13`/`DW-10.15`/`DW-10.16`): the ancestor pair is asked **2** times (its registration's evaluation and the
|
|
1062
|
+
pass's), a pair AT the deleted address is asked **once** by the pass and unchanged, **and a pair at a DESCENDANT of
|
|
1063
|
+
the deleted address is asked `0` times and its address reads ABSENT afterwards** — that last direction is the one
|
|
1064
|
+
`D-56` does not cover and this guide does not claim. **THE MEMBERSHIP RULE, ONCE: your pair is asked when THE ACT'S
|
|
1065
|
+
ADDRESS IS AT OR BELOW YOUR PAIR'S ADDRESS.** **AND WHAT YOUR PAIR IS HANDED DID NOT MOVE**: the ACT's `address`, the
|
|
1066
|
+
ACT's `tier` and the value that landed at the ACT's address (absence, for a delete) — **not** the object standing at
|
|
1067
|
+
its own address — so **a pair that wants its OWN object reads it** (`§7.6`'s `read()` rule, which `D-56` leaves
|
|
1068
|
+
standing and which is no longer NECESSARY for an ancestor's interest in a descendant). **So the "exactly one tab is
|
|
1069
|
+
active" constraint `§6.4` writes now judges the delete as well as the write — and a delete that would leave a state
|
|
1070
|
+
your pair refuses is refused at the delete.** (`D-56`; §7.6's prefix-set clause of the same date; `HL-8` item 1 and
|
|
1071
|
+
attempt-lifecycle `§2.4` item 5 (iv), annotated at their own homes and not rewritten.)
|
|
1072
|
+
|
|
1073
|
+
**Why you care: a delete cannot be used to slip a state past a constraint you registered, and a listener you write
|
|
1074
|
+
for the use case in `§6.4` — "exactly one tab is active" — now runs on a state the pass has already approved.**
|
|
1075
|
+
**WHAT THE WITNESS STILL SPEAKS FOR** (§8's `record.discarded`): a `rollback` your listener raises inside the
|
|
1076
|
+
DELIVERY is still after every check, so it is reported and its effect stands — but a `rollback` a PAIR BODY raises
|
|
1077
|
+
inside the delete's own pass is now drained and judged at the state the delete left (`§7.7`).
|
|
1078
|
+
|
|
1079
|
+
**MEASURED, ON THIS CHECKOUT, THROUGH THE PUBLISHED ENTRY:** a pair at the deleted address is asked **twice** —
|
|
1080
|
+
its registration and the delete's pass — where before this clause it was asked **once**; a listener fired by a
|
|
1081
|
+
delete reads the pass's ask already taken (`1`, where it read `0`) and the after-state the pass approved; a
|
|
1082
|
+
refused delete leaves the store at the state it opened at; and the delete's log is the commit's ONE list with
|
|
1083
|
+
`seq` `1..2` (`tests/e2e/discard-witness.mjs`'s `DW-10.8`–`DW-10.13`).
|
|
1084
|
+
|
|
1085
|
+
**⟶ AND TWO READINGS OF THAT MEASUREMENT SENTENCE ARE THE READINGS OF THEIR OWN DATE, 2026-10-29 — THE POSITION IS
|
|
1086
|
+
THE ARRAY INDEX (`D-65`) AND A REFUSED DELETE RESTS AT THE LAST VALIDATED STATE (`D-63`).** `RCA-8(g)`
|
|
1087
|
+
ANNOTATE-BESIDE: **every clause of §7.8 above stands BYTE-FOR-BYTE and is read THROUGH this one; the asks, the
|
|
1088
|
+
after-state, the witness and the four steps are unmoved — two figures and one noun move.** **(i) *"the delete's log is
|
|
1089
|
+
the commit's ONE list with `seq` `1..2`"* IS READ AS: the commit's ONE list, TWO entries, standing at INDEX `0` and
|
|
1090
|
+
`1`** — the `seq` member is gone from `LogEntry` (`D-65`; §8.0a's retirement clause below), so `log[0]` is the
|
|
1091
|
+
`constraint-check` entry and `log[1]` the `outcome` entry, and the position is where an entry stands in the array.
|
|
1092
|
+
**(ii) *"a refused delete leaves the store at the state it opened at"* IS READ AS: at THE LAST VALIDATED STATE** —
|
|
1093
|
+
which is the state it opened at whenever the delete's sweep never passed, and the state a passing sweep approved
|
|
1094
|
+
otherwise (`D-63`; §5.4's `D-63` clause). **AND THE THIRD THING THIS CLAUSE OWES, WHICH NO EARLIER CLAUSE OF §7.8
|
|
1095
|
+
NAMES: WHAT THE DELETE'S RECEIPT CARRIES AND WHAT FOLLOWS FROM IT.**
|
|
1096
|
+
- **THE RECEIPT.** `delete` still ANSWERS `{address, op, tiers, triggered}` (`index.d.ts`, `DeleteRecord`), and the
|
|
1097
|
+
**non-enumerable `discarded` witness** still rides it unlisted (`§8`). **⟶ AND BESIDE BOTH, FROM THIS DATE, A
|
|
1098
|
+
NON-ENUMERABLE `commit` MEMBER** — **the same four-member `CommitRecord` a write's `K-2` mints** (`attempt`, `op`,
|
|
1099
|
+
`triggered`, `pending`). **`Object.keys(receipt)` still shows the record's own members and not this one**, so a
|
|
1100
|
+
consumer that copies a receipt loses the commit as it loses the witness — **read it off the receipt itself.**
|
|
1101
|
+
- **ADOPTION.** The entry **ADOPTS** that record: it stands the delete's own receipt in the leg slot for the pass and
|
|
1102
|
+
**restores the enclosing leg in a `finally`**, so a refusal raised inside the delete's pass reports the DELETE's
|
|
1103
|
+
record and not an enclosing write's.
|
|
1104
|
+
- **THE `pending` RESIDUE.** The delete's `pending` is **the returned set MINUS the handles the delete's own pass
|
|
1105
|
+
checked off** — on a passing delete, empty (`D-56`; §6.2's clause) — **and the delivery fires THE RESIDUE**, not the
|
|
1106
|
+
whole returned set.
|
|
1107
|
+
- **`K-4` AND `K-5` BY REFERENCE.** Both readers now answer a delete's attempt **BY REFERENCE** where they refused
|
|
1108
|
+
`NO-REF` before — so `K.log(store, receipt.commit.attempt)` and `K.report(store, receipt.commit.attempt)`
|
|
1109
|
+
reach the delete's own attempt once it has passed, and not only through a refusal's `log` — and **both still
|
|
1110
|
+
refuse a LIVE attempt with `KC-7`** (`D-58`; §8.6's clause). **`receipt.commit` is `null` on the arm that removed
|
|
1111
|
+
nothing** (`D-3`), so guard it before you reach through it.
|
|
1112
|
+
- **THE SAVE.** The `file`-tier save is the delete's own op, on the passing arm — **§9.3's clause below is its home
|
|
1113
|
+
and this bullet does not restate its scope.**
|
|
1114
|
+
**AND NO KERNEL ITEM IS OWED FOR ANY OF IT: the verb is the surface, the kernel's set stays `K-1`…`K-6`, and a `K-7`
|
|
1115
|
+
`commitRemoval` would be owed only if you could drive a delete's attempt directly — you cannot** (`D-57`'s second
|
|
1116
|
+
half; `D-3`; `D-44`; `D-53`).
|
|
1117
|
+
|
|
1118
|
+
### §7.9 ★ WHICH KIND — two different jobs, and why a derived value is not an address
|
|
1119
|
+
|
|
1120
|
+
**★ THIS IS WHERE THE TWO SURFACES MEET, AND IT IS THE SECTION TO READ BEFORE YOU REGISTER ANYTHING.** `§6`'s
|
|
1121
|
+
listener and this section's constraint stand over the SAME address domain and are named in the SAME `triggered`
|
|
1122
|
+
list (`§6.2`'s `D-69` clause: *an element that is a constraint's `id` is ASKED, a listener's is DELIVERED*), and
|
|
1123
|
+
**they are not interchangeable.** **Three rules, each with its reason.**
|
|
1124
|
+
|
|
1125
|
+
1. **★ WHICH KIND. IF YOUR CALLBACK CHANGES THE STORE, IT IS A CONSTRAINT OR A REPAIR; IF IT TELLS THE WORLD, IT IS
|
|
1126
|
+
A LISTENER** — **updating external code with new data, changing the rendered information on screen.**
|
|
1127
|
+
**THE REASON, STATED: a constraint runs INSIDE the pass, its answer is JUDGED — a `{message}` is a refusal that
|
|
1128
|
+
rolls the commit back (`§5.4`) — and it HAS LOOP GUARDS**: the restart budget (`RESTART_LIMIT` `3`, `§7.7`), the
|
|
1129
|
+
applied-repair marker (an applied repair never runs twice, `P-SM-6`), and the per-op floor. **A listener runs
|
|
1130
|
+
AFTER every constraint, opens its OWN commit when it writes, and HAS NO LOOP GUARD** — nothing counts its
|
|
1131
|
+
writes, nothing marks its repair, and its write is a unit of its own (`D-53`) — **so a listener that writes as a
|
|
1132
|
+
matter of course drives a chain the machinery does not bound.** **The loop guard exists only inside the pass;
|
|
1133
|
+
that is the whole reason a data-changing callback belongs there.** (`D-70`, the architect, verbatim: *"Functions
|
|
1134
|
+
that automatically produce changes in the stored data should be written as constraint/repair, not as listeners,
|
|
1135
|
+
because constraint/repair has loop guards while listeners don't"* — and *"The use case for listeners is for
|
|
1136
|
+
updating external code with new data, ex. changing the rendered information on screen"*.)
|
|
1137
|
+
2. **★ A DERIVED VALUE IS NOT AN ADDRESS. AN INDEX INSIDE A COLLECTION IS DERIVED.** **`delete(['v',0])` RENUMBERS
|
|
1138
|
+
the survivors in container order from `0` (`§6.4`'s closing clause), so `['v',1]` stops meaning what it meant:
|
|
1139
|
+
the node that stood there stands at `['v',0]` afterwards and a DIFFERENT node stands at `['v',1]`.** **So a
|
|
1140
|
+
registration at an index is a registration on a value the store never wrote** — it names a POSITION, and the
|
|
1141
|
+
store writes LOCATIONS. **THE RULE: a constraint or a listener that cares about positional membership REGISTERS
|
|
1142
|
+
AT THE CONTAINER — `['v']` — AND READS WHAT IT CARES ABOUT** (`D-50`'s `read()` rule, `§7.6`; `D-70`, verbatim:
|
|
1143
|
+
*"A constraint/listener that cares about a derived value like the vec index should listen to the vec, not the
|
|
1144
|
+
contained node"*).
|
|
1145
|
+
3. **AND THE CONSEQUENCE A CONSUMER WILL OTHERWISE DISCOVER: A REGISTRATION AT AN INDEX WILL NOT FIRE WHEN ITS
|
|
1146
|
+
NEIGHBOURS MOVE — AND THAT IS NOT A DEFECT, BECAUSE NOTHING WAS WRITTEN AT THAT ADDRESS.** **`D-70`, verbatim:
|
|
1147
|
+
*"a listener at `['v','1']` does not hear it — correctly … A registration at a DERIVED address is a
|
|
1148
|
+
registration on a value the store never wrote … the `triggered` list is right to be empty, because nothing at
|
|
1149
|
+
`['v','1']` was written."*** **The act's address was `['v',0]`, the anchor it removed was at `['v',0]`, and no
|
|
1150
|
+
op of the store carries `['v',1]` as its address** — so there is no firing to owe. **The store owes you the
|
|
1151
|
+
CONTAINER's firing; it does not owe you a firing for a renumbering.** **And a registration at an index is not
|
|
1152
|
+
the only way to get this wrong — a listener whose job is to change data is the other half of rule 1.**
|
|
1153
|
+
|
|
1154
|
+
**MEASURED 2026-10-29, ON THIS CHECKOUT, THROUGH THE PUBLISHED ENTRY** (`tests/e2e/discard-witness.mjs`'s
|
|
1155
|
+
`DW-15.1`/`DW-15.2`/`DW-15.3`; the drive is `store.write(['v'], 'mem', [{b:1}, {b:2}, {b:3}])` and then
|
|
1156
|
+
`store.delete(['v',0])`, so `{b:2}` moves from `['v',1]` to `['v',0]` and `{b:3}` from `['v',2]` to `['v',1]`):
|
|
1157
|
+
|
|
1158
|
+
- **THE LISTENER AT `['v']` FIRES, ONCE**, is handed the ACT's address `['v',0]`, and **the collection it reads
|
|
1159
|
+
inside its body is the RENUMBERED one** — `[{"b":2},{"b":3}]`, the value it cares about, read at the address that
|
|
1160
|
+
is not derived; **and the record's `triggered` names its handle at `['v']`, the address it was bound at.**
|
|
1161
|
+
- **THE LISTENER AT `['v',1]` DOES NOT FIRE** — `0` invocations, its handle nowhere in `triggered` — **and the
|
|
1162
|
+
cell asserts `0` as CORRECT, citing `D-70`, not as a defect**: `['v',1]` named `{"b":2}` before the act and
|
|
1163
|
+
names `{"b":3}` after it, so the renumber moved a DIFFERENT node under the position it registered.
|
|
1164
|
+
- **THE CONSTRAINT AT `['v']` IS ASKED FOR THE SAME DELETE** — once by the pass — and it is handed **THE ACT's
|
|
1165
|
+
`address` (`['v',0]`), `tier` and `value`**: the value the delete left AT THE ACT'S ADDRESS, which under a
|
|
1166
|
+
renumbering is **the survivor that moved into index `0`, `{"b":2}`, and NOT the collection.** **The collection it
|
|
1167
|
+
JUDGES is the one its body reads at `['v']`** (`D-50`): the renumbered `[{"b":2},{"b":3}]`. **Its registration's
|
|
1168
|
+
own ask is the one with no attempt, and that ask's `value` IS the collection at `['v']`** — so a container pair
|
|
1169
|
+
is asked at the container and reads at the container, whichever ask it is answering. **Its `id` stands in the
|
|
1170
|
+
same `triggered` list as the two listener handles.**
|
|
1171
|
+
|
|
1172
|
+
**EACH CELL BITES UNDER A `/tmp` REVERT, AND THE REVERT IS THE ONE THIS RULE NAMES — THE CONTAINER REGISTRATION
|
|
1173
|
+
REPLACED BY AN INDEX ONE:** `['v']` → `['v',1]` at the listener turns `DW-15.1` RED (no firing, no collection read)
|
|
1174
|
+
and at the pair turns `DW-15.3` RED (the pass asks it `0` times); the mirror move — the index listener's `['v',1]`
|
|
1175
|
+
→ `['v']` — turns `DW-15.2` RED (the `0` becomes a `1`). **So each arm is held by the registration's ADDRESS and by
|
|
1176
|
+
nothing else in the drive.**
|
|
1177
|
+
|
|
1178
|
+
**AND WHAT `D-70` DOES NOT CHANGE, SAID PLAINLY: no member, status, kernel item or bound moves.** **This section
|
|
1179
|
+
declares a rule of USE over a surface that was already measured — the seventh residual closes by DECLARATION, not by
|
|
1180
|
+
machinery** (`D-70`'s own terms; `§3.1`'s fourteen members; `§8.1`'s twenty statuses; `§7.7`'s budget).
|
|
1181
|
+
|
|
1182
|
+
**★ AND THE FOUR THINGS THIS SECTION STILL LEFT A CONSUMER TO DISCOVER, WRITTEN DOWN 2026-10-29 — `D-66` READ
|
|
1183
|
+
BESIDE `D-70`, AND EVERY SENTENCE ABOVE STANDS BYTE-FOR-BYTE AND IS READ THROUGH THIS CLAUSE.** `RCA-8(g)`
|
|
1184
|
+
ANNOTATE-BESIDE: **this clause declares no member, no status, no kernel item and no bound, and it moves no figure
|
|
1185
|
+
of `§3.1`, `§8.1` or `§7.7`. Each of the four is a reading this surface leaves you to DERIVE unless it is
|
|
1186
|
+
stated — none of them is a rule the surface lacks, and that is exactly why they have to be written.**
|
|
1187
|
+
|
|
1188
|
+
1. **★ THE RECONCILIATION, IN ONE CLAUSE: THE REACH IS PER ADDRESS THE STORE WROTE, NOT PER POSITION.** **`D-66`'s
|
|
1189
|
+
sentence — *"a pair is asked when the act changes the value at its address"* — is a criterion over the
|
|
1190
|
+
addresses the act WROTE and the addresses it REPLACED, and `D-70`'s — *"nothing was written at `['v',1]`"* —
|
|
1191
|
+
is that same criterion read at an address the act never wrote.** **The two collide only ON THEIR FACE, and the
|
|
1192
|
+
noun reconciles them: a `VEC` renumber DOES change what a read at an index returns, but AN INDEX'S *MEANING*
|
|
1193
|
+
CHANGING IS NOT THE STORE *WRITING* IT.** **The store wrote `['v',0]` and replaced nothing at `['v',1]`, so it
|
|
1194
|
+
owes you a firing at the one address it wrote and none at the other** — which is rule 3 above, measured at
|
|
1195
|
+
`DW-15.1`/`DW-15.2`.
|
|
1196
|
+
2. **★ THE MISSING LOOP GUARD IS AN ABSENCE, AND THIS IS WHAT IT MEANS.** **Nothing observably bounds a chain of
|
|
1197
|
+
listener writes: no budget, no depth, no counter and NO INSTRUMENT YOU CAN READ.** **The constraint side is
|
|
1198
|
+
different for one reason and not for a better one: THE GUARDS ARE THE PASS'S, and a listener does not run in a
|
|
1199
|
+
pass** — the restart budget (`RESTART_LIMIT` `3`), the applied-repair marker and the per-op floor all belong to
|
|
1200
|
+
the pass, and a listener is delivered after it. **SO YOU BOUND YOUR OWN CHAIN, in your own code, with your own
|
|
1201
|
+
count, because the package will not.** **And the practical shape is a sequence rather than a defect: a listener
|
|
1202
|
+
that writes is a listener whose writes OPEN COMMITS, whose commits RUN CONSTRAINTS, whose constraints FIRE
|
|
1203
|
+
LISTENERS — the machine follows that as far as your code takes it and stops only when your code stops.**
|
|
1204
|
+
3. **★ A CALLBACK THAT BOTH WRITES AND RENDERS IS NOT REFUSED.** **The two kinds are A RULE OF USE, not an enforced
|
|
1205
|
+
classification.** **The package registers such a function, delivers it as a listener, and lets its write go
|
|
1206
|
+
UNGUARDED — nothing inspects a listener's body for a store call, and no refusal names the mix.** **So a consumer
|
|
1207
|
+
who assumes the package enforces the split will build the chain of item 2 believing it cannot happen.**
|
|
1208
|
+
4. **WHAT MOVED IS NOT REPORTED.** **The delivery's one argument is the ACT's address** — `['v',0]`, the address the
|
|
1209
|
+
store wrote — **and not what shifted under it.** **So a container listener that must know which position moved
|
|
1210
|
+
DIFFS THE COLLECTION ITSELF**, inside its own body, at the container (`D-50`'s `read()` rule). **The reason is
|
|
1211
|
+
not an omission: the act's address is THE STORE'S OWN DATUM** — it stands on every op and on every record
|
|
1212
|
+
`§6.4` declares — **whereas WHICH POSITION MOVED IS THE CALLER'S QUESTION, asked against state the caller chose
|
|
1213
|
+
to keep.**
|
|
1214
|
+
|
|
769
1215
|
---
|
|
770
1216
|
|
|
771
1217
|
## §8 · Refusals
|
|
@@ -811,9 +1257,10 @@ commit does — the constraint passes that judged its value, a repair's own writ
|
|
|
811
1257
|
and the terminal arm's `outcome` entry — **appends to ONE array**. Consequences you can rely on, each measured
|
|
812
1258
|
on this checkout:
|
|
813
1259
|
|
|
814
|
-
-
|
|
815
|
-
and there is one list
|
|
816
|
-
|
|
1260
|
+
- **the entries run `0..n-1` CONTINUOUSLY across those steps**, because the position of an entry in the list
|
|
1261
|
+
**IS the array index** and there is one list (`D-65`; the `seq` member that used to restate it is gone). A
|
|
1262
|
+
repair's `commit` entry can stand in the middle of the list, between the refused pass's entries and the
|
|
1263
|
+
re-run's: **it is a real entry of the same commit, not a second log.**
|
|
817
1264
|
|
|
818
1265
|
**⟶ AND WHAT `seq` IS TRUE OF, SAID PLAINLY, 2026-10-28 — `DEF-AI`, MEASURED.** **`seq` is the position of the
|
|
819
1266
|
entry in the commit's own message log; it is not the store's journal ordinal, and the two differ by design.**
|
|
@@ -827,6 +1274,18 @@ CLAIMED: one repair write appends the five entries `constraint-check` `1` · `re
|
|
|
827
1274
|
the repair's own joined write and the re-run** (`tests/e2e/repair-in-commit.test.mjs`'s `D-46` (1) cell, printed).
|
|
828
1275
|
**No entry is ever minted from the store's journal counter**: that would be a second ordinal with no consumer.
|
|
829
1276
|
(`D-53`; `D-48` · `DEF-AI`; `index.d.ts`'s `LogEntry` carries the same sentence.)
|
|
1277
|
+
|
|
1278
|
+
**⟶ AND THE MEMBER THAT SENTENCE WAS ABOUT IS GONE, 2026-10-28 — `D-65`: THE ARRAY INDEX IS THE POSITION.**
|
|
1279
|
+
`RCA-8(g)` ANNOTATE-BESIDE: **every byte above stands BYTE-FOR-BYTE and is read THROUGH this paragraph; what it says
|
|
1280
|
+
of `seq` is RETIRED, not rewritten.** **THE RULING, THE ARCHITECT, ONE WORD: *"Drop it"*** (`strata/decisions.md` ·
|
|
1281
|
+
`D-65`) — `seq: log.length + 1` IS the index of the entry being appended, a second copy of a fact the array already
|
|
1282
|
+
carries, so **the entry carries FIVE members — `kind` · `id` · `message` · `opId` · `state` — and NO `seq`.**
|
|
1283
|
+
**WHAT THAT MEANS FOR YOU, WITHOUT A MEMBER TO READ:** the position of an entry is **`log.indexOf(entry)`**, or
|
|
1284
|
+
the index of the loop you are already in; the order across a commit's attempts is the same order those bullets
|
|
1285
|
+
measured, read off the array; and **`JournalOp.seq` IS UNMOVED** — the store's append ordinal is a different subject
|
|
1286
|
+
and is now impossible to confuse with the commit's, because the commit's no longer has a member to be confused
|
|
1287
|
+
with. **NO OTHER MEMBER, KIND, STATUS, COUNT OR SENTENCE OF THIS GUIDE MOVES** (`bindings/js/index.d.ts`'s
|
|
1288
|
+
`LogEntry` carries the same retirement).
|
|
830
1289
|
- **A nested write a CONSTRAINT body makes is part of the same commit**, so **its entries are in YOUR list** —
|
|
831
1290
|
which is why a constraint body that performs a refused write now shows you that refusal's own
|
|
832
1291
|
`constraint-check` and `outcome` entries, verbatim, before the enclosing `outcome` entry. **That is `DEF-AK`'s
|
|
@@ -855,6 +1314,17 @@ invoked at a REVERSAL's judgement has no commit record standing beside it to nam
|
|
|
855
1314
|
makes still opens a commit of its own and its entries land in that commit's list and not in the reversal's
|
|
856
1315
|
refusal (`js/contract.md §2.6`'s `D-53` clause, item `6`).
|
|
857
1316
|
|
|
1317
|
+
**⟶ AND A THROWN MESSAGE STANDS IN THAT LOG, 2026-10-28 — `D-55` ITEM 1 · `DEF-AK`, MEASURED.** `RCA-8(g)` ANNOTATE-BESIDE: **every clause of §8.0a above stands BYTE-FOR-BYTE and is read THROUGH this one; the list's home, its one-`kind`-per-entry rule and its `seq` ordinal do not move — ⟶ and from 2026-10-28 the ordinal is the ARRAY INDEX and the member is gone (`D-65`), so *"its six members"* above is read as the FIVE-member shape this guide declares at `LogEntry`.** **THE RULING, THE ARCHITECT, VERBATIM: *"Include a thrown message in the message log. Obviously."*** **WHAT `1.3.0` DID, MEASURED: a constraint body that threw its own `Error` crossed `ConstraintBodyThrew` whose `log` carried only the attempt's `outcome` entry — the thrown value's own literal occurred ZERO TIMES in it — while the crossed sentence said *"a constraint body's throwable crosses as this refusal"*, a carriage the log did not make.** **WHAT YOU GET NOW:**
|
|
1318
|
+
- **the value the body threw is PRINTED IN THE LOG**, in the ONE entry the attempt's terminal act appends (the `outcome` entry): *"the constraint `c1` threw; the attempt is closed — the value it threw was …"*. **THE FORM IS NAMED AND NOT SILENTLY STRINGIFIED**: an `Error` (or any object) reads *"an object whose own `message` member is the string "…""*, a thrown string reads *"a string whose text is "…""*, a thrown number *"a number whose value is 42"*, a thrown `void 0` *"no value at all"*, and an object with no `message` *"an object carrying no own string `message` member"*. **Nothing is read off a prototype and no caller code runs to spell it**;
|
|
1319
|
+
- **and the crossed `message` stops claiming the value crosses**: after the store's generated sentence it says, in its own words, that the value the body threw does **NOT** cross as this refusal — and it names where the form it threw stands, which is that `log` entry. **THE CALLER'S OWN TEXT IS QUOTED IN THE LOG AND NEVER IN THE SENTENCE**, so a forged `{status, message}` cannot make the package speak a refusal it did not find (`DEF-A`'s protection, unmoved);
|
|
1320
|
+
- **where no attempt stands there is no commit log to carry it** — a registration-time body's throw crosses with `log: []`, which is `surface.md §2.5` item 4's declared arm — **and the crossed sentence says exactly that instead of pointing at a list that would not hold the form**. (`tests/e2e/discard-witness.mjs`'s `DW-10.3`/`DW-10.4` print the readings for an `Error`, a string and an object with no `message`; both cells are RED under a revert of the terminal sentence.)
|
|
1321
|
+
|
|
1322
|
+
**⟶ AND THE RECORD YOU ARE HANDED IS FINAL, 2026-10-28 — `D-55` ITEM 6 · `DEF-AO`, MEASURED.** `RCA-8(g)` ANNOTATE-BESIDE: **every clause above stands BYTE-FOR-BYTE and is read THROUGH this one; the log's home on the commit, its entries and its identity are unmoved.** **THE RULING, THE ARCHITECT, VERBATIM: *"Attempts don't log. This is what the commits are for. The commits have their log handed off to the client and are dropped after closing out."*** **SO: a closed attempt's report and its log DO NOT MUTATE AFTER THE HANDOFF — a consumer holding a refusal is holding the record, and a record that changes after it is given out is not a record.** **WHAT THE PACKAGE DOES: at the end of the commit's own processing — the restarted check's amendment included, which is part of closing out — the report and the log it carries are `Object.freeze`d**, so a later act can neither append an entry nor re-member the report. **THE LIST IS THE SAME LIST** (`D-53`: *"the crossed report IS that list"*) — the refusal you caught and `report(store, ref)`'s `log` are ONE array, now sealed. **AND THE LIST IS SEALED WITH ITS COMMIT**, never at a nested attempt's own handoff: a constraint body's nested write is part of your commit (`D-53`), so freezing the list from inside it would stop the enclosing attempt's own `append`. (`tests/e2e/discard-witness.mjs`'s `DW-10.6` holds the crossed refusal and the closed report, drives four further acts, and prints the bytes before → after beside `Object.isFrozen` on both; RED under a revert of the seal.)
|
|
1323
|
+
**⟶ AND ITS SECOND HALF IS NOW DELIVERED, 2026-10-28 — `D-58` · THE DROP, AND NOTHING OF A RUNNING COMMIT.** `RCA-8(g)` ANNOTATE-BESIDE: **every clause above stands BYTE-FOR-BYTE and is read THROUGH this one; the seal, the list's identity and the log's home on the commit are unmoved.** **THE RULING, THE ARCHITECT, VERBATIM: *"The client does not need any insight into the current state of a commit/attempt before it completes. Don't expose any of it. After a commit/attempt resolves/rejects, the store does not need to retain the message log or other client-returned data. Don't retain it."*** **WHAT THAT MEANS FOR YOU, IN TWO SENTENCES. (1) A RUNNING COMMIT HANDS YOU NOTHING**: the log reader refuses a live attempt with `KC-7` — the same refusal the report reader already answered — so an in-flight `seq` list is no longer a thing you can read; **and (2) ONCE THE COMMIT RESOLVES OR REJECTS, THE STORE KEEPS NO MESSAGE LOG**: what it goes on holding is the outcome, the ops and the reversal's record, and the list it handed you — the entries on the refusal you caught, or on `report`'s return — is the ONLY copy, sealed at the handoff. **WHAT THIS COSTS, SAID PLAINLY**: on an arm whose handoff carries no list at all — a repair body that throws, and a PASSING `write` (whose answer is `{op, watched, triggered}` and has never carried one) — the entries are no longer readable after the call. **THAT IS THE RULING'S PRICE AND NOT A DEFECT**, and it is why the seal mattered: a record you hold is the record. (`tests/e2e/discard-witness.mjs`'s `DW-11.1`–`DW-11.4` print both readers, the pass's own instant, the retained shape before → after and the single-copy proof; each RED under its own `/tmp` revert.)
|
|
1324
|
+
|
|
1325
|
+
**AND THE RECORD THAT SURVIVES THE HANDOFF HAS FOUR MEMBERS, NAMED HERE BECAUSE THE CLAUSE ABOVE SAYS WHAT IT HOLDS WITHOUT SPELLING ITS SHAPE: `{attempt, ops, outcome, rollback}`** — the attempt's identity, the ops the commit appended, the outcome it reached, and the reversal's record where one ran — **and NO message list.** **A log entry's position is its ARRAY INDEX (`D-65`), so *"an in-flight `seq` list"* above is read as an in-flight ENTRY list**: there is no `seq` member in it and no list to read while the attempt runs. (`D-58`; `D-65`; `bindings/js/src/entry/kernel-terminal.mjs`'s `dropHandoff`, which is where the closed record's four members are written; `D-55` item `6` is the seal they are frozen under.)
|
|
1326
|
+
|
|
1327
|
+
|
|
858
1328
|
**EVERY REFUSAL NAMES ITSELF** — by a `status` from the store's own closed set, and by a **human `message` that
|
|
859
1329
|
carries the address, the domain, and what was found instead.** The message is generated from the code and the
|
|
860
1330
|
failing trigger's arguments and never hand-authored at a call site
|
|
@@ -1083,6 +1553,22 @@ removed version still stands again after the re-boot; **the save is the REFUSED
|
|
|
1083
1553
|
entry (a save is an effect and not an op), and a save that cannot land is reported (`FileCommitFailed`, the
|
|
1084
1554
|
in-memory state restored and the bytes not updated) rather than swallowed.**
|
|
1085
1555
|
|
|
1556
|
+
**⟶ AND A `file`-TIER `DELETE` NOW SAVES ITS OWN OP, 2026-10-29 — THE SECTION HEADING ABOVE IS READ THROUGH THIS
|
|
1557
|
+
CLAUSE AND SO IS EVERY SENTENCE OF IT THAT NAMES A `delete`.** `RCA-8(g)` ANNOTATE-BESIDE: **THE READING ABOVE WAS TAKEN ON A TREE WHERE A `delete` MINTED NO
|
|
1558
|
+
`CommitRecord`, so nothing ever named its journal op to the save — and a
|
|
1559
|
+
consumer was told to expect a durable deletion back after a restart.** **WHAT MOVED:** the delete now mints the
|
|
1560
|
+
same four-member record a write's `K-2` mints, the entry adopts it, and **the terminal arm saves the DELETE's own
|
|
1561
|
+
op on its passing arm** — the same `commit_file` a `file`-tier write's save is. **MEASURED, ON THIS TREE:**
|
|
1562
|
+
`store.write(['a'], 'file', 1)` then `store.delete(['a'], 'file')` leaves the bytes at `filePath` reading `{}`
|
|
1563
|
+
where they read `{"a":1}`, and a fresh `storeCreate` over that path answers `AddressNotFound` where it answered
|
|
1564
|
+
`1`. **WHAT STILL READS AS THE SECTION SAYS, SAID SO THE CORRECTION IS NOT TAKEN WIDER THAN IT IS:** **the `delete`
|
|
1565
|
+
VERB still commits nothing of its own** (`DD-28`; the save is the ARM's), **a `rollback` still performs no file
|
|
1566
|
+
I/O** (its control reading is unmoved), **a WHOLE-TARGET delete — no tier named — names an op standing at no
|
|
1567
|
+
tier, so nothing is saved and the version still comes back after a re-boot**, and **a save that cannot land is
|
|
1568
|
+
reported the write's way** (`FileCommitFailed`, the store's journal rollback first, the message second, the
|
|
1569
|
+
commit's log carrying the one `outcome` entry at `KC-9`) rather than swallowed. **So the one-line advice above is
|
|
1570
|
+
now: a `file`-tier deletion is durable; a whole-target deletion is not.**
|
|
1571
|
+
|
|
1086
1572
|
### §9.4 When the save itself fails
|
|
1087
1573
|
|
|
1088
1574
|
**On a `file`-tier write's save that fails — the act itself failing, not a constraint — the store rolls the write
|
|
@@ -1241,6 +1727,8 @@ each is the figure its own clause already prints.** **THE FIVE, EACH WITH ITS ME
|
|
|
1241
1727
|
5. **THE DEPTH CEILING IS `126`** — the crossing's guard reads `depth > 126` (`bindings/js/src/crossing.rs`), so a value of depth `126` is admitted and one of depth `127` is refused; the node ceiling stands at `1,000,000` beside it (`json-crossing/contract.md §4`'s clause; §10.4).
|
|
1242
1728
|
**NO FIGURE MOVES BY THIS CLAUSE, AND THE ONE THING IT ADDS IS THE DATE ON WHICH ALL FIVE WERE READ AT THE SAME TREE.**
|
|
1243
1729
|
|
|
1730
|
+
**⟶ AND THE SIX FIGURES THIS GUIDE STATES ABOUT THE SHIPPED TREE ARE RE-MEASURED AGAIN AT THE `1.5.0` PREPARATION, 2026-10-29 — EVERY ONE OF THEM UNMOVED, AND THE PACKED SET MEASURED RATHER THAN CARRIED.** `RCA-8(g)` ANNOTATE-BESIDE: **every row, every ★ paragraph and every dated clause above stands BYTE-FOR-BYTE and is read THROUGH this one; this clause adds no rule, moves no figure and re-reads all six at one tree.** **THE SIX, EACH WITH THE READING ITS OWN BYTES GIVE THIS DATE:** **(1) THE STATUS SET IS TWENTY** — `src/error.rs`'s `CODES` is `[&str; 20]`, ranks `1`…`20` (`§8.1`'s heading, its table's `20` row). **(2) THE KERNEL'S TOKEN SET IS NINE** — `bindings/js/src/entry/kernel-foundations.mjs`'s `KC` carries `KC-1`…`KC-9` and no tenth (§8.0a's `KC-9` clause). **(3) THE INSTANCE CARRIES FOURTEEN OWN MEMBERS** (§3.1's table; `index.d.ts`'s `StoreInstance`). **(4) THE DEPTH CEILING IS `126`** — `bindings/js/src/entry/carrier.mjs`'s `ENCODER_CEILING = 126` and `bindings/js/src/crossing.rs`'s `depth > 126` are ONE figure, with `127` and beyond refused at the walk and the node ceiling at `1,000,000` beside it (`§10.3`). **(5) THE ALLOWLIST IS SEVEN ROWS** — `index.mjs` · `README.md` · `README.guide.md` · `RELEASE-NOTES.md` · `index.d.ts` · `src/entry/` · `src/constraint-kernel.mjs`, `7 = 5 + 2` ✓ (the clause of 2026-10-28 above). **(6) THE PACKED MEMBER SET IS `20` BARE AND `21` ASSEMBLED** — measured with `npm pack --dry-run --json` at `bindings/js` this date: `1` `package.json` + `5` root files + `13` entry modules + `1` kernel `= 20` ✓, and the assembled artefact adds the release's `LICENSE` for `21` (the clause above). **AND THE VERSION IS `1.5.0` AT ELEVEN CELLS** (§0's clause of this date). **NO FIGURE MOVED BETWEEN THE `1.4.0` PREPARATION AND THIS ONE**, and the reason is named rather than left to inference: **the twenty-seven commits past `v1.4.0` add no member, no status, no token, no ceiling, no packed byte and no allowlist row** — they declare the reach, the two kinds, the derived address, the listener's independence, the delivery's nesting and the throw (`D-66` … `D-73`), and those are clauses of use rather than rows of this section. (`D-66`–`D-73`; §0's clause of this date; `scripts/publish.sh --figures`.)
|
|
1731
|
+
|
|
1244
1732
|
**★ THE `exports` MAP HAS ONE ARM, AND THAT IS A PROMISE ABOUT WHAT IS REACHABLE:** **the addon is NOT reachable
|
|
1245
1733
|
as a subpath and neither is the constraint kernel.** **There is no wildcard export, no deep import to the addon
|
|
1246
1734
|
and no CJS arm** (`bindings/js/README.md`; `docs/specs/storeroom-publish/contract.md §1.3`;
|
|
@@ -1435,7 +1923,7 @@ tree sharing no mutable member with the store or with another crossing**, which
|
|
|
1435
1923
|
|
|
1436
1924
|
**Two windows in the constraint machinery are known and are NOT defects. Each is a state you can leave.**
|
|
1437
1925
|
|
|
1438
|
-
1. **A `delete`'s listener delivery.** A listener invoked by a delete's delivery runs **no constraint pass** — there is no attempt for a pass to run over. **A `rollback` issued from inside it is unjudged, and its effect stands.**
|
|
1926
|
+
1. **A `delete`'s listener delivery** — **AND FROM 2026-10-29 THE DELETE ITSELF RUNS ONE, WHICH IS WHY THIS WINDOW IS DEFINED BY THE DELIVERY AND NOT BY THE VERB (§7.8: the delivery sits AFTER the pass, so a listener fired by a delete sees a JUDGED state).** A listener invoked by a delete's delivery runs **no constraint pass** — there is no attempt for a pass to run over. **A `rollback` issued from inside it is unjudged, and its effect stands.**
|
|
1439
1927
|
2. **A registration's own evaluation.** `addConstraint`'s and `constraintRegister`'s evaluation of a supplied body runs **no pass** for the same reason — **an action raised there is unjudged and its effect stands.**
|
|
1440
1928
|
|
|
1441
1929
|
**THE FIRST EXIT — RELOAD THE FILE.** Since `1.1.0`, **a restore that moved a version standing at the `file` tier SAVES the restored image**, so **the file records the state the store was restored to, and a fresh boot over that path returns to judged bytes.**
|
|
@@ -1444,6 +1932,8 @@ tree sharing no mutable member with the store or with another crossing**, which
|
|
|
1444
1932
|
|
|
1445
1933
|
**Neither window is new and neither is a defect**: both are places **a pass does not begin**, not places a judgement was skipped mid-flight.
|
|
1446
1934
|
|
|
1935
|
+
**⟶ AND ONE CLAUSE OF ITEM 1 ABOVE IS CORRECTED AT ITS OWN SITE, 2026-10-29 — `D-55` ITEM 3 AND `D-57`: A `delete` DOES MINT AN ATTEMPT NOW, AND THE WINDOW'S REAL REASON IS THE DELIVERY'S POSITION.** `RCA-8(g)` ANNOTATE-BESIDE: **item 1's operative claim — a listener invoked by a `delete`'s delivery runs no constraint pass, and a `rollback` raised there is unjudged and its effect stands — is UNMOVED; what is false of this tree is its given reason, *"there is no attempt for a pass to run over."*** **THE TREE AT `1.4.0`: a `delete` opens its own commit, its own attempt and its own pass, and its receipt carries the four-member `CommitRecord`** (`§7.8`'s clause; `D-57`'s second half; `D-55` item 3). **SO THE ATTEMPT EXISTS — IT HAS ALREADY CLOSED** — **and the window stays passless for the ruling's own reason: the delivery runs AFTER the pass, and after a pass nothing re-enters on that work** (`D-64`; `§7.8`'s four steps). **NOTHING ELSE OF ITEM 1, OF ITEM 2, OF THE EXITS OR OF THE WITNESS MOVES.** (`D-55` item 3; `D-57`; `D-64`; §7.8; §12.6's clause above.)
|
|
1936
|
+
|
|
1447
1937
|
**⟶ AND THE SENTENCE THAT STOOD HERE IS WITHDRAWN AS FALSE, 2026-10-28 — `D-54`, MEASURED, AND IT IS WITHDRAWN RATHER THAN NARROWED. THE BYTES WERE: *"And every pass that DOES run drains its own restart request — so the fail-state is bounded by the two acts that run none."* THE CONSUMER MEASURED THREE HOSTS WHERE NEITHER HALF HOLDS — a repair body, a delete's pass, and a reentrant reversal-then-write — AND IN EACH ONE A PASS RAN, THE REVERSAL WAS LEFT UNJUDGED, AND ITS EFFECT STOOD (zero judge invocations; the refused `5` still there). What follows replaces them and is true of the code after that fix.**
|
|
1448
1938
|
|
|
1449
1939
|
**THE RULE NOW, IN ONE SENTENCE: NO PASS CONSUMES A RESTART REQUEST IT DID NOT RAISE, AND A REQUEST RAISED INSIDE A PASS IS CARRIED TO THAT PASS'S OWN TERMINAL STATE AND JUDGED THERE — ONCE, AS `D-38` DECLARES, AND NEVER PER STEP.** The budget, its three asks, the failing-closed arm and `D-42` are exactly as they were; what changed is where the request is carried.
|
|
@@ -1455,7 +1945,7 @@ tree sharing no mutable member with the store or with another crossing**, which
|
|
|
1455
1945
|
|
|
1456
1946
|
**SO THE FAIL-STATE OF PASS-HOSTED REVERSALS IS EMPTY, AND WHAT GUARANTEES IT IS ONE CONSUMER: the drain in the pass's own commit scope, reached on BOTH arms, matching the window that raised the request, and judging at the pass's terminal state.** The exits below are now owed to the two passless windows alone.
|
|
1457
1947
|
|
|
1458
|
-
**YOUR `delete` QUESTION, ANSWERED HERE, AND IT IS A THIRD CASE AND NOT THE DECLARED ONE.** A `delete` runs **no constraint pass of its own** (`D-1-bis`); the window declared above is its LISTENER DELIVERY, which runs no pass and is UNCHANGED — the `discarded` witness still reports the discard and the reversal's effect still stands. **Where a `delete` is performed INSIDE a pass — the case the consumer measured — the delete's window IS that pass's window**, and a reversal raised by the listener its delivery fires is a DIFFERENT ACT AT A DIFFERENT INSTANT from the declared window: **a THIRD case, and it is now judged** — the request lands in the pass's window and that pass's terminal check asks the pairs at the state the reversal left. **A registration's own evaluation remains the second passless window, silent and identity-only** (`D-49`), and both exits below still leave it honestly.
|
|
1948
|
+
**YOUR `delete` QUESTION, ANSWERED HERE, AND IT IS A THIRD CASE AND NOT THE DECLARED ONE.** **⟶ AND ITS FIRST CLAUSE IS SUPERSEDED FROM 2026-10-29 — `D-55` ITEM `3` BUILT THE PASS THIS SENTENCE SAID DID NOT EXIST: a `delete` now runs a constraint pass of its own kind, at the delete's own journal op, with the delivery after it (§7.8 below); the sentence is kept so the ruling it recorded is legible.** `RCA-8(g)` ANNOTATE-BESIDE: **every other byte of this paragraph stands — the declared window is still the LISTENER DELIVERY, which still runs no pass, the `discarded` witness still reports what that delivery discarded and the reversal's effect still stands, and the third case the consumer measured (a `delete` performed INSIDE a pass) is still judged by that pass.** **WHAT MOVED IS ONLY THE PREMISE: `D-1-bis` (`a delete consults no constraint`) is superseded by `D-55` item `3` (`A delete is an editing verb and must pass constraints`) for the delete's OWN act; it remains the ground of the DELIVERY window, which runs after that act's pass has closed.** The original bytes read: A `delete` runs **no constraint pass of its own** (`D-1-bis`); the window declared above is its LISTENER DELIVERY, which runs no pass and is UNCHANGED — the `discarded` witness still reports the discard and the reversal's effect still stands. **Where a `delete` is performed INSIDE a pass — the case the consumer measured — the delete's window IS that pass's window**, and a reversal raised by the listener its delivery fires is a DIFFERENT ACT AT A DIFFERENT INSTANT from the declared window: **a THIRD case, and it is now judged** — the request lands in the pass's window and that pass's terminal check asks the pairs at the state the reversal left. **A registration's own evaluation remains the second passless window, silent and identity-only** (`D-49`), and both exits below still leave it honestly.
|
|
1459
1949
|
|
|
1460
1950
|
**⟶ ADDED 2026-10-28 — `D-49`: THE DISCARD IS NOW OBSERVABLE ON THE `delete`'S OWN ANSWER, AND THE OTHER
|
|
1461
1951
|
WINDOW STILL CARRIES NOTHING. READ THIS BEFORE YOU RELY ON EITHER FAIL-STATE.**
|
|
@@ -1484,6 +1974,9 @@ window throws away**: if it fails and you raise a `rollback`, **the request dies
|
|
|
1484
1974
|
**THE EXITS ARE STILL YOURS AND ARE UNCHANGED: reload the file, or regenerate the store.** What changed is that
|
|
1485
1975
|
**the `delete` now tells you the discard happened** — *"a fail-state is honest only if the consumer learns it
|
|
1486
1976
|
happened"* (`D-49`).
|
|
1977
|
+
**⟶ AND AN ENTRY IS NEVER SHIPPED WITH AN EMPTY ADDRESS LIST, 2026-10-28 — `D-55` ITEM 2 · `DEF-AH`, MEASURED.** `RCA-8(g)` ANNOTATE-BESIDE: **every bullet above stands BYTE-FOR-BYTE and is read THROUGH this clause; the member's name, its non-enumerability, its `{ opId, addresses }` shape and the two limits stay exactly as they were.** **THE RULING, THE ARCHITECT, VERBATIM: *"A witness address of `[]` corresponds to the register itself. This is not a legal response for any other watched address."*** **SO THE TWO EMPTY THINGS ARE NOT THE SAME THING, AND THE SHIPPED `1.3.0` CONFLATED THEM: an ADDRESS of `[]` is THE REGISTER (the root) and is lawful; an entry whose address LIST is `[]` is never lawful.** **WHAT `1.3.0` DID, MEASURED: a delivery whose reversal moved NOTHING — a nested write, then a reversal of an op the watched address never held — reported `addresses: []`**, a list with no address in it. **WHAT YOU GET NOW:** **`record.discarded` is `[]` where nothing moved** — *if nothing moved there is no discard to report, so the entry is ABSENT and not empty* — **and a reversal that moves the ROOT reports `addresses: [[]]`**: one element, whose segment list is empty. **THE TWO READINGS BESIDE EACH OTHER, so a consumer can tell them apart: `[]` is "nothing was discarded"; `[[]]` is "the register itself is what moved".** **NO OTHER MEMBER MOVES:** `opId` is still the id the reversal named, the list is still `0` or `1` long, and nothing is minted into the status set. (`tests/e2e/discard-witness.mjs`'s `DW-10.1`/`DW-10.2` measure both arms; the empty-list arm is RED under a revert of the one line.)
|
|
1978
|
+
|
|
1979
|
+
**⟶ AND YOUR `delete`'S OWN PASS IS STILL A MEASUREMENT AND NOT A BEHAVIOUR, 2026-10-28 — `D-55` ITEM 3 · `DEF-X`.** `RCA-8(g)` ANNOTATE-BESIDE: **every clause of this section and of §12.6 above stands BYTE-FOR-BYTE; this clause answers the question a consumer asked about `1.2.0` by measuring `1.3.0`.** **THE RULING, THE ARCHITECT, VERBATIM: *"A delete is an editing verb and must pass constraints. This includes all of the relevant commit and message logic."*** **SO IT MUST — AND NO SHAPE AT THIS VERSION ADMITS ONE, WHICH IS WHAT THE MEASUREMENT SAYS RATHER THAN WHAT AN INTENTION SAYS.** **A `delete`'s path, verb by verb:** the entry's `delete` opens a commit window of its own (`openCommits` is `1` inside its delivery), calls the addon's `delete` entry, builds the record `{address, tiers, triggered, op}`, delivers the triggering set, reads the witness and closes the window. **IT NEVER CALLS THE KERNEL**: no `K-2` opens an attempt, no `K-3` runs a pass, no message log is minted and no terminal entry is appended — so the pair standing at the deleted address is asked exactly as often as its registration asked it, and NOT ONCE FOR THE DELETE. **AND THE ONE PASS SHAPE THE ARTEFACT HAS CANNOT STAND IN**: `K-2` **LANDS A WRITE OF ITS OWN BEFORE `K-3` JUDGES**, so a pass driven at the deleted address judges a state the delete is not in (measured: the store after it holds the value `K-2` wrote). **WHAT WOULD HAVE TO BE BUILT, SAID PLAINLY AND NOT BUILT HERE:** **a delete would have to be GIVEN a commit and an attempt of its own** — a `K-2`-shaped opening that takes the delete's own op instead of a write's, a commit list minted for it, a pass over the pairs standing at the deleted address against the state the delete leaves, a terminal arm that appends that log's `outcome` entry, and the delivery moved AFTER the pass so its listeners see a judged state — **and the current `delete` verb has none of those.** **A delete performed INSIDE a pass IS judged**, and that is the third case §12.6 above declares — but that is the pass's check, not the delete's. (`tests/e2e/discard-witness.mjs`'s `DW-10.8` prints the path, the counts and the pass shape it drove.)
|
|
1487
1980
|
|
|
1488
1981
|
### §12.7 A skip is not a green, and your own throws are not refusals
|
|
1489
1982
|
|