wyrd-scribe 0.1.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/dist/span.js ADDED
@@ -0,0 +1,242 @@
1
+ /**
2
+ * B1 — THE SPAN RESOLVER. Bytes in, a located span or a refusal out.
3
+ *
4
+ * Built to `designs/2026-08-31-span-resolver-plan.md`, which carries the reasoning and four rounds
5
+ * of gate findings. The one rule everything here serves:
6
+ *
7
+ * ⚠⚠ NO SILENT MISLOCATION. AMBIGUITY REFUSES; IT NEVER PICKS.
8
+ *
9
+ * ⚠ FENCE-FREE, AND THAT IS STRUCTURAL RATHER THAN POLITE. This module takes `(bytes, request)` and
10
+ * no path. It never opens a file, never imports the fence, and has no way to reach the filesystem —
11
+ * reading is the fence's job, and a resolver that opens files has become a second write path.
12
+ *
13
+ * ⚠ THE PROPERTY THIS MODULE PROTECTS: no substituted span resolution in a refusal, and no source
14
+ * bytes in EITHER variant. It is NOT the fence's no-absolute-path rule — that belongs to
15
+ * `wyrd-fence` (`packages/wyrd-fence/src/fsgate.ts`) and is asserted there. A reviewer briefed on
16
+ * the fence's property is on the wrong axis here.
17
+ *
18
+ * ⚠⚠ AND THE PROPERTY IS NOT ENFORCED BY THESE TYPES. `SpanRefusal` and `ResolvedSpan` are
19
+ * TypeScript-only shapes: the compiler forbids no extra property at runtime, and an unchecked
20
+ * assignment can put a value anywhere. The enforcement is the own-key envelope in
21
+ * `test/span.test.js`, applied to EVERY arm — refusal and success alike. This comment is not a
22
+ * mechanism.
23
+ *
24
+ * ⚠ `Object.freeze` on every return is the runtime half OF THE EXTRA-KEY RULE ONLY. It says
25
+ * nothing about what an ACCESSOR returns: freeze constrains the property, never the getter, so a
26
+ * frozen own getter can serve a correct value to the first readers and the source buffer to the
27
+ * next. Measured at code-gate round 2 — it passed all 29 arms. What closes that is the
28
+ * data-property assertion in `envelope()`, not this line.
29
+ */
30
+ /**
31
+ * ⚠ AN UNKNOWN KEY REFUSES RATHER THAN BEING IGNORED, and the reason is a fail-open, not tidiness.
32
+ * `{ offset, length, qoute }` read leniently is a valid OFFSETS-ONLY request: it succeeds, records
33
+ * a quote the caller never asked to have verified, and silently drops the verification the caller
34
+ * was asking for. That is mislocation arriving through a typo.
35
+ */
36
+ const REQUEST_KEYS = new Set(['offset', 'length', 'quote']);
37
+ function refuse(reason, where) {
38
+ return Object.freeze({ ok: false, reason, where });
39
+ }
40
+ function resolved(offset, length, quote) {
41
+ return Object.freeze({ ok: true, offset, length, quote });
42
+ }
43
+ /** A UTF-8 continuation byte — `10xxxxxx`. A span may not START on one, nor END just before one. */
44
+ function isContinuation(byte) {
45
+ return byte !== undefined && (byte & 0xc0) === 0x80;
46
+ }
47
+ /**
48
+ * ⚠ BOTH ENDPOINTS, AND THE ARMS TEST THEM SEPARATELY. `é` is `c3 a9`: `{1,1}` cuts the START,
49
+ * `{0,1}` cuts the END, and a check that validates one side passes half the rule while reading as
50
+ * complete.
51
+ *
52
+ * Why a non-aligned span refuses at all: D4 stores the quoted TEXT over a source model the spec
53
+ * declares UTF-8, and a lone `a9` has no lossless UTF-8 text representation. Decoding substitutes
54
+ * `U+FFFD`, so the stored quote no longer re-encodes to the bytes it came from — the identity D4
55
+ * exists to establish is destroyed at the moment of recording.
56
+ */
57
+ function alignmentFault(bytes, offset, length) {
58
+ if (isContinuation(bytes[offset]))
59
+ return 'offset';
60
+ const end = offset + length;
61
+ if (end < bytes.length && isContinuation(bytes[end]))
62
+ return 'length';
63
+ return null;
64
+ }
65
+ /**
66
+ * ⚠ THE QUOTE IS VALIDATED FOR LOSSLESS UTF-8 ROUND-TRIP BEFORE ANY MATCHING. `"\uD800"` is a
67
+ * well-formed JavaScript string and NOT well-formed Unicode text; encoding it substitutes
68
+ * `U+FFFD`, so an unvalidated lone-surrogate quote would match GENUINE replacement-character bytes
69
+ * in the source and return a quote that does not identify them.
70
+ *
71
+ * The empty quote refuses here too: against an empty source it would otherwise "uniquely match"
72
+ * and mint a zero-length span, which the positive-length rule forbids on the offsets side.
73
+ */
74
+ function quoteFault(quote) {
75
+ if (typeof quote !== 'string' || quote.length === 0)
76
+ return refuse('SPAN_INVALID_QUOTE', 'quote');
77
+ if (Buffer.from(quote, 'utf8').toString('utf8') !== quote)
78
+ return refuse('SPAN_INVALID_QUOTE', 'quote');
79
+ return null;
80
+ }
81
+ /** Narrows `spanShapeFault`'s result. A refusal carries `ok: false`; members never do. */
82
+ function isMembers(value) {
83
+ return value.ok !== false;
84
+ }
85
+ export function spanShapeFault(request) {
86
+ const raw = request;
87
+ if (typeof raw !== 'object' || raw === null)
88
+ return refuse('SPAN_INVALID_RANGE', 'request');
89
+ // ⚠ OWN KEYS, NOT `in`. `in` walks the prototype chain, so an inherited `offset` would be read
90
+ // as a supplied one. Symbol keys are unknown keys and refuse with the rest.
91
+ for (const key of Reflect.ownKeys(raw)) {
92
+ if (typeof key !== 'string' || !REQUEST_KEYS.has(key))
93
+ return refuse('SPAN_INVALID_RANGE', 'request');
94
+ }
95
+ const own = (key) => Object.prototype.hasOwnProperty.call(raw, key);
96
+ const hasOffset = own('offset');
97
+ const hasLength = own('length');
98
+ const hasQuote = own('quote');
99
+ // ⚠ THE SAME READ-ONCE, READ-ONLY-WHAT-WAS-SUPPLIED DISCIPLINE AS THE RESOLVER BELOW, and it
100
+ // binds here for an additional reason: this function runs BEFORE any source is read, so an
101
+ // inherited accessor fired here would run earlier than the one the resolver's rule is about.
102
+ const view = raw;
103
+ const offsetValue = hasOffset ? view.offset : undefined;
104
+ const lengthValue = hasLength ? view.length : undefined;
105
+ const quoteValue = hasQuote ? view.quote : undefined;
106
+ if (!hasOffset && !hasLength && !hasQuote)
107
+ return refuse('SPAN_INVALID_RANGE', 'request');
108
+ // ⚠ `offset` and `length` are REQUIRED TOGETHER; `where` names the member that is missing.
109
+ if (hasOffset !== hasLength)
110
+ return refuse('SPAN_INVALID_RANGE', hasOffset ? 'length' : 'offset');
111
+ if (hasOffset) {
112
+ /**
113
+ * ⚠⚠ RANGE VALIDATION RUNS FIRST AND COMPLETELY. Common byte-slice APIs coerce or clamp a
114
+ * fractional, negative or oversized bound silently, producing a quote for a DIFFERENT
115
+ * effective range while the record stores the offsets as supplied. That is silent
116
+ * mislocation arriving through type coercion. An invalid range must never reach the
117
+ * matcher, or the refusal describes the wrong failure.
118
+ *
119
+ * ⚠ THE `> bytes.length` HALF IS NOT HERE, and its absence is the boundary of this
120
+ * function. Whether a well-formed range FITS is a question about the source.
121
+ */
122
+ if (!Number.isSafeInteger(offsetValue) || offsetValue < 0) {
123
+ return refuse('SPAN_INVALID_RANGE', 'offset');
124
+ }
125
+ if (!Number.isSafeInteger(lengthValue) || lengthValue <= 0) {
126
+ return refuse('SPAN_INVALID_RANGE', 'length');
127
+ }
128
+ }
129
+ // ⚠ THE QUOTE'S OWN WELL-FORMEDNESS IS SOURCE-INDEPENDENT ON BOTH PATHS — a lone surrogate or
130
+ // an empty string refuses against any source at all — so it is decided here for both, in the
131
+ // order the resolver reaches it: after the range members on the offsets path, immediately on
132
+ // the quote-only path.
133
+ if (hasQuote) {
134
+ const badQuote = quoteFault(quoteValue);
135
+ if (badQuote)
136
+ return badQuote;
137
+ }
138
+ return Object.freeze({ hasOffset, hasQuote, offsetValue, lengthValue, quoteValue });
139
+ }
140
+ export function resolveSpan(bytes, request) {
141
+ if (!Buffer.isBuffer(bytes)) {
142
+ throw new TypeError('resolveSpan(bytes, request): bytes must be a Buffer of source bytes');
143
+ }
144
+ /**
145
+ * ⚠⚠ THE SOURCE-INDEPENDENT PREFIX, RUN AS ONE STEP, AND IT CARRIES THE READ-ONCE RULE WITH IT.
146
+ * Every refusal it can reach is one this function reached itself before the extraction, in the
147
+ * identical order — which is what makes `stamp.ts`'s pre-check a prediction of this resolver
148
+ * rather than a second opinion about the same request. It also performs the ONLY read of the
149
+ * request's members on this path and hands the values back: reading them again here would fire
150
+ * a caller-supplied accessor twice, which is `M1`'s defect arriving through a refactor.
151
+ */
152
+ const shape = spanShapeFault(request);
153
+ if (!isMembers(shape))
154
+ return shape;
155
+ const { hasOffset, hasQuote, offsetValue, lengthValue, quoteValue } = shape;
156
+ if (hasOffset) {
157
+ const offset = offsetValue;
158
+ const length = lengthValue;
159
+ // ⚠ OVERFLOW-SAFE BY SUBTRACTION, NEVER `offset + length > bytes.length`. Both operands are
160
+ // safe integers, so `bytes.length - offset` is exact and small; the sum is never compared.
161
+ if (offset > bytes.length)
162
+ return refuse('SPAN_INVALID_RANGE', 'offset');
163
+ if (length > bytes.length - offset)
164
+ return refuse('SPAN_INVALID_RANGE', 'length');
165
+ const fault = alignmentFault(bytes, offset, length);
166
+ if (fault)
167
+ return refuse('SPAN_NOT_ALIGNED', fault);
168
+ const slice = bytes.subarray(offset, offset + length);
169
+ const text = slice.toString('utf8');
170
+ /**
171
+ * ⚠ ALIGNMENT IS NECESSARY AND NOT SUFFICIENT. `41 c3` is aligned at both endpoints and
172
+ * still decodes lossily, because the sequence INSIDE the span is truncated. The round-trip
173
+ * is the property alignment is a proxy for, so it is asserted directly rather than assumed
174
+ * from the endpoints. `where` is `'request'`: with a well-formed source neither endpoint is
175
+ * at fault, and naming one would be a guess.
176
+ */
177
+ if (!Buffer.from(text, 'utf8').equals(slice))
178
+ return refuse('SPAN_NOT_ALIGNED', 'request');
179
+ if (!hasQuote)
180
+ return resolved(offset, length, text);
181
+ /**
182
+ * ⚠⚠ MISMATCH PREFERS NEITHER SIDE, AND `where` IS `'request'`. Preferring the offsets
183
+ * silently rewrites the caller's quote; preferring the quote silently relocates the
184
+ * caller's offsets. Naming `'quote'` at fault IS preferring the offsets — the first of the
185
+ * two failures the rule forbids. A whole-request inconsistency is `'request'`.
186
+ *
187
+ * String comparison is exact and safe here only because both sides are known to round-trip
188
+ * losslessly: the source slice by the check above, the quote by `quoteFault`.
189
+ */
190
+ if (quoteValue !== text)
191
+ return refuse('SPAN_MISMATCH', 'request');
192
+ return resolved(offset, length, text);
193
+ }
194
+ // ⚠ THE QUOTE IS ALREADY KNOWN WELL-FORMED — `spanShapeFault` decided it, on both paths, before
195
+ // any byte was looked at. The two `quoteFault` calls that used to sit here and in the offsets
196
+ // branch are GONE rather than kept as belt-and-braces: a second call would re-run a decided
197
+ // check against a value that can no longer change, and a guard nothing can trip is a guard
198
+ // nobody is measuring.
199
+ const needle = Buffer.from(quoteValue, 'utf8');
200
+ /**
201
+ * ⚠⚠ THE SCAN ADVANCES BY ONE BYTE, NEVER BY THE NEEDLE LENGTH. Bytes `"aaa"` with quote `"aa"`
202
+ * match at offset 0 AND offset 1; a scan advancing by the needle length finds one match and
203
+ * returns it, passing a naive occurs-twice arm while resolving an ambiguous span. Two matches
204
+ * is all this needs to know, so it stops there.
205
+ */
206
+ let first = -1;
207
+ let ambiguous = false;
208
+ for (let from = 0; from <= bytes.length - needle.length;) {
209
+ const at = bytes.indexOf(needle, from);
210
+ if (at === -1)
211
+ break;
212
+ if (first === -1) {
213
+ first = at;
214
+ from = at + 1;
215
+ continue;
216
+ }
217
+ ambiguous = true;
218
+ break;
219
+ }
220
+ if (first === -1)
221
+ return refuse('SPAN_NOT_FOUND', 'quote');
222
+ if (ambiguous)
223
+ return refuse('SPAN_AMBIGUOUS', 'quote');
224
+ /**
225
+ * ⚠ ALIGNMENT IS A PROPERTY OF THE SPAN, SO IT IS CHECKED ON EVERY SHAPE THAT PRODUCES ONE.
226
+ * In well-formed UTF-8 a located match is aligned by construction, since a valid encoded quote
227
+ * never begins with a continuation byte. In a malformed source it need not be — `41 a9` with
228
+ * quote `"A"` ends immediately before a continuation byte — and applying the rule only to the
229
+ * shape that supplies offsets is the one-sided check this plan kept catching.
230
+ */
231
+ // ⚠ `where` is 'request', NOT the endpoint `alignmentFault` names. A quote-only request has no
232
+ // `offset` and no `length` member, so naming either blames something the caller never sent —
233
+ // the diagnostic would point at a value it derived itself. The offsets path above reaches the
234
+ // same conclusion for a malformed source and says so in its own words: naming one endpoint
235
+ // "would be a guess." Extending alignment to quote-only matches was right; inheriting the
236
+ // offsets path's `where` with it was not. Both round-1 code-gate lenses, both judging the CODE
237
+ // wrong and the SPEC right.
238
+ if (alignmentFault(bytes, first, needle.length))
239
+ return refuse('SPAN_NOT_ALIGNED', 'request');
240
+ return resolved(first, needle.length, quoteValue);
241
+ }
242
+ //# sourceMappingURL=span.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"span.js","sourceRoot":"","sources":["../src/span.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,297 @@
1
+ /**
2
+ * `writePage` — THE STAMP PATH, TIER A (create-only). The Scribe-side half of the spec's step list.
3
+ *
4
+ * ⚠⚠ THE ORDER IS THE SECURITY PROPERTY, NOT AN IMPLEMENTATION DETAIL. Read the numbered steps
5
+ * below as a sequence of things that must not have happened yet:
6
+ *
7
+ * · EVERY REFUSAL KNOWABLE FROM THE REQUEST'S OWN SHAPE IS DECIDED BEFORE ANY SOURCE IS READ —
8
+ * the `Arc/` screen, the count caps, and every span fault. If any of them ran after, a caller
9
+ * could send a request that was ALWAYS going to refuse — an `Arc/` target, 65 sources, a
10
+ * malformed span — with a `derived_from` naming any path on the machine, and read those
11
+ * sources' existence and hashes out of the refusal's timing and reason. That turns a check into
12
+ * an EXISTENCE ORACLE, which is D8's hole arriving through the back door after D8 closed the
13
+ * front one. ⚠ The rule was written for `Arc/` and enforced only there until 2026-09-02; the
14
+ * counts and the span shapes were being decided after the whole source loop had run.
15
+ *
16
+ * ⚠⚠ THIS SAID "EVERY GUARANTEED REFUSAL" UNTIL 2026-09-08 AND THAT IS NO LONGER TRUE, so the
17
+ * claim is narrowed to what the order actually buys. The fence's `PARENT_ALIAS` is a guaranteed
18
+ * refusal — the page's parent is an alias, and no source read can change that — but it is
19
+ * knowable only from the vault's TOPOLOGY, not from the request's shape, and it fires at page
20
+ * creation in step 6, AFTER the source loop. ⚠ The oracle argument survives the narrowing: what
21
+ * a caller learns from a `PARENT_ALIAS` is a fact about a directory INSIDE the grant, which
22
+ * every read operation already discloses. The `.wyrd/` half is not affected at all — step 4's
23
+ * `loadConfig` refuses an aliased `.wyrd` before step 5 reads anything.
24
+ * · NO PAGE IS WRITTEN until every source has resolved and every limit KNOWABLE BEFORE THE PAGE
25
+ * EXISTS has passed. When a page IS created and the ledger step then REFUSES — the final size
26
+ * check at step 9, or the append after it — this invocation leaves that page with no ledger
27
+ * line: an ORPHAN, which the no-rollback ruling below leaves on disk and which the
28
+ * create-before-append clause two bullets down calls "detectable and repairable". That is what
29
+ * the draft size check at step 6 stands in front of.
30
+ *
31
+ * ⚠ THE FIRST SENTENCE READ UNCONDITIONALLY UNTIL 2026-09-08 — "a page created before the
32
+ * ledger line is known to be serialisable is a page this invocation leaves with no ledger line"
33
+ * — WHICH IS WIDER THAN THE CODE. The ordinary path creates the page before the FINAL size
34
+ * check and then appends successfully; the orphan is what a REFUSAL at either ledger step
35
+ * leaves, not what the ordering leaves. The condition is now in the sentence.
36
+ *
37
+ * ⚠ AND IT SAID "a page whose provenance can NEVER be recorded" BEFORE THAT, WHICH THE CODE
38
+ * ALSO DID NOT ESTABLISH. What this invocation leaves behind is a page with no ledger line.
39
+ * **`writePage` provides no pass that records one later, and re-invoking it on the same path
40
+ * refuses `EXISTS`** — so nothing in this package records one, which is narrower than the
41
+ * retired "a later pass can still record one" and is what the code actually says. Overclaiming
42
+ * in either direction is not a harmless flourish: the first makes the residual hole at step 9
43
+ * (`ST41`) sound impossible instead of merely narrow, the second promises a repair path this
44
+ * package does not contain.
45
+ *
46
+ * ⚠⚠ "EVERY LIMIT" IS NOT WHAT THIS ORDERING BUYS AND THE CLAUSE SAID SO UNTIL 2026-09-08. One
47
+ * limit is decided AFTER the page exists: the final ledger-line size check at step 9, over the
48
+ * CANONICAL path the fence returns from the create. It is reachable — a fold-equal parent alias
49
+ * of different byte width makes `created.rel` wider than the request the draft was sized
50
+ * against (`ST41`) — and when it fires the page is already on disk with no ledger line. That
51
+ * ORPHAN is the outcome, asserted PRESENT on purpose by `ST38` and `ST41` so a green suite is
52
+ * never read as meaning it was closed. What the ordering buys is the narrower claim, which is
53
+ * still the one worth having: no page exists until every check that CAN be decided without one
54
+ * has passed.
55
+ *
56
+ * ⚠⚠ THIS SAID "NOTHING IS WRITTEN" UNTIL 2026-09-08 AND THAT IS NO LONGER TRUE EITHER, so the
57
+ * subject is narrowed to the thing the ordering actually protects. Option C's part 3 hoists
58
+ * `loadConfig`'s exclusive create ahead of its read, so STEP 4 CAN CREATE `.wyrd/scribe.json`
59
+ * BEFORE ANY SOURCE IS READ. The distinction is worth the words: the config is this server's
60
+ * own bookkeeping, minted at a fixed path inside a directory the user created for it, and a
61
+ * vault that gains one has gained none of the caller's PAGE CONTENT (the `vault_id` it carries
62
+ * is whatever `newUuid` returned, and a caller supplying that option chose it). The PAGE is
63
+ * the first write of caller-supplied content, and it is still the write every check above
64
+ * stands in front of.
65
+ * Sources are read BETWEEN the two.
66
+ * · the page is created BEFORE the ledger line is appended, and that ORDER IS ALSO DELIBERATE.
67
+ * The reverse records provenance for a page that may never exist, which is a FALSE PROVENANCE
68
+ * CLAIM — D4 names that as the failure that matters, worse than breakage, because it looks
69
+ * healthy. A page with a missing ledger line is DETECTABLE — the page is on disk and the ledger
70
+ * has no line naming it, which a reader can see — and repairable BY SOMETHING OUTSIDE THIS
71
+ * PACKAGE; a ledger line with no page is a lie in the permanent record, and nothing detects it.
72
+ * ⚠ "repairable" ALONE READ AS A PROMISE THIS PACKAGE KEEPS, WHICH IT DOES NOT: `writePage` has
73
+ * no repair pass and refuses `EXISTS` on the orphan's own path. What the ordering buys is that
74
+ * the failure is the visible kind rather than the invisible kind.
75
+ *
76
+ * ⚠⚠ AND THERE IS NO ROLLBACK, WHICH IS A RULING RATHER THAN AN OMISSION. If the append fails after
77
+ * the page was created, the page STAYS and the refusal says so (`PAGE_WRITTEN_LEDGER_FAILED`,
78
+ * carrying the fence's `Created`). Two reasons, and the second is the load-bearing one: the fence
79
+ * has no delete primitive at all in tier A, and deleting BY PATH would reopen the TOCTOU window the
80
+ * fence's `wx` create exists to close — between the create and the delete, the name can become
81
+ * something else. A caller told exactly what happened can decide; a caller handed a silent cleanup
82
+ * that deleted the wrong object cannot.
83
+ */
84
+ import type { Created, FenceRefusal, FsGate } from 'wyrd-fence';
85
+ import type { LineageRecord } from './lineage.js';
86
+ import type { LedgerAppender } from './ledger.js';
87
+ import type { ScribeRefusal } from './refusal.js';
88
+ import { SourceCache } from './source.js';
89
+ import type { SpanRefusal, SpanRequest } from './span.js';
90
+ /** A `derived_from` entry: one source path and the spans cited from it. */
91
+ export interface DerivedFrom {
92
+ readonly source: string;
93
+ readonly spans: readonly SpanRequest[];
94
+ }
95
+ export interface WritePageRequest {
96
+ readonly path: string;
97
+ readonly content: string;
98
+ readonly derivedFrom: readonly DerivedFrom[];
99
+ }
100
+ /**
101
+ * ⚠⚠ THE OUTER SIDE-EFFECT SUMMARY, AND IT IS A DIFFERENT QUESTION FROM `retained`.
102
+ *
103
+ * `retained` is the FENCE's field and it is honest exactly as scoped: **it describes the ONE fence
104
+ * call that produced the refusal it sits on** — whether that call may have left bytes at the target
105
+ * IT was given. It says nothing about the invocation around it, and reading it as an
106
+ * invocation-wide "nothing happened" is the mistake this field exists to ANSWER — the answer is
107
+ * available beside it now, which is a different thing from the misreading being unavailable; a
108
+ * caller can still read `retained` alone and reach the old wrong conclusion. Since
109
+ * part 3 hoisted `loadConfig`'s exclusive create ahead of its read, a `writePage` that refuses at
110
+ * step 4 or later may ALREADY have minted the vault's config — a real, persistent change to the
111
+ * user's vault — while every `retained` in sight is `null` because the target that fence call was
112
+ * given was never opened.
113
+ *
114
+ * ⚠ WHICH TARGET IT DESCRIBES IS GIVEN BY WHERE THE REFUSAL AROSE, AND THERE IS NO SHORTER RULE
115
+ * THAN THAT. `retained`, where present, describes the fence call that produced the refusal it sits
116
+ * on — which target that call was given is answered by reading where in `writePage` the refusal
117
+ * came from, not by the refusal's shape. A pass-through from step 4 describes the CONFIG target
118
+ * (`ST39`, `stamp.test.js` ~2280); one from step 8 describes the page; a source-read refusal may
119
+ * carry no `retained` at all. On `LedgerFailed` the field lives at `cause.retained` and describes
120
+ * whatever the failing call addressed — and where the cause is Scribe-originated, as `ST41`'s
121
+ * `LINEAGE_LINE_TOO_LARGE` is, there is no `cause.retained` to read. The page's own outcome is
122
+ * never in that field either way: it is the `created` beside it, which says the page exists.
123
+ *
124
+ * **`retained` describes the fence call that produced the refusal it sits on; `config_created` says
125
+ * whether this invocation minted the vault config.** A caller repairing after a refusal needs both.
126
+ *
127
+ * ⚠ This field was added before the package's first public contract, so no earlier consumer shape
128
+ * required compatibility.
129
+ * It is on the ok shape AND on every POST-CONFIG Scribe-originated refusal shape for the same
130
+ * reason: a field present only on success answers the question only when the answer is least
131
+ * interesting. (A refusal decided at steps 1-3 carries no field; there is no answer yet.)
132
+ *
133
+ * ⚠⚠ AND THE RULE IS EXACTLY ONE SENTENCE WIDE, so read it before adding a return site. **Every
134
+ * result `writePage` returns after `loadConfig` has run carries `config_created`, set from what
135
+ * THIS invocation's `loadConfig` did — except a fence refusal passed through unchanged under D8,
136
+ * which carries no field.** A refusal decided at steps 1-3, before `loadConfig` runs, carries no
137
+ * field either, for the plainer reason that there is no answer yet.
138
+ *
139
+ * ⚠ ON A FENCE PASS-THROUGH THE CALLER IS SIMPLY NOT TOLD, and the residual is stated rather than
140
+ * argued away: a fence refusal from step 6's create, or from the source loop, CAN follow a config
141
+ * mint at step 4 and will not say so. **The absence of the field is not evidence the config was
142
+ * untouched.** On a fence pass-through the caller is not told whether this invocation minted the
143
+ * config, and **this package offers no observation that attributes it** — a later stat says the
144
+ * file is there, not who put it there, and nothing here records the answer anywhere else. ⚠ THAT
145
+ * CLAUSE READ "no later observation attributes it" UNTIL 2026-09-08, which speaks for observations
146
+ * outside this package that it knows nothing about. Closing the gap properly means a discriminated
147
+ * envelope around the pass-throughs, which is a wider change than this round is scoped to make.
148
+ */
149
+ export interface ConfigCreated {
150
+ /** True when THIS invocation minted `.wyrd/scribe.json`. */
151
+ readonly config_created: boolean;
152
+ }
153
+ /**
154
+ * ⚠ THE FAILED-LEDGER OUTCOME CARRIES THE `Created`, AND IT IS A REQUIRED FIELD. The caller's next
155
+ * action depends entirely on knowing the page IS on disk and where — telling them only that
156
+ * something failed leaves them unable to distinguish "nothing happened" from "a page exists with no
157
+ * provenance", which are opposite situations demanding opposite repairs.
158
+ */
159
+ export interface LedgerFailed extends ScribeRefusal, ConfigCreated {
160
+ readonly reason: 'PAGE_WRITTEN_LEDGER_FAILED';
161
+ readonly created: Created;
162
+ /**
163
+ * The fence or ledger refusal that actually fired, passed through rather than summarised.
164
+ *
165
+ * ⚠⚠ `ScribeRefusal` IS IN THIS UNION BECAUSE ITS ABSENCE WAS THE DEFECT. Every cause reaching
166
+ * here used to be typed `FenceRefusal`, whose `reason` is the fence's closed `FenceReason`
167
+ * union — so the one site that fires on a Scribe-side fault, the post-create line-size
168
+ * re-check, had NOTHING HONEST IT COULD TYPECHECK and returned a fabricated `IO_ERROR`. The
169
+ * caller was told a filesystem error lost their provenance when the truth was a request over a
170
+ * documented bound. **A too-narrow cause type does not prevent the wrong cause; it compels
171
+ * one.** Widening the union is what lets the site name `LINEAGE_LINE_TOO_LARGE`.
172
+ */
173
+ readonly cause: FenceRefusal | ScribeRefusal;
174
+ }
175
+ export interface Stamped extends ConfigCreated {
176
+ readonly ok: true;
177
+ readonly created: Created;
178
+ readonly record: LineageRecord;
179
+ readonly appended: number;
180
+ }
181
+ /**
182
+ * A Scribe-originated refusal from the stamp path, carrying the outer summary.
183
+ *
184
+ * ⚠⚠ EVERY SCRIBE-ORIGINATED SHAPE RETURNED AFTER STEP 4 CARRIES THE FIELD, AND THE ONE EXCLUSION
185
+ * IS THE FENCE PASS-THROUGH. The fence's refusals are returned unwrapped under D8 — a second
186
+ * vocabulary for one containment implementation is what D8 exists to prevent — so they carry no
187
+ * `config_created` and a caller reading one is NOT told whether this invocation minted the config.
188
+ * That is the whole of the exclusion, and it is stated on the type at `WritePageResult` below.
189
+ *
190
+ * ⚠⚠ THE SPAN REFUSALS WERE IN THAT EXCLUSION UNTIL 2026-09-08 AND ARE NOT ANY MORE. The retired
191
+ * argument was that `span.ts`'s envelope holds exactly `ok`, `reason` and `where`, that `ST20`
192
+ * asserts that own-key set exactly, and that widening it here would be this module rewriting
193
+ * another module's contract on its way past. What that reasoning missed is WHICH module's contract
194
+ * the returned value belongs to: a `SPAN_*` refusal reaching a `writePage` caller is `writePage`'s
195
+ * result, and the own-key envelope exists to stop UNDOCUMENTED keys reaching that caller rather
196
+ * than to fix a cardinality. `config_created` is documented, on the type, in the same words on
197
+ * every shape that carries it — so the envelope grows by exactly one documented key and `ST20`'s
198
+ * assertion is widened to say so. `span.ts`'s own arms are untouched: the module still returns
199
+ * three keys, and step 5 is what adds the fourth on its way out.
200
+ *
201
+ * ⚠ THE SHAPE-ONLY SPAN FAULTS AT STEP 3 STILL CARRY NOTHING, and that is the ordering rather than
202
+ * an exception: they are decided BEFORE `loadConfig` runs, so there is no answer to report.
203
+ */
204
+ export interface StampRefusal extends ScribeRefusal, ConfigCreated {
205
+ }
206
+ /**
207
+ * A `SPAN_*` refusal from step 5, carrying the outer summary. See `StampRefusal` for why the span
208
+ * envelope grew by one key on 2026-09-08, and `span.ts` for why the module's own returns did not.
209
+ */
210
+ export interface StampSpanRefusal extends SpanRefusal, ConfigCreated {
211
+ }
212
+ export interface WritePageOptions {
213
+ readonly gate: FsGate;
214
+ readonly appender: LedgerAppender;
215
+ readonly version: string;
216
+ /** Injected so an arm can pin `recorded_at` and the minted vault id. Production uses the defaults. */
217
+ readonly now?: () => Date;
218
+ readonly newUuid?: () => string;
219
+ /**
220
+ * ⚠ A SEPARATE SEAM FROM `newUuid`, WHICH MINTS THE VAULT ID. They are both v4 UUIDs and both
221
+ * default to `randomUUID`, and sharing one injection point would be convenient and wrong: an
222
+ * arm pinning the vault id to a fixed value would pin every `event_id` to the SAME value, which
223
+ * is precisely the collision `event_id` exists to make impossible. A suite that cannot pin one
224
+ * without flattening the other cannot measure either.
225
+ */
226
+ readonly newEventId?: () => string;
227
+ readonly cache?: SourceCache;
228
+ }
229
+ /**
230
+ * ⚠⚠ THE ONE RULE THIS UNION EXISTS TO STATE, AND IT IS A RULE ABOUT `config_created`:
231
+ *
232
+ * **Every result `writePage` returns after `loadConfig` has run carries `config_created`, set
233
+ * from what THIS invocation's `loadConfig` did — EXCEPT a fence refusal passed through unchanged
234
+ * under D8, which carries no field at all.**
235
+ *
236
+ * So the union is written to make that sentence checkable rather than merely asserted. `ScribeRefusal`
237
+ * and `SpanRefusal` appear as `PreConfigRefusal`, a shape reachable ONLY from steps 1-3 — before
238
+ * `loadConfig` runs, where there is nothing to report — and every Scribe-originated shape from step
239
+ * 4 down carries the field. `FenceRefusal` is the documented exclusion: it is the ONE POST-CONFIG
240
+ * member that does not carry the field. `PreConfigRefusal` lacks it too, which is the ordering
241
+ * rather than an exclusion — it is decided before there is an answer.
242
+ *
243
+ * ⚠⚠ THE BARE `ScribeRefusal` MEMBER WAS THE DEFECT, NOT THE FIX. Until 2026-09-08 this union
244
+ * listed `ScribeRefusal` and `SpanRefusal` outright, which made the type say "a Scribe refusal may
245
+ * or may not carry the summary" — so a return site that dropped the field TYPECHECKED, and three
246
+ * did. A type that admits both answers cannot catch the wrong one. If a later change makes
247
+ * TypeScript reject a return site here, that site is an instance of the class, not a reason to
248
+ * widen the union back.
249
+ *
250
+ * ⚠⚠ AND THE BRAND IS WHAT MAKES IT BIND, WHICH LISTING THE SHAPES SEPARATELY DID NOT. Measured
251
+ * twice during this round, because the first attempt looked right and was not:
252
+ *
253
+ * · With `PreConfigRefusal` declared as a bare `ScribeRefusal | SpanRefusal`, a post-config site
254
+ * returning the refusal unwrapped still COMPILED. The two shapes are structurally identical,
255
+ * so TypeScript matched the return against the pre-config member and had nothing to say. A
256
+ * union whose members are structurally interchangeable cannot encode WHERE a value came from.
257
+ * · With the brand declared OPTIONAL (`?: never`) it still compiled, for the plainer reason that
258
+ * an optional property is satisfied by its absence. An optional brand brands nothing.
259
+ *
260
+ * So the brand is REQUIRED. `PRE_CONFIG` is a `unique symbol` declared and never defined, so the
261
+ * property exists only in the type system — no runtime value carries it, nothing can read it, and
262
+ * it costs no bytes. What it buys is that an unbranded bare refusal returned from step 4 down
263
+ * matches NO member of this union and is REJECTED, which is the whole point of writing the rule as
264
+ * a type instead of as a sentence.
265
+ */
266
+ declare const PRE_CONFIG: unique symbol;
267
+ /**
268
+ * A refusal decided at steps 1-3, before `loadConfig` has run. It carries no `config_created` for
269
+ * the plain reason that there is no answer yet, and the brand is what keeps it from standing in for
270
+ * the post-config shapes — see the note above for the two measurements that forced it.
271
+ */
272
+ export type PreConfigRefusal = (ScribeRefusal | SpanRefusal) & {
273
+ readonly [PRE_CONFIG]: never;
274
+ };
275
+ export type WritePageResult = Stamped | StampRefusal | StampSpanRefusal | PreConfigRefusal | LedgerFailed | FenceRefusal;
276
+ /**
277
+ * ⚠⚠ THE OUTER HALF IS THE **ONLY** SCOPE IN WHICH THIS MODULE HOLDS THE CALLER'S REQUEST OBJECT,
278
+ * and that is the containment rather than a tidy split. (The caller's own option hooks may close
279
+ * over that object and run downstream; they are the caller's, and outside what the split
280
+ * contains.) Steps 4-9 live in `stampValidated` below, which takes a
281
+ * `Validated` snapshot and has NO `request` parameter — so a downstream read of the caller's object
282
+ * is not a discipline anyone has to remember, it is `TS2304: Cannot find name 'request'`. The same
283
+ * argument the `preConfigRefuse` local makes about building a pre-config refusal: a name out of
284
+ * scope is a compile error.
285
+ *
286
+ * ⚠⚠ THE DISCRIMINATION IS `instanceof`, NOT `ok === false`, AND THE OLD TEST WAS A LIVE DEFECT.
287
+ * `isRefusalLike(checked)` read `.ok` through the prototype chain, so a request whose PROTOTYPE
288
+ * carried `{ ok: false }` came back from `preflight` validated, was read HERE as a refusal, and was
289
+ * returned to the caller as one — a refusal the caller minted, wearing this module's name. Asking
290
+ * `checked instanceof Validated` puts the answer on a prototype this module owns and never exports,
291
+ * which a caller cannot supply through inheritance or any other means. `isRefusalLike` stays where
292
+ * it belongs, guarding the fence and Scribe results below — values returned by the `gate` and the
293
+ * `appender`, which are injected dependencies. Reading `.ok` off those is trust in the dependency
294
+ * the caller chose to inject, not a defence against the request; it was never meant as one.
295
+ */
296
+ export declare function writePage(request: WritePageRequest, options: WritePageOptions): Promise<WritePageResult>;
297
+ export {};