provident-storeroom 1.2.0 → 1.3.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 +122 -3
- package/README.md +24 -8
- package/RELEASE-NOTES.md +584 -0
- package/index.d.ts +38 -2
- package/index.mjs +11 -3
- package/package.json +7 -6
- package/src/entry/constraints.mjs +402 -69
- package/src/entry/delivery.mjs +8 -2
- package/src/entry/kernel-attempt.mjs +136 -19
- package/src/entry/kernel-commit.mjs +26 -2
- package/src/entry/kernel-foundations.mjs +100 -8
- package/src/entry/kernel-registration.mjs +63 -22
- package/src/entry/kernel-terminal.mjs +18 -5
- package/src/entry/state.mjs +47 -4
package/RELEASE-NOTES.md
ADDED
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
# Release notes — `provident-storeroom` `1.1.0`
|
|
2
|
+
|
|
3
|
+
**⟶ THE FILE'S OPERATIVE READING, 2026-10-28 — THE HEAD IS CORRECTED HERE, AND THE DEFECT WAS ONE LEVEL IN: A DOCUMENT
|
|
4
|
+
DENYING THE STATE IT DESCRIBED.** `RCA-8(g)` ANNOTATE-BESIDE: **the title line above and every dated paragraph below
|
|
5
|
+
stand BYTE-FOR-BYTE as the reading of their own date; THIS clause and the `§MAIN` section beneath it are the operative
|
|
6
|
+
readings of the two things the head got wrong.** **(i) `1.2.0` IS NOT UNRELEASED.** `git tag --list` answers `v1.0.0` ·
|
|
7
|
+
`v1.0.1` · `v1.1.0` · `v1.2.0`; the tag `v1.2.0` names a tree whose `bindings/js/package.json` reads `"version": "1.2.0"`
|
|
8
|
+
at its own bytes; and the registry carries `1.2.0`. **So the section that stood under *"UNRELEASED"* is the change list
|
|
9
|
+
of a CUT release, its *"no `v1.2.0` tag exists"* sentence and its *"what is installed today is still `1.1.0`"* sentence
|
|
10
|
+
are the reading of the date they were written, and its heading is corrected at its own line** — the withdrawn bytes are
|
|
11
|
+
recorded there, because a heading that denies its own tag is exactly the class of defect this project calls `DEF-AC`.
|
|
12
|
+
**(ii) THE PUBLISHED FIGURE IS `1.2.0`, NOT `1.1.0`, AND THE TREE READ HERE IS NINE COMMITS PAST THAT RELEASE WITH NO
|
|
13
|
+
TAG ON IT.** **So this file now carries a section for the tree that is not cut (`§MAIN`, prepared and marked so) beside
|
|
14
|
+
the section for the release that is (`§1.2.0`).** **No version is bumped, no manifest moves, and nothing in `§MAIN`
|
|
15
|
+
claims to be installable.**
|
|
16
|
+
|
|
17
|
+
**THE ONE THING THIS DOCUMENT IS FOR: `1.1.0` IS A MINOR, AND THIS IS WHERE ITS BREAKING CHANGES ARE
|
|
18
|
+
DECLARED.** `1.0.1` was published as a PATCH and it carried behaviour changes a patch may not carry. The
|
|
19
|
+
architect ruled the remedy a MINOR — *"the next release is `1.1.0`"* (`strata/decisions.md` · `D-42` item `1`)
|
|
20
|
+
— so **`1.0.1` stands as published with a disclosed breach, and `1.1.0` carries the declaration.** This file
|
|
21
|
+
is that declaration. **It is not a changelog of everything that happened; it is the list of things that can
|
|
22
|
+
break you, each with what it did before and what it does now.**
|
|
23
|
+
|
|
24
|
+
**WHERE IT BELONGS, AND WHY IT IS AT THE REPOSITORY ROOT.** This note is at the packaging root's PARENT, not
|
|
25
|
+
inside `bindings/js/`: the main manifest's `files` allowlist does not name it and must not, so **it ships in NO
|
|
26
|
+
tarball.** It addresses a consumer deciding whether to move off `1.0.1`, so it must be readable BEFORE an
|
|
27
|
+
install and must cover the six packages at once — the main package and the five platform packages share one
|
|
28
|
+
version and one decision.
|
|
29
|
+
|
|
30
|
+
**⟶ AND THE PARAGRAPH ABOVE IS SUPERSEDED BY MEASUREMENT, 2026-10-28 — `DEF-AC`, AND THIS FILE NOW SHIPS.** `RCA-8(g)`
|
|
31
|
+
ANNOTATE-BESIDE: **the paragraph above stands BYTE-FOR-BYTE and is the reading of its own date (the `1.1.0` cut, when
|
|
32
|
+
it did ship nowhere); it is read THROUGH this clause.** **THE DEFECT THE DOWNSTREAM FILED: the ONE behaviour change
|
|
33
|
+
`1.2.0` makes — a refused reversal's restore to the commit's OPENING mark — was declared in THIS FILE and in no
|
|
34
|
+
shipped document, so a consumer installing `1.2.0` received a behaviour change with no shipped declaration of it.**
|
|
35
|
+
**SO THE FILE IS LANDED THE WAY THE GUIDE IS LANDED**: `RELEASE-NOTES.md` at the repository root is the ONE authored
|
|
36
|
+
home, the release's assembly stages it **BYTE-FOR-BYTE** at the packaging root as `bindings/js/RELEASE-NOTES.md`,
|
|
37
|
+
and the main manifest's `files` allowlist **names it** — one additive documentation row beside `README.guide.md`,
|
|
38
|
+
staged by the same assembly step and admitted by the reachability rule's own declared non-code class. **A BARE
|
|
39
|
+
`npm pack` STILL WORKS** because the landed copy is a TRACKED, GENERATED file like `README.guide.md`: it is present
|
|
40
|
+
whether or not the assembly has run. **AND WHAT SHIPS IS THE CHANGE LIST, at `node_modules/provident-storeroom/
|
|
41
|
+
RELEASE-NOTES.md`.**
|
|
42
|
+
|
|
43
|
+
**THE VERSION THIS RELEASE DECLARES IS `1.1.0` AT ELEVEN SITES, MOVING TOGETHER** — the main manifest's
|
|
44
|
+
`version`, its FIVE exact `optionalDependencies` pins, and the FIVE `platforms/*/package.json` versions. **The
|
|
45
|
+
artefact is stamped from the manifest by `bindings/js/build.rs`, so `version()` answers `1.1.0`** and that is
|
|
46
|
+
the value to assert against, not the crate's own `Cargo.toml` version.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## §1.3.0 · **PREPARED — NOT CUT** — what `1.3.0` carries, ten commits past `v1.2.0`
|
|
51
|
+
|
|
52
|
+
**★ THIS SECTION DESCRIBES A TREE NOTHING CAN INSTALL, AND IT IS MARKED BEFORE IT SAYS ANYTHING ELSE, BECAUSE A MARK
|
|
53
|
+
ADDED AFTERWARDS IS THE DEFECT THIS FILE JUST CORRECTED ONE SECTION DOWN.** **THE PUBLISHED RELEASE IS `1.2.0`**: `git
|
|
54
|
+
tag --list` answers `v1.0.0` · `v1.0.1` · `v1.1.0` · `v1.2.0`, the tag `v1.2.0` names a tree whose
|
|
55
|
+
`bindings/js/package.json` reads `"version": "1.2.0"` at its own bytes, and the registry carries that release. **THE TREE
|
|
56
|
+
THIS SECTION IS DERIVED FROM IS NINE COMMITS PAST IT** — `git describe` reads `v1.2.0-9-g989c216`, `git rev-list --count
|
|
57
|
+
v1.2.0..HEAD` reads `9`, and **no version bump is in any of those nine commits: the manifest still reads `1.2.0` at
|
|
58
|
+
eleven sites and no tag names this tree.** **SO THE HONEST READING IS THIS: A CONSUMER INSTALLING `1.2.0` GETS `1.2.0`'S
|
|
59
|
+
BYTES AND `1.2.0`'S CHANGE LIST (the section below); WHAT THIS SECTION DECLARES IS PREPARED AND NOT CUT, and a prepared
|
|
60
|
+
change is not a shipped one.** **No consumer can install any of it yet, and every figure this section prints is a
|
|
61
|
+
figure of the TREE and not of a tarball.**
|
|
62
|
+
|
|
63
|
+
**⟶ AND THE VERSION THIS SECTION DECLARES IS `1.3.0` AT ELEVEN SITES, PREPARED AND NOT CUT, 2026-10-28.** `RCA-8(g)` ANNOTATE-BESIDE: **the ★ paragraph above stands BYTE-FOR-BYTE as the reading of its own date and is read THROUGH this clause; the two sentences of it that the bump supersedes are named here rather than overwritten.** **THE MANIFEST AT ITS OWN BYTES THIS DATE: `bindings/js/package.json`'s `version` is `"1.3.0"`, all five `optionalDependencies` pins are `1.3.0` with it, and the five `bindings/js/platforms/*/package.json` `version` fields read `1.3.0` with them — ELEVEN SITES, ONE REBUILD — and the rebuilt artefact is stamped from that manifest, so `version()` answers `1.3.0`.** **SO *"no version bump is in any of those nine commits: the manifest still reads `1.2.0` at eleven sites"* IS THE READING OF ITS OWN DATE, AND THE BUMP IS THIS PREPARATION'S (a working-tree change, not a commit): the tree is `10` commits past `v1.2.0` — `git describe` reads `v1.2.0-10-g14787b5` and `git rev-list --count v1.2.0..HEAD` reads `10` — and the eleven sites read `1.3.0`.** **AND WHAT THIS SECTION IS AND IS NOT, SAID PLAINLY BECAUSE THE EASIER THING TO GET WRONG IS THAT A BUMP IS A RELEASE: `1.3.0` IS THE PREPARED VERSION — at the eleven sites and in the rebuilt artefact — and it is NOT A RELEASE. `git tag --list` answers `v1.0.0` · `v1.0.1` · `v1.1.0` · `v1.2.0` and no `v1.3.0`, the registry carries no `1.3.0`, and no consumer can install any of this until the architect cuts the tag.** **The published release remains `1.2.0`, and the section below remains its change list, unchanged and still marked CUT AND PUBLISHED.** (The shipped guide's two generated figures — `scripts/publish.sh --figures` — agree with this clause: the version line reads `1.3.0` and the cut-state sentence reads *"`v1.3.0` DOES NOT EXIST, SO THE RELEASE THIS GUIDE DESCRIBES IS NOT CUT"*.)
|
|
64
|
+
|
|
65
|
+
**IT DECLARES TWO BEHAVIOUR CHANGES A CONSUMER WILL FEEL, AND THEN FOUR SMALLER ITEMS. THE TWO COME FIRST BECAUSE THEY
|
|
66
|
+
ARE THE CONTENT.**
|
|
67
|
+
|
|
68
|
+
**1 · ★ THE MESSAGE LOG BELONGS TO THE COMMIT NOW — SO A CROSSED REPORT CARRIES MORE ENTRIES THAN `1.2.0`'S DID.**
|
|
69
|
+
**`AT `1.2.0`:** the `log` was the **ATTEMPT's** own list — the constraint-judged write inside a commit carried the
|
|
70
|
+
entries its own pass appended, and where an attempt was **nested** inside another (a write a constraint body makes, a
|
|
71
|
+
repair's own write) the enclosing report carried the **enclosing** attempt's list: the nested refusal's entries were not
|
|
72
|
+
in it, and on the arm the downstream filed (`DEF-AK`) the inner refusal's own entry was **REPLACED** rather than joined.
|
|
73
|
+
**NOW:** the list belongs to the **COMMIT** — it is taken once per commit off the kernel's frame stack, defined on the
|
|
74
|
+
`CommitRecord` under a symbol, and **every attempt in that commit holds a reference to it** — so **a crossed report
|
|
75
|
+
carries the entries of EVERY attempt in the commit**, including a nested write made by a constraint body, whose refusal
|
|
76
|
+
now shows its own `constraint-check` and `outcome` entries verbatim before the enclosing `outcome` entry. **A CONSUMER
|
|
77
|
+
DIFFING `1.2.0` AGAINST THIS RELEASE SEES A REPORT CARRY MORE ENTRIES** — and that is the whole change: what is refused,
|
|
78
|
+
what is restored, and which status crosses have not moved with it. **The one declared reach that is still a new list: a
|
|
79
|
+
listener's write opens a commit of its own** (*"the triggering commit already passed constraints"*), so its entries are
|
|
80
|
+
not in the triggering commit's list; and a pair body asked at a reversal's judgement names no commit, so a write that
|
|
81
|
+
body makes opens one.
|
|
82
|
+
**AND THE ENTRY'S `seq` IS THE POSITION IN THAT LIST — NOT THE STORE'S JOURNAL ORDINAL.** **`1.2.0`'s declaration said
|
|
83
|
+
*"the ordinal of the list it stands in"* and that sentence was read as the journal's** (`DEF-AI`, measured): a refusal's
|
|
84
|
+
entry carried `seq` `1` while the store's own journal counter read `41`. **They are two ordinals with two homes:
|
|
85
|
+
`JournalOp.seq` is the store's append ordinal that orders the store's record, and this `seq` orders the COMMIT's message
|
|
86
|
+
log and nothing else; neither equals the other by design.** **One repair write appends five entries in one list at `seq`
|
|
87
|
+
`1..5`, strictly increasing in array order** — the pass's `constraint-check`, the repair's `repair-run`, the repair's
|
|
88
|
+
`commit`, the re-run's `constraint-check`, and the terminal `commit`/`outcome` — **and no entry is ever minted from the
|
|
89
|
+
store's journal counter.** **Say it to yourself before you count: `log.length` is every entry of the COMMIT, not "how
|
|
90
|
+
many constraints were evaluated" and not "how many attempts this write made".**
|
|
91
|
+
|
|
92
|
+
**2 · ★ A REVERSAL RAISED INSIDE A PASS IS NOW JUDGED — THREE HOSTS CHANGED FROM "A PASS RAN AND THE REVERSAL'S EFFECT
|
|
93
|
+
STOOD" TO "REFUSED AND RESTORED".** **`AT `1.2.0`:** a reversal raised where a pass was running could be **left unjudged
|
|
94
|
+
with its effect standing**, and the shipped guide said so falsely: *"And every pass that DOES run drains its own restart
|
|
95
|
+
request — so the fail-state is bounded by the two acts that run none."* **That sentence is WITHDRAWN AS FALSE** (`D-54`;
|
|
96
|
+
measured by the downstream): **a pass no longer consumes a request it did not raise, and the request is carried to the
|
|
97
|
+
pass's own terminal state.** **The rule, quoted from the shipped guide:** ***"no pass consumes a restart request it did
|
|
98
|
+
not raise, and a request raised inside a pass is carried to that pass's own terminal state and judged there — once,
|
|
99
|
+
never per step."*** **THE THREE HOSTS, EACH MEASURED, BEFORE → AFTER:**
|
|
100
|
+
- **A REPAIR BODY'S REVERSAL** — the judge was asked the pairs `[9,7]` and never `[5]`, with `0` judge invocations and the
|
|
101
|
+
refused `5` standing; **now the pairs asked are `[9,7,5]`, the invocation count is `1`, the write crosses
|
|
102
|
+
`ConstraintPassRefused`, and the store reads `9`** — the refused value does not stand.
|
|
103
|
+
- **A REVERSAL INSIDE A `delete`'S CONSTRAINT PASS** — the same `[9,7]`/`0`/standing-`5` reading, and **now the same
|
|
104
|
+
`[9,7,5]`/`1`/`9`**, with the delete's own `discarded` witness reading `[]` because nothing was discarded.
|
|
105
|
+
- **A REENTRANT REVERSAL-THEN-WRITE** — the write used to answer with the reversal's effect standing at `5`; **now it
|
|
106
|
+
REFUSES and the store reads `9`.**
|
|
107
|
+
**AND THE TWO DECLARED PASSLESS WINDOWS STILL RUN NO PASS, WITH THEIR EXITS UNCHANGED: a `delete`'s LISTENER DELIVERY
|
|
108
|
+
(a listener invoked by a delete's delivery runs no constraint pass, so a reversal raised inside it is unjudged and its
|
|
109
|
+
effect stands, and the `discarded` witness reports the discard) and a REGISTRATION'S OWN EVALUATION (`addConstraint`'s
|
|
110
|
+
and `constraintRegister`'s evaluation of a supplied body runs no pass, so an action raised there is unjudged and its
|
|
111
|
+
effect stands).** **Their exits are the two the guide declares and they are unchanged: RELOAD THE FILE — a restore that
|
|
112
|
+
moved a `file`-tier version saves the restored image, so a fresh boot over that path returns to judged bytes — or
|
|
113
|
+
REGENERATE THE STORE, replaying your writes.** **A reversal raised by the listener a `delete`'s delivery fires INSIDE a
|
|
114
|
+
pass is a THIRD case and not the declared window: that pass's terminal check now asks the pairs at the state the
|
|
115
|
+
reversal left.** `D-42`, `D-38`'s once-per-terminal-state rule, the restart budget (`3`), its spend-and-never-reset rule
|
|
116
|
+
and its failing-closed arm are **untouched**; what changed is where the request is carried and who may consume it.
|
|
117
|
+
|
|
118
|
+
**3 · THE RESTORE NO LONGER REPORTS A SUCCEEDED WHOLE-COMMIT UNDO AS A FAILURE (`DEF-AJ`).** **`AT `1.2.0`:** a message
|
|
119
|
+
could say *"0 of the 1 address it moved stand again"* when **every** address stood: the restore verified each moved
|
|
120
|
+
address against the watch's **pre-reversal capture** while the store returned it to the **journal mark the commit cached
|
|
121
|
+
at its OPENING**, and on the restart arm the commit had already landed `7` before the reversal ran, so the mark's `9`
|
|
122
|
+
did not equal the capture's `7` and a complete whole-commit undo read as a failure. **NOW:** a successful restore counts
|
|
123
|
+
every measured address restored to the mark, a standing **past** the capture is recorded as a `wholeCommit` and spoken as
|
|
124
|
+
such with the baseline named, and **only a refused mark, a cacheless window or an unrestored address say NOT undone.**
|
|
125
|
+
The message's meaning is the thing that moved; nothing about what a refusal restores moved with it.
|
|
126
|
+
|
|
127
|
+
**4 · THE `discarded` WITNESS NAMES WHAT MOVED, NOT THE WHOLE WATCHED REGISTRY (`DEF-AM`).** A delete's `discarded`
|
|
128
|
+
carries `addresses` — the addresses the discarded check would have been asked at — and **those are now the addresses the
|
|
129
|
+
reversal actually MOVED, measured once against the standing the store holds, and not every address the registry
|
|
130
|
+
watches.** **Measured: two pairs in the registry, one moved, and the witness names one.** **The witness is still a
|
|
131
|
+
WITNESS and not a repair** — the delete answers what it answered, throws nothing, and the reversal's effect still
|
|
132
|
+
stands in that declared window — and it is still **non-enumerable** (`Object.keys` never shows it; a spread copy loses
|
|
133
|
+
it).
|
|
134
|
+
|
|
135
|
+
**5 · THE CHANGE LIST ITSELF NOW SHIPS IN THE TARBALL (`DEF-AC` (a)).** **`RELEASE-NOTES.md` is the SEVENTH row of the
|
|
136
|
+
main manifest's `files` allowlist**, staged **byte-for-byte** from this repository-root home to the packaging root as
|
|
137
|
+
`bindings/js/RELEASE-NOTES.md` by the release's assembly — the same mechanism that lands `README.guide.md` and the
|
|
138
|
+
`LICENSE` copy — and the preflight REFUSES a drifted landing. **So a consumer installing the next release has this
|
|
139
|
+
change list at `node_modules/provident-storeroom/RELEASE-NOTES.md`**, and the packed member set reads **`20` bare / `21`
|
|
140
|
+
assembled** (`1` manifest + `5` root files + `13` entry modules + `1` kernel = `20`, plus the licence at assembly).
|
|
141
|
+
**This is the half of `DEF-AC` that is already cut in `1.2.0` and it stays cut in this tree.**
|
|
142
|
+
|
|
143
|
+
**6 · THE PREFLIGHT GENERATES AND ASSERTS THE SHIPPED GUIDE'S VERSION LINE AND CUT-STATE (`DEF-AC` (b)).** **The
|
|
144
|
+
guide's version line and cut-state sentence used to be HAND-WRITTEN, which is how the guide came to deny the release it
|
|
145
|
+
travelled in.** **Now `scripts/publish.sh` GENERATES both from `bindings/js/package.json`'s `version` and `git tag
|
|
146
|
+
--list`, and its preflight REFUSES when the shipped guide's two marked lines disagree** — a hand-written *"IS NOT CUT"*
|
|
147
|
+
beside a generated *"IS CUT"* fails, and the generated line passes; `--figures` runs that one check read-only. **A
|
|
148
|
+
hand-written line that contradicts the manifest can no longer travel.**
|
|
149
|
+
|
|
150
|
+
**NOTHING IN THIS SECTION IS CUT, NO TAG NAMES IT, NO VERSION BUMP IS PROPOSED, AND THE ELEVEN VERSION SITES STAY AT
|
|
151
|
+
`1.2.0`.** **The two behaviour changes are declared here because a consumer must be able to read them BEFORE the cut and
|
|
152
|
+
must not be told they are in a tarball that does not carry them** — the same honesty the corrected head above owes.
|
|
153
|
+
|
|
154
|
+
**⟶ AND WHAT THE PARAGRAPH ABOVE SAYS ABOUT THE FIGURES IS SUPERSEDED BY THIS PREPARATION, 2026-10-28 — WHAT IT SAYS ABOUT THE CUT IS NOT.** `RCA-8(g)` ANNOTATE-BESIDE: **the paragraph above stands BYTE-FOR-BYTE as the reading of its own date and is read THROUGH this clause.** **UNMOVED: *"NOTHING IN THIS SECTION IS CUT, NO TAG NAMES IT"* — `git tag --list` carries no `v1.3.0`, the registry carries no `1.3.0`, and a consumer cannot install any of this.** **SUPERSEDED: *"NO VERSION BUMP IS PROPOSED, AND THE ELEVEN VERSION SITES STAY AT `1.2.0`"* — the bump IS this preparation's, and the eleven sites read `1.3.0`** (the clause at this section's head states the manifest, the pins, the five platform manifests and the rebuilt artefact's `version()`). **The two behaviour changes are unchanged, and they are `1.3.0`'s content: what a consumer installing `1.2.0` gets is `1.2.0`'s bytes, and this section is the change list of the version being prepared, not of a released one.**
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## §1.2.0 · **CUT AND PUBLISHED — tag `v1.2.0`** — what that release adds, derived from the tree
|
|
159
|
+
|
|
160
|
+
**⟶ THE HEADING THAT STOOD HERE WAS *"`## §1.2.0 · **UNRELEASED** — what the release in preparation adds, derived from
|
|
161
|
+
the tree`"*, AND IT IS CORRECTED RATHER THAN ANNOTATED AROUND, 2026-10-28.** `RCA-8(g)` ANNOTATE-BESIDE: **the withdrawn
|
|
162
|
+
bytes are quoted above and the section's body below stands BYTE-FOR-BYTE as the reading of its own date; every reading
|
|
163
|
+
in it that says the release is not cut is read THROUGH the head clause and the clause of 2026-10-28 at the end of this
|
|
164
|
+
section's opening paragraphs.** **THE DEFECT: a heading denying the release it describes is the `DEF-AC` class one level
|
|
165
|
+
in, and a reader opening this file met *"UNRELEASED"* over a version the registry carries.** **The tag is cut — `git tag
|
|
166
|
+
--list` answers `v1.0.0` · `v1.0.1` · `v1.1.0` · `v1.2.0` — and the release this section describes is published.**
|
|
167
|
+
|
|
168
|
+
**★ THIS SECTION DESCRIBES A RELEASE THAT HAS NOT BEEN CUT, AND IT IS MARKED SO IT CAN NEVER BE MISTAKEN FOR A
|
|
169
|
+
DECLARATION OF SHIPPED BYTES.** `bindings/js/package.json`'s `version` reads **`1.2.0`** at its own bytes this
|
|
170
|
+
date, all five `optionalDependencies` pins read `1.2.0` with it, the five platform manifests' `version` fields read
|
|
171
|
+
`1.2.0` with them — **eleven sites, one rebuild, and the artefact the manifest stamps answers `version()` =
|
|
172
|
+
`1.2.0`** — **and no `v1.2.0` tag exists: `git tag --list` answers `v1.0.0` · `v1.0.1` · `v1.1.0` and nothing else.**
|
|
173
|
+
**SO THE HONEST READING IS THIS: THE MANIFEST IS PREPARED AT `1.2.0` AND THE RELEASE IS NOT CUT — a prepared version
|
|
174
|
+
is not a released one, the tag is not cut until the architect cuts it, and no consumer can install any of this
|
|
175
|
+
yet.** **What `1.1.0` describes is still what is cut and published; what this section describes is the preparation,
|
|
176
|
+
derived from the tree on 2026-10-28, and the figure a reader checks against the registry today is still `1.1.0`.**
|
|
177
|
+
|
|
178
|
+
**AND WHAT SHIPS NOWHERE, SAID FIRST BECAUSE IT IS THE EASIEST THING TO GET WRONG: THIS FILE IS AT THE
|
|
179
|
+
REPOSITORY ROOT AND NO TARBALL CARRIES IT.** The main manifest's `files` allowlist names six rows — `index.mjs` ·
|
|
180
|
+
`README.md` · `README.guide.md` · `index.d.ts` · `src/entry/` · `src/constraint-kernel.mjs` — **and this document
|
|
181
|
+
is not one of them and must not be.** So a `npm install provident-storeroom` puts **no `RELEASE-NOTES.md`** on
|
|
182
|
+
disk: a consumer holding only `node_modules` has the package's own bytes and the shipped guide
|
|
183
|
+
(`README.guide.md`), and reaches this file only by reading the repository. **The same was already true of the
|
|
184
|
+
`1.1.0` declaration above and it is true of the `1.2.0` list below.**
|
|
185
|
+
|
|
186
|
+
**⟶ AND BOTH PARAGRAPHS ABOVE ARE RE-READ, 2026-10-28 — `DEF-AC`: THIS FILE SHIPS, AND IT IS THE CHANGE LIST A
|
|
187
|
+
CONSUMER OF `1.2.0` RECEIVES.** `RCA-8(g)` ANNOTATE-BESIDE: **the two paragraphs stand BYTE-FOR-BYTE as the reading
|
|
188
|
+
of their own date; THIS clause is the operative reading of BOTH the heading and the allowlist.** **(i) THE TAG IS
|
|
189
|
+
CUT:** `git tag --list` answers `v1.0.0` · `v1.0.1` · `v1.1.0` · `v1.2.0`, so **the `1.2.0` section's *"UNRELEASED"*
|
|
190
|
+
heading and its *"no `v1.2.0` tag exists"* sentence are the reading of the date they were written and are read
|
|
191
|
+
THROUGH this clause; the release is CUT, and this text travels in its tarball.** **(ii) THE ALLOWLIST NAMES SEVEN
|
|
192
|
+
ROWS, THIS FILE AMONG THEM:** `index.mjs` · `README.md` · `README.guide.md` · `RELEASE-NOTES.md` · `index.d.ts` ·
|
|
193
|
+
`src/entry/` · `src/constraint-kernel.mjs` — **the landed copy of this document is a TRACKED, GENERATED file at
|
|
194
|
+
`bindings/js/RELEASE-NOTES.md`, staged byte-for-byte from this authored home by the release's assembly, exactly as
|
|
195
|
+
`README.guide.md` is staged from `docs/guides/package-use.md`** (`scripts/publish.sh`'s notes stage; the preflight
|
|
196
|
+
REFUSES a drifted landing). **SO THE SENTENCE *"NO TARBALL CARRIES IT"* IS UNTRUE FROM THIS DATE AND IS READ THROUGH
|
|
197
|
+
THIS CLAUSE: a consumer installing `1.2.0` has this change list at `node_modules/provident-storeroom/
|
|
198
|
+
RELEASE-NOTES.md`.**
|
|
199
|
+
|
|
200
|
+
**WHAT HAS CHANGED SINCE `1.1.0`, IN FOUR ITEMS — ONE OF THEM A BEHAVIOUR CHANGE AND THREE OF THEM NOT.**
|
|
201
|
+
|
|
202
|
+
1. **`DeleteRecord` GAINED `discarded` (ADDITIVE).** A delete's record carries a fifth own member beside
|
|
203
|
+
`address`, `op`, `tiers` and `triggered`: **`discarded`**, a list `0` or `1` long of `{ opId, addresses }` —
|
|
204
|
+
the reversal a delivery raised and the engine threw away, with the addresses the discarded check owed.
|
|
205
|
+
`discarded.length === 0` means the delivery discarded nothing. **It is a WITNESS and not a repair:** the
|
|
206
|
+
delete still answers everything it answered, throws nothing, mints no status, and the reversal's effect still
|
|
207
|
+
stands. **AND ITS ENUMERABILITY CONSEQUENCE IS PART OF THE DECLARATION, NOT A FOOTNOTE: `discarded` is
|
|
208
|
+
DECLARED as a member in `index.d.ts` and is NON-ENUMERABLE at runtime.** Measured on this checkout
|
|
209
|
+
2026-10-28: a delete's record answers `Object.keys` as `['address','op','tiers','triggered']` — **four** —
|
|
210
|
+
while `Object.getOwnPropertyNames` answers **five**, `discarded`'s own descriptor being
|
|
211
|
+
`{enumerable: false, configurable: true, writable: false}`. **So `Object.keys` never shows it, and a spread or
|
|
212
|
+
`Object.assign` copy of a receipt LOSES it:** both were driven and neither copy carried the member. **Read the
|
|
213
|
+
witness off the record itself** — a consumer that copies a receipt loses the witness, and nothing throws when
|
|
214
|
+
it does.
|
|
215
|
+
|
|
216
|
+
2. **THE ONE BEHAVIOUR CHANGE: A REFUSED REVERSAL NOW RESTORES TO THE COMMIT'S OPENING MARK, NOT THE JOURNAL'S
|
|
217
|
+
HEAD.** `restoreReversal` used to restore the journal's head, on the reasoning that *"a reversal appends no
|
|
218
|
+
journal entry, so that head describes the state that stood BEFORE the action"*. **That reasoning fails on the
|
|
219
|
+
restart arm**: the enclosing write's own op stands at the head when a listener's reversal runs, so the
|
|
220
|
+
compensation restored the write's LANDING and **a write that reported a refusal had left its own value
|
|
221
|
+
standing** (`DEF-AE`: `9` stayed `9` where the refused write should have restored `7`). **Now the opening
|
|
222
|
+
caches its mark — the journal's last valid op id, or the floor where the journal holds no entry — and the
|
|
223
|
+
restore hands that cached mark back.** Measured: the refusing arm leaves the store where the ATTEMPT found it
|
|
224
|
+
(`tests/e2e/def-ae-restore-mark.test.mjs`, cells `1`–`2`, and the seven refusing arms swept around them).
|
|
225
|
+
**A lawful reversal is unchanged**, and a lawful write still answers its record with its value standing.
|
|
226
|
+
|
|
227
|
+
3. **`context.value`'S RESPONSIBILITY IS NOW DECLARED (NO BEHAVIOUR CHANGE).** `D-50`, the architect, verbatim:
|
|
228
|
+
*"If a constraint needs to look for the data at a given location, it can read()"*. **`context.value` is the
|
|
229
|
+
value the write verb returned** — a repair that lands elsewhere hands your body the value that op landed — **so
|
|
230
|
+
a constraint that cares about any other location reads it itself.** Nothing about the machinery moved; the
|
|
231
|
+
guide's `§7.6` now says it, and the same section now names the exact-address consequence beside it: **a pass
|
|
232
|
+
judges the pairs standing at the address it writes, so a pair at another address is not asked** (measured: a
|
|
233
|
+
pair at `['b']` asked `0` times by a write at `['a']`).
|
|
234
|
+
|
|
235
|
+
4. **THE PASS'S STATE MACHINE IS PINNED BY CELLS (NO BEHAVIOUR CHANGE).** The architect's state machine — one
|
|
236
|
+
action, the checks in order, **a repair restarting the checks from `#1` because a repair can break a check
|
|
237
|
+
already passed**, and termination being structural rather than counted — is now measured property by property
|
|
238
|
+
through the entry (`tests/e2e/repair-restart-state-machine.test.mjs`, whose cells also print that a pass
|
|
239
|
+
evaluates exactly the pairs at the attempt's own address). **The behaviour is the behaviour `1.1.0` already
|
|
240
|
+
ships; what is new is that it is pinned, so a later move meets a red instead of a reader.**
|
|
241
|
+
|
|
242
|
+
**AND ONE NUMBER A CONSUMER MIGHT WANT, WITH ITS WARNING: THE RESTART BUDGET IS `3` AND IT NOW SAYS SO IN THE
|
|
243
|
+
GUIDE.** `RESTART_LIMIT = 3` at `bindings/js/src/entry/constraints.mjs:1366` has been the behaviour since
|
|
244
|
+
`1.1.0`; the shipped guide declared it nowhere, and the guide's `§7.7` now does. **This is a DECLARATION and not
|
|
245
|
+
a change** — the bound, its spend-and-never-reset rule and its fail-closed arm stand exactly as they shipped.
|
|
246
|
+
|
|
247
|
+
**NOTHING ELSE IN THIS LIST IS A BREAKING CHANGE, AND NOTHING IN IT IS CUT.** Items `1` and `2` are the two a
|
|
248
|
+
consumer can feel; `3` and `4` are declarations and pins. **When `1.2.0` is cut, this section is the declaration
|
|
249
|
+
it carries — and until then the six figures a reader checks are PREPARED at `1.2.0` and the tag is NOT CUT, so what
|
|
250
|
+
is installed today is still `1.1.0`.**
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## §0 · What to do if you are on `1.0.1`
|
|
255
|
+
|
|
256
|
+
1. **Re-read your refusal mapper.** Read §2 items `B-1`, `B-2` and `B-5` before you upgrade. **A status you
|
|
257
|
+
branch on has changed meaning and one has changed value.** If your mapper treats `FileCommitFailed` as "a
|
|
258
|
+
constraint refused my write", it is now WRONG: that code is the SAVE's alone.
|
|
259
|
+
2. **Wrap `rollback` in your throw path.** It throws now (§2 `B-3`) where a total function was declared.
|
|
260
|
+
3. **Stop assuming a refused attempt leaves the refused value.** It leaves the state before the attempt
|
|
261
|
+
(§2 `B-4`).
|
|
262
|
+
4. **Add `op` to anything that reads a delete's record** (§2 `B-7`). The record grew a fourth member; a
|
|
263
|
+
delete's reversal is now reachable by that member's id.
|
|
264
|
+
5. **Do not carry a handle, an address object or a store instance across the recovery exits in §3.** Those
|
|
265
|
+
exits are the consumer's act, and the package does not perform them.
|
|
266
|
+
6. **If you pinned `<1.0.1`** — the answer to `1.0.1`'s breach — **`1.1.0` is the version that supersedes it.**
|
|
267
|
+
Pin `1.1.0`, not `1.0.1`: the four items `B-1`…`B-4` reached `1.0.1`'s bytes UNDECLARED, and you get them
|
|
268
|
+
either way.
|
|
269
|
+
|
|
270
|
+
**A NOTE ON `B-1`…`B-4` AND THE WORD "AT `1.0.1`".** Those four were already in `1.0.1`'s shipped bytes —
|
|
271
|
+
`src/error.rs`'s `CODES` reads `[&str; 20]` at the `v1.0.1` tag, not `17`. **So for each of them this file
|
|
272
|
+
gives the reading at `1.0.0` (what moved), the fact that `1.0.1` shipped the move UNDECLARED, and the
|
|
273
|
+
reading at `1.1.0` (which is the same behaviour, now declared).** A consumer already on `1.0.1` has absorbed
|
|
274
|
+
them; **`B-5` onward are what `1.1.0` changes under them.**
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## §1 · The shape of the list, and where it exceeded the ruling's count
|
|
279
|
+
|
|
280
|
+
`D-42` item `1` named **FOUR** breaking changes. **Derived from the tree rather than from the ruling's count,
|
|
281
|
+
the list is ELEVEN.** Four were shipped in `1.0.1` (`B-1`…`B-4`); seven more land in `1.1.0` (`B-5`…`B-11`).
|
|
282
|
+
**The list exceeded `D-42`'s four at `B-5`**, and every addition below was measured at its own site — the
|
|
283
|
+
commits named beside each item are the ones that made the change.
|
|
284
|
+
|
|
285
|
+
| # | the change | shipped in |
|
|
286
|
+
|---|---|---|
|
|
287
|
+
| `B-1` | the emitted vocabulary `17 → 20` | `1.0.1` (undeclared) |
|
|
288
|
+
| `B-2` | a constraint-refused write's status is no longer `FileCommitFailed` | `1.0.1` (undeclared) |
|
|
289
|
+
| `B-3` | `rollback` went from total to partial: **it throws** | `1.0.1` (undeclared) |
|
|
290
|
+
| `B-4` | a failing attempt leaves the pre-attempt state (`9 → 5`) | `1.0.1` (undeclared) |
|
|
291
|
+
| `B-5` | a refused reversal is RESTORED, and its restore WRITES the file | `1.1.0` |
|
|
292
|
+
| `B-6` | a constraint check always runs inside an open commit | `1.1.0` |
|
|
293
|
+
| `B-7` | `DeleteRecord` gained `op` — a record's shape grew | `1.1.0` |
|
|
294
|
+
| `B-8` | a delete's journal before-state was captured at the wrong home | `1.1.0` |
|
|
295
|
+
| `B-9` | an external callback's call opens its OWN commit | `1.1.0` |
|
|
296
|
+
| `B-10` | an action inside an open commit RESTARTS the constraint check | `1.1.0` |
|
|
297
|
+
| `B-11` | a directly driven pass reports a restart as a REPORT, not a throw | `1.1.0` |
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## §2 · The breaking changes, one line of "before" and one of "now"
|
|
302
|
+
|
|
303
|
+
### `B-1` · The emitted status vocabulary grew `17 → 20`
|
|
304
|
+
|
|
305
|
+
**AT `1.0.0`:** `CODES` was `[&str; 17]` — ranks `1`…`17`, the last being `ValueRefusedAtBoundary`.
|
|
306
|
+
**`1.0.1` SHIPPED THREE MEMBERS UNDER A PATCH WITHOUT SAYING SO.**
|
|
307
|
+
**AT `1.1.0`:** the set is **`[&str; 20]`**, ranks `1`…`20`, and the three appended members are:
|
|
308
|
+
|
|
309
|
+
- **rank `18` `ConstraintBodyThrew`** — a constraint BODY threw, where the package's crossing used to answer
|
|
310
|
+
the store's own code for a body that failed;
|
|
311
|
+
- **rank `19` `ConstraintRefusedAtRegistration`** — the store refused a pair AT REGISTRATION;
|
|
312
|
+
- **rank `20` `ConstraintPassRefused`** — the ONE status a constraint's refusal of a `write` answers, and of
|
|
313
|
+
a `rollback` whose terminal state a pair refuses.
|
|
314
|
+
|
|
315
|
+
**WHAT BREAKS YOU:** a mapper with a closed `switch` over the seventeen old members now meets three values it
|
|
316
|
+
has no arm for, and `errorMessage(code, args)` resolves a clause for each. **NO RANK OF `1`…`17` MOVED**, so
|
|
317
|
+
an ordinal you already stored is still that member (`FileCommitFailed` is still rank `15`).
|
|
318
|
+
**ONE MEMBER IS NOT A CONSUMER'S TO SEE:** `ConstraintRefusedAtRegistration` is the store's and crosses only
|
|
319
|
+
where the store mints it.
|
|
320
|
+
|
|
321
|
+
### `B-2` · A constraint-refused write's status is `ConstraintPassRefused`, not `FileCommitFailed`
|
|
322
|
+
|
|
323
|
+
**AT `1.0.0` (and under `1.0.1`'s undeclared change, before it):** a write a constraint refused crossed as
|
|
324
|
+
`FileCommitFailed` — **which is the STORE'S SAVE CODE, rank `15`** — so the status named a layer the failure
|
|
325
|
+
did not happen at.
|
|
326
|
+
**AT `1.1.0`:** the refusal crosses as **`ConstraintPassRefused`, rank `20`**, the pass's own member.
|
|
327
|
+
**`FileCommitFailed` STAYS the save's code and is produced by save failures ONLY.**
|
|
328
|
+
**WHAT BREAKS YOU:** the most likely break in this release. **A consumer who treated `FileCommitFailed` as
|
|
329
|
+
"a constraint said no" now reads a save failure as a constraint refusal.** The refusal's own `KC` token still
|
|
330
|
+
rides `log[].state`; it is one status for all eight tokens, not one per token.
|
|
331
|
+
|
|
332
|
+
### `B-3` · `rollback` went from total to partial, and it THROWS
|
|
333
|
+
|
|
334
|
+
**AT `1.0.1`'s DECLARATION:** `rollback(opId): RollbackRecord` — **a total function that answers a record and
|
|
335
|
+
fails never.** It was already untrue of `1.0.1`'s code; **this file declares it against the code, which is
|
|
336
|
+
where the truth is.**
|
|
337
|
+
**AT `1.1.0`:** a reversal is JUDGED. **A `rollback` whose TERMINAL state a registered pair refuses THROWS
|
|
338
|
+
`{status: 'ConstraintPassRefused', message, log}` at rank `20`.** A LAWFUL reversal is unchanged — same
|
|
339
|
+
`{opId, undoneOps, restoredTo}`, three members, no throw, no new member. **The judgement is of the state the
|
|
340
|
+
reversal LEFT, never a per-step approval** — an unwinding composition passes through intermediate states no
|
|
341
|
+
constraint would approve.
|
|
342
|
+
**WHAT BREAKS YOU:** a call you could not fail now throws a plain object literal (never an `Error` subclass).
|
|
343
|
+
**AND THE THROW IS NOT THE WHOLE OF IT — SEE `B-5`: THE REVERSAL IS ALSO UNDONE.**
|
|
344
|
+
**THE JUDGEMENT IS NOT A WALK AND NOT FREE:** it takes a pre-reversal standing of every enabled pair's every
|
|
345
|
+
address before the store call and asks the terminal state after it. A store that registered no constraint
|
|
346
|
+
reaches no read at all, and the member is what it was.
|
|
347
|
+
|
|
348
|
+
### `B-4` · A failing attempt leaves the PRE-ATTEMPT state, not the refused value
|
|
349
|
+
|
|
350
|
+
**AT `1.0.0`:** seed `5`, refused write `9`, repair `1` — **the store was left holding `9`, the value the
|
|
351
|
+
constraint REFUSED.** A per-op walk over the repair's op reversed it AND marked it compacted, so the second
|
|
352
|
+
call over the write's op was answered `RollbackCompacted` and undid nothing. **The floor law made the walk
|
|
353
|
+
unsound and the violation stood.**
|
|
354
|
+
**AT `1.1.0`:** the failing arm leaves **`5`** — the state before the attempt. **A plain rejection still
|
|
355
|
+
leaves it, a repair answering `{ok: false}` still leaves it, and a PASSING repaired write still lands its
|
|
356
|
+
repaired value and composes nothing.**
|
|
357
|
+
**WHAT BREAKS YOU:** if you compensated for the old residue in your own layer, **your compensation now
|
|
358
|
+
unwinds a state that is already unwound.** The composition reports one step per address it issued a call at,
|
|
359
|
+
and `report.rollback` carries what actually happened.
|
|
360
|
+
|
|
361
|
+
### `B-5` · A refused reversal is RESTORED, and its restore WRITES THE FILE
|
|
362
|
+
|
|
363
|
+
**AT `1.0.1`:** the compensating undo cleared every tier and rewrote at `mem` — **so a `file`-tier value was
|
|
364
|
+
DESTROYED by a refusal that then told you every address stood again at its pre-reversal state** (measured by
|
|
365
|
+
the downstream at 200 of 200), **and the restore was in memory only, so a re-boot read bytes describing the
|
|
366
|
+
PRE-restore state.** *(Commits `00f90df`, `9e16c0e`, `bd0fd8c`.)*
|
|
367
|
+
**AT `1.1.0`:** the restore puts back **exactly the per-tier standing it captured** — a present capture writes
|
|
368
|
+
each tier at its own tier, most persistent first, so a shadow reconstructs; an absent capture becomes ONE
|
|
369
|
+
untiered delete, where `AddressNotFound` **is** the owed state; **no tier that stood is ever cleared.** It
|
|
370
|
+
**verifies the result and counts an address restored only if it is exact**, so the message cannot claim what
|
|
371
|
+
did not happen — where it fails it names the address, what stands, what was owed, and how many of the
|
|
372
|
+
addresses it moved stand again. It restores **from the store's own journal** (`restoreMark`), not from a JS
|
|
373
|
+
capture, so a mixed-depth shadow and a higher tier whose parent the reversal moved below it are now reachable.
|
|
374
|
+
**AND `bd0fd8c` MAKES THE FILE AGREEMENT REAL:** the restore takes a `file`-tier snapshot before and after the
|
|
375
|
+
replay, and **where a version standing at `Tier::File` moved it SAVES the restored image** with the store's one
|
|
376
|
+
writer — so a re-boot returns to JUDGED bytes.
|
|
377
|
+
**WHAT BREAKS YOU — TWO THINGS, BOTH NEW BEHAVIOUR:**
|
|
378
|
+
- **THE BYTES AT `cfg.filePath` ARE NO LONGER "the last successful `file`-tier write's and no other state".**
|
|
379
|
+
A refused reversal's restore is now a SECOND producer of them. **A consumer who diffs those bytes, hashes the
|
|
380
|
+
file, or watches its inode/mtime will see it move on a path that used to write nothing** — and where no
|
|
381
|
+
file-tier version moved, **no byte is touched and the inode and mtime stay put.**
|
|
382
|
+
- **THE ASYMMETRY, SAID PLAINLY RATHER THAN LEFT TO BE DISCOVERED: a SUCCESSFUL `rollback` persists NOTHING.**
|
|
383
|
+
Measured: `file` `a=1`, `file` `a=2`, rollback the second — memory reads `1`, the bytes still say `{"a":2}`,
|
|
384
|
+
and a fresh boot reads `2`. **A reversal writes no bytes; a restore now does.**
|
|
385
|
+
**AND A SAVE THAT CANNOT LAND IS REPORTED, NOT SWALLOWED.** `FileCommitFailed` propagates after the replay and
|
|
386
|
+
the refusal says the store restored its state IN MEMORY while its FILE WAS NOT UPDATED. **The status stays
|
|
387
|
+
`ConstraintPassRefused`; nothing is minted for it.** **A constraint BODY that throws at the judgement now fails
|
|
388
|
+
closed**: the reversal is restored and the log names the throwing pair, with the body's own rank-`18` status
|
|
389
|
+
crossing — the code says what happened, the treatment is the violation's.
|
|
390
|
+
**STILL NOT RESTORABLE, NAMED SO IT IS NOT ASSUMED:** mixed-depth shadows *(reached by `restoreMark` now)*; an
|
|
391
|
+
address whose parent the journal never resolved *(reported as residue rather than guessed)*; state changed by
|
|
392
|
+
an act the store does not journal; an entry a failed file-save removed.
|
|
393
|
+
|
|
394
|
+
### `B-6` · No constraint check without an open commit — a check always runs inside one
|
|
395
|
+
|
|
396
|
+
**AT `1.0.1`:** a CHECK could run where NO commit was open. That one state was every remaining window on this
|
|
397
|
+
branch's list — a listener body's write at a descendant, a pass driven by hand (`open=0`/`live=1`), and the
|
|
398
|
+
restart's dependence on the write path being the consumer. *(Commit `846f5ea`.)*
|
|
399
|
+
**AT `1.1.0`:** **an open commit is the PRECONDITION of the check**, enforced **where the check runs, not where
|
|
400
|
+
it is called from** — a seam a driver can bypass is not an invariant. `runAttempt` wraps its whole pass in the
|
|
401
|
+
store's own commit scope: **the scope opens one if none is open, joins the one that is, and closes only what it
|
|
402
|
+
opened.**
|
|
403
|
+
**WHAT BREAKS YOU:** **a store that carries no such channel is REFUSED, not run uncommitted** — the invariant
|
|
404
|
+
fails closed before status moves, before an entry, before any body, and without minting a token. **So a
|
|
405
|
+
consumer or a harness that hand-drives a pass over a store lacking that seam now gets a refusal where it used
|
|
406
|
+
to run.** **MEASURED: the production write's nesting is UNCHANGED** — the count during the pass is one at the
|
|
407
|
+
body's ask and one after its nested write. **A pass you drive directly now carries `open 1` where it carried
|
|
408
|
+
`0`**, opening exactly one commit itself and returning to zero.
|
|
409
|
+
|
|
410
|
+
### `B-7` · `DeleteRecord` gained `op` — a record's shape grew
|
|
411
|
+
|
|
412
|
+
**AT `1.0.1`:** `delete` answered **`{address, tiers, triggered}`** — three members, **and no identity.** The
|
|
413
|
+
act was already journalled and `rollback` already accepted its id, **but no caller could NAME the act, so no
|
|
414
|
+
caller could reverse it**: an act that cannot be named cannot be reversed. *(Commit `c25765c`.)*
|
|
415
|
+
**AT `1.1.0`:** `delete` answers **`{address, tiers, triggered, op}`** — `op` is the store's own `JournalOp`,
|
|
416
|
+
**the same member, at the same name and the same type, that `WriteRecord` carries**, so `rollback(record.op.opId)`
|
|
417
|
+
is the one route to reversing a delete.
|
|
418
|
+
**`op` IS `null` ON THE ARM THAT REMOVED NOTHING** (`tiers: []`): that arm appends no journal entry, so it names
|
|
419
|
+
no op — **`null` is the truth rather than a minted id no reversal could find.**
|
|
420
|
+
**WHAT BREAKS YOU:** an object-shape assertion (`Object.keys(...).length === 3`, a strict deep-equal, a
|
|
421
|
+
serializer with a closed schema) breaks. **And `null` does not fold to `undefined`** — it crosses as `null`
|
|
422
|
+
unchanged.
|
|
423
|
+
|
|
424
|
+
### `B-8` · A delete's journal before-state was captured at the wrong home
|
|
425
|
+
|
|
426
|
+
**AT `1.0.1`:** the entry's before-state was captured at the WRONG HOME — `pair_home` resolved the first
|
|
427
|
+
standing version's link — **so a STRUCTURED target journalled an EMPTY before-state and its reversal restored
|
|
428
|
+
NOTHING AT ALL.** Scalars looked healthy, which is why it went unseen; an EMPTY-ADDRESS delete had the same
|
|
429
|
+
fate. *(Commit `c25765c`, the defect the `B-7` exposure had been hiding.)*
|
|
430
|
+
**AT `1.1.0`:** the capture is at the correct home, and **an empty-address delete captures the register and
|
|
431
|
+
reverses too.**
|
|
432
|
+
**WHAT BREAKS YOU:** **code that worked around the old behaviour** — a manual re-write after reversing a
|
|
433
|
+
subtree delete — now restores over a correction that already happened. The reversal restores the values AND
|
|
434
|
+
their tiers, scalar and subtree alike. **A SECOND reversal of the same op answers `RollbackCompacted`**, which
|
|
435
|
+
is the existing floor and not a new rule.
|
|
436
|
+
|
|
437
|
+
### `B-9` · An external callback's call opens its OWN commit
|
|
438
|
+
|
|
439
|
+
**AT `1.0.1`:** the join rule said a nested call *"JOINS the extant commit rather than opening a second"* — it
|
|
440
|
+
was written to keep ONE ACT's machinery from double-counting itself, **and it conflated INTERNAL NESTING (the
|
|
441
|
+
package's machinery inside its own verb) with EXTERNAL RE-ENTRY (your function, which the package invoked,
|
|
442
|
+
calling back in).** A listener body's write reused the outer verb's commit, so a listener's write at a
|
|
443
|
+
DESCENDANT address left the ancestor pair unasked and the violating effect stood. *(Commits `98cb3ac`,
|
|
444
|
+
`835f09f`; rulings `D-45`, `D-46`.)*
|
|
445
|
+
**AT `1.1.0`:** **a listener body — and every one of the caller-code invocation sites — OPENS A NEW COMMIT**,
|
|
446
|
+
because that is what entering at the package surface means. **The discriminator is the COMMIT ID a call NAMES,
|
|
447
|
+
not an authorship the package infers:** a call that supplies a commit id **JOINS** that commit; **a call that
|
|
448
|
+
supplies none OPENS a new one.** `write`'s FOURTH position already carries the commit, so **the surface did not
|
|
449
|
+
grow a member — it grew a rule about one it has.**
|
|
450
|
+
**A REPAIR JOINS** — a repair is not a separate act at the surface, it is PART OF THE ATTEMPT BEING TRACKED, so
|
|
451
|
+
it stays inside the attempt's own commit and the commit's record still describes the repair it performed.
|
|
452
|
+
**WHAT BREAKS YOU — THE OBSERVABLE COUNTS MOVE:** a listener body's write now **opens a second commit**, so an
|
|
453
|
+
attempt-opened observer **fires TWICE where it fired once**, and the descendant address is now asked by the
|
|
454
|
+
listener's OWN attempt rather than the outer write's. **The outer commit's count is unchanged across the call
|
|
455
|
+
and the outer write's record is untouched.** An ordinary write with a pair body and a listener body inside it
|
|
456
|
+
still opens ONE commit. **ONE EXTERNAL FUNCTION STILL INHERITS, named rather than hidden: the attempt-opened
|
|
457
|
+
observer itself**, which is called inside the opening before it returns — marking it would let a self-writing
|
|
458
|
+
observer open a commit at every notification, unbounded.
|
|
459
|
+
|
|
460
|
+
### `B-10` · An action inside an open commit RESTARTS the constraint check
|
|
461
|
+
|
|
462
|
+
**AT `1.0.1`:** a reversal issued INSIDE an open commit — from a listener body, from a constraint body — was
|
|
463
|
+
**UNJUDGED**, so its violating effect STOOD, **while the identical call one frame out was refused and
|
|
464
|
+
restored.** The conservative direction had become the permissive one. *(Commit `fd11eff`.)*
|
|
465
|
+
**AT `1.1.0`:** **an action performed inside an open commit STARTS THE CONSTRAINT CHECK OVER AGAIN** — the
|
|
466
|
+
check the action invalidated is **re-run from its FIRST pair against the state the action left**, so no
|
|
467
|
+
violating effect can stand unjudged and no body is invoked mid-unwind either, because the restart is a fresh
|
|
468
|
+
pass and not a judgement of a partial composition. **The step itself is still not judged at its instant**
|
|
469
|
+
(`D-40`'s gate stands: the watch keeps answering empty and no body runs there).
|
|
470
|
+
**WHAT BREAKS YOU:** **a violating reversal inside an open commit is now REFUSED and restored, where it used to
|
|
471
|
+
stand.** Measured: a listener body asked `[9,7,5]` and the write was REFUSED, store at `7`; a constraint body
|
|
472
|
+
the same; a LAWFUL act asked `[9,7,5]` with NO refusal and its effect stands. **TERMINATION IS BOUNDED RATHER
|
|
473
|
+
THAN ASSUMED: three restarts per terminal act**, the budget spent and not reset by actions raised inside a
|
|
474
|
+
restart, **and past it the check FAILS CLOSED in the same form.** **So a consumer whose body writes and
|
|
475
|
+
reverses in a loop now meets a refusal at the third restart instead of an unbounded pass.**
|
|
476
|
+
|
|
477
|
+
### `B-11` · A directly driven pass LEARNS a restart from the REPORT, not from a throw
|
|
478
|
+
|
|
479
|
+
**AT `1.0.1`:** the restart's request was consumed by the WRITE's frame, **so a body's `rollback` inside a
|
|
480
|
+
directly driven pass was neither judged at its instant nor restarted, and the request died with its window.**
|
|
481
|
+
*(Commit `0ade6b3`.)*
|
|
482
|
+
**AT `1.1.0`:** the request is a store-side fact and **its consumer is the store's own commit scope** — one
|
|
483
|
+
mechanism, not two. `runAttempt` drains it after its pass answers, inside the same scope. **And a directly
|
|
484
|
+
driven pass learns it from the REPORT rather than a throw: the outcome becomes `failed` and the refusal's own
|
|
485
|
+
constraint-check entry joins the attempt's log with its REAL ordinal**, so `K-3` does not throw while `K-5`
|
|
486
|
+
answers the same object.
|
|
487
|
+
**WHAT BREAKS YOU:** **a harness that drove a pass by hand and expected a THROW now gets none** — it must read
|
|
488
|
+
`report.outcome` and `report.log`. **The refusal's compensating restore runs before the report is amended**, so
|
|
489
|
+
your store stands restored; what you must READ is the report, never a throw.
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## §3 · The two known fail-states, and their exits
|
|
494
|
+
|
|
495
|
+
**These are DECLARED, not fixed.** `D-43` item `2`, the architect, verbatim: *"Known fail-state, file reloads /
|
|
496
|
+
client regenerates store to return to valid state."* **A known fail-state with a documented recovery is a
|
|
497
|
+
specification; an unreachable window with no exit is a defect.** **The declaration of record is
|
|
498
|
+
`docs/specs/storeroom-js/contract.md`'s clause of 2026-10-27 and it is restated here because a consumer of the
|
|
499
|
+
tarball reads this file.**
|
|
500
|
+
|
|
501
|
+
**THE TWO WINDOWS.** Each holds a commit and **runs NO PASS**, so a request raised inside it has no consumer
|
|
502
|
+
and dies with that window:
|
|
503
|
+
1. **A `delete`'s listener delivery** — a delete consults no constraint, so the delivery runs with an open
|
|
504
|
+
commit and no check; a `rollback` a listener body issues there is a step whose check never runs.
|
|
505
|
+
2. **A REGISTRATION's own evaluation** — `addConstraint`/`register` asks a pair's body inside the registering
|
|
506
|
+
verb's own commit window, at an instant no `K-2`/`K-3` attempt stands at, so a `rollback` a registration
|
|
507
|
+
body issues dies the same way.
|
|
508
|
+
|
|
509
|
+
**NEITHER WINDOW IS NEW, NEITHER IS A DEFECT, AND NEITHER IS AN UNREACHED CASE OF THE RESTART.**
|
|
510
|
+
**WHAT IS TRUE OF BOTH, SAID BEFORE THE EXIT: the reversal's own effect STANDS in the live instance — so the
|
|
511
|
+
absence of a refusal there is NOT an approval and must not be read as one.**
|
|
512
|
+
|
|
513
|
+
**EXIT 1 — RELOAD THE FILE.** **Construct a fresh instance over the same `cfg.filePath`.** The boot reads the
|
|
514
|
+
bytes at the path and nothing else. **AND SINCE `1.1.0` THIS RETURNS TO JUDGED BYTES RATHER THAN TO BYTES
|
|
515
|
+
DESCRIBING AN UNJUDGED STATE**: a restore that moved a version standing at `Tier::File` now SAVES the restored
|
|
516
|
+
image (§2 `B-5`). **What it does not carry:** the boot reads the last successful save and no other state — no
|
|
517
|
+
journal, no registration, no `mem` or `temp` version — and a reversal that moved only `mem`/`temp` nodes does
|
|
518
|
+
not save, so a reload does not carry it. **This exit is the store's declared boot semantics, not a general
|
|
519
|
+
undo.**
|
|
520
|
+
|
|
521
|
+
**EXIT 2 — REGENERATE THE STORE.** **Discard the instance and construct the store again from the same
|
|
522
|
+
configuration** — a `mem` register with no `filePath`, or a `file` register over the same path, which is exit
|
|
523
|
+
`1` as well. **The new instance is a judged state by construction**: no attempt, no open commit, no window
|
|
524
|
+
carried over.
|
|
525
|
+
|
|
526
|
+
**THE CONSUMER'S OBLIGATION IS THE SAME ON BOTH EXITS, AND IT IS THE POINT: the recovery is YOUR act; the
|
|
527
|
+
package neither performs it nor pretends to.**
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
## §4 · What did NOT break — so the reading is not taken wider than it is
|
|
532
|
+
|
|
533
|
+
- **THE FOURTEEN OWN MEMBERS ARE UNMOVED** — `{ read, readLiteral, write, delete, rollback, listen, unlisten,
|
|
534
|
+
addConstraint, constraintOn, constraintOff, register, constraints, removeListener, removeConstraint }`.
|
|
535
|
+
**No member was added and none was removed.** The module namespace keeps its ONE name, `storeCreate`.
|
|
536
|
+
- **NO POSITION, ARITY OR CLASS TEST MOVED.** `write`'s fourth position was READ for a new purpose and its
|
|
537
|
+
class domain is unmoved: it accepts the commit RECORD and refuses everything else in the one `TypeError`.
|
|
538
|
+
- **THE THROWN FORM IS THE SAME ONE FORM** — a plain object literal `{status, message, log}`, never an `Error`
|
|
539
|
+
subclass — and `ConstraintPassRefused` is a member of the SAME set, not a new shape.
|
|
540
|
+
- **REGISTRATION ORDER IS A GUARANTEE, NOT A LUCK.** `constraints()` emits by a MONOTONE REGISTRATION
|
|
541
|
+
SEQUENCE, kept through overwrite, toggle and restore, so five snapshots are element-wise equal and equal to
|
|
542
|
+
registration order. **This is new in `1.0.1` and it is a GUARANTEE, NOT A BREAK** — before it, *"no order is
|
|
543
|
+
promised"* and a toggle could perturb it. It is declared here because a consumer diffing snapshots relies on
|
|
544
|
+
it and because a later change would be a break.
|
|
545
|
+
- **NO TABLE ROW, RANK OR TRIGGER MOVED** inside ranks `1`…`17`.
|
|
546
|
+
- **THE CRATE'S VERSION IS NOT THE RELEASE VERSION.** `bindings/js/Cargo.toml` reads the crate's own `0.1.0`
|
|
547
|
+
and `version()` answers the RELEASE's. They are deliberately distinct.
|
|
548
|
+
|
|
549
|
+
---
|
|
550
|
+
|
|
551
|
+
## §5 · Where the authority lives
|
|
552
|
+
|
|
553
|
+
- **The declaration of record** is the tree under `docs/specs/` — chiefly `storeroom-js/contract.md` (the
|
|
554
|
+
package surface), `storeroom-native/contract.md` (the store), and `storeroom-publish/contract.md` (the six
|
|
555
|
+
packages' coupling). **This note is a CONSUMER SUMMARY of them and yields to them.**
|
|
556
|
+
- **The rulings** are `strata/decisions.md` — `D-42` (the minor, the four, the restart), `D-43` (a listener's
|
|
557
|
+
own commit, the known fail-state, a journaled and reversible delete), `D-44` (no check without an open
|
|
558
|
+
commit), `D-45`/`D-46` (a callback opens, a repair joins, the commit id decides), `D-41`/`D-40` (the
|
|
559
|
+
attempt's footprint and the step's gate).
|
|
560
|
+
- **The shipped guide** is `README.guide.md` INSIDE the main package — the assembly stages
|
|
561
|
+
`docs/guides/package-use.md` there BYTE-FOR-BYTE, so when you read it from `node_modules/provident-storeroom/`
|
|
562
|
+
it IS that document.
|
|
563
|
+
|
|
564
|
+
**THE SIX PACKAGES, ONE VERSION, ONE DECISION:** `provident-storeroom` and the five
|
|
565
|
+
`@auspicion/provident-storeroom-js-{linux-x64-gnu, linux-arm64-gnu, darwin-x64, darwin-arm64, win32-x64-msvc}`
|
|
566
|
+
all read **`1.1.0`**, and the main manifest's five `optionalDependencies` pins are EXACT `1.1.0` — no caret, no
|
|
567
|
+
tilde, no range. **A manifest that reads anything else anywhere is the wrong package.**
|
|
568
|
+
|
|
569
|
+
**STATUS OF THIS RELEASE'S OWN REHEARSAL:** the six tarballs were packed, a `1.1.0` install was driven in a
|
|
570
|
+
`/tmp` consumer with no repository in its ancestry, and `version()` answered `1.1.0` at the rebuilt artefact and
|
|
571
|
+
at the installed package. **No tag was cut, nothing was pushed, nothing was published, and no registry install
|
|
572
|
+
was witnessed** — a registry-installed `1.1.0` is described here from the manifest and from a locally packed,
|
|
573
|
+
installed artefact, and is still not witnessed as a registry download.
|
|
574
|
+
|
|
575
|
+
**⟶ AND THE TWO PARAGRAPHS ABOVE ARE `1.1.0`'S OWN READING AND ARE READ THROUGH THIS CLAUSE, 2026-10-28: AT THIS
|
|
576
|
+
TREE THE SIX FIGURES READ `1.2.0`, AND `1.2.0` IS PREPARED AND NOT CUT.** `RCA-8(g)` ANNOTATE-BESIDE: **both stand
|
|
577
|
+
BYTE-FOR-BYTE; what each says of `1.1.0` was true of the tree it was read on.** **THE SIX FIGURES, RE-READ AT THEIR
|
|
578
|
+
OWN BYTES THIS DATE: the main manifest's `version` is `"1.2.0"`, its five `optionalDependencies` pins are exact
|
|
579
|
+
`1.2.0` with it, and the five platform manifests' `version` fields are `1.2.0` with them — eleven sites, moving
|
|
580
|
+
together and never singly** (`PUB-DD-10`) — **so the rule the paragraph above teaches is UNMOVED and its figure is
|
|
581
|
+
`1.2.0`: a manifest that reads anything else anywhere is the wrong package.** **THE CUT, AND WHAT THIS FILE STILL
|
|
582
|
+
DOES NOT CLAIM:** `git tag --list` carries `v1.0.0` · `v1.0.1` · `v1.1.0` and no `v1.2.0`, so **`1.2.0` is PREPARED
|
|
583
|
+
at those eleven figures and the tag is not cut until the architect cuts it**; the bytes a registry install yields
|
|
584
|
+
today are `1.1.0`'s, and nothing here says otherwise.
|