@bjornpagen/bumbledb-log 0.20.0 → 0.20.2
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/braids.d.ts +28 -0
- package/dist/braids.d.ts.map +1 -0
- package/{src/braids.ts → dist/braids.js} +22 -29
- package/dist/braids.js.map +1 -0
- package/dist/bytes.d.ts +26 -0
- package/dist/bytes.d.ts.map +1 -0
- package/dist/bytes.js +86 -0
- package/dist/bytes.js.map +1 -0
- package/dist/chain.d.ts +56 -0
- package/dist/chain.d.ts.map +1 -0
- package/dist/chain.js +137 -0
- package/dist/chain.js.map +1 -0
- package/dist/codec.d.ts +71 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +179 -0
- package/dist/codec.js.map +1 -0
- package/dist/descriptor.d.ts +61 -0
- package/dist/descriptor.d.ts.map +1 -0
- package/dist/descriptor.js +160 -0
- package/dist/descriptor.js.map +1 -0
- package/dist/errors.d.ts +202 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +122 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/keys.d.ts +52 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +166 -0
- package/dist/keys.js.map +1 -0
- package/dist/manifest.d.ts +42 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +116 -0
- package/dist/manifest.js.map +1 -0
- package/dist/replica.d.ts +154 -0
- package/dist/replica.d.ts.map +1 -0
- package/dist/replica.js +820 -0
- package/dist/replica.js.map +1 -0
- package/dist/store-s3.d.ts +31 -0
- package/dist/store-s3.d.ts.map +1 -0
- package/dist/store-s3.js +299 -0
- package/dist/store-s3.js.map +1 -0
- package/dist/store.d.ts +129 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +577 -0
- package/dist/store.js.map +1 -0
- package/dist/tenants.d.ts +56 -0
- package/dist/tenants.d.ts.map +1 -0
- package/dist/tenants.js +333 -0
- package/dist/tenants.js.map +1 -0
- package/dist/tilde-family.json +21 -0
- package/dist/vector.d.ts +31 -0
- package/dist/vector.d.ts.map +1 -0
- package/dist/vector.js +87 -0
- package/dist/vector.js.map +1 -0
- package/dist/writer.d.ts +106 -0
- package/dist/writer.d.ts.map +1 -0
- package/dist/writer.js +677 -0
- package/dist/writer.js.map +1 -0
- package/package.json +12 -7
- package/src/bytes.ts +0 -115
- package/src/chain.ts +0 -181
- package/src/codec.ts +0 -252
- package/src/descriptor.ts +0 -240
- package/src/errors.ts +0 -309
- package/src/index.ts +0 -57
- package/src/keys.ts +0 -218
- package/src/manifest.ts +0 -161
- package/src/replica.ts +0 -1061
- package/src/store-s3.ts +0 -377
- package/src/store.ts +0 -727
- package/src/tenants.ts +0 -414
- package/src/vector.ts +0 -103
- package/src/writer.ts +0 -977
package/src/keys.ts
DELETED
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The key layout: generation numbers zero-padded lowercase hex, 16
|
|
3
|
-
* chars; a prefix is a store; a tenant is a prefix under `t/`. A
|
|
4
|
-
* StoreKey is parsed here, once — the verbs take the proof and never
|
|
5
|
-
* re-check. An empty prefix joins as the rest alone, so a leading
|
|
6
|
-
* slash is unrepresentable. Temps and leases live under a reserved
|
|
7
|
-
* tilde-family first segment no StoreKey can spell — the generated
|
|
8
|
-
* table under `conformance/v3/keys/` is the set, read at module init
|
|
9
|
-
* — so those namespaces stay disjoint from every honest key. Format
|
|
10
|
-
* characters, line and paragraph separators, and space separators
|
|
11
|
-
* cannot hide a reserved prefix or a `.lock` suffix: a segment
|
|
12
|
-
* containing one is refused outright.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import * as fs from "node:fs"
|
|
16
|
-
import { internalLogParseCkptScratch, internalLogRenderCkptScratch } from "@bjornpagen/bumbledb"
|
|
17
|
-
import * as errors from "@superbuilders/errors"
|
|
18
|
-
import { regex } from "arkregex"
|
|
19
|
-
import type { Digest32 } from "#bytes.ts"
|
|
20
|
-
import { digest32, hex32, U64_MAX } from "#bytes.ts"
|
|
21
|
-
import type { Braid } from "#descriptor.ts"
|
|
22
|
-
|
|
23
|
-
/** A segment wearing this suffix is not a key. */
|
|
24
|
-
const LOCK_SUFFIX = ".lock"
|
|
25
|
-
|
|
26
|
-
/** Reserved first-segment names no StoreKey can spell. Temps and leases live here. */
|
|
27
|
-
const TEMP_NAMESPACE = "~tmp"
|
|
28
|
-
const LEASE_NAMESPACE = "~lease"
|
|
29
|
-
|
|
30
|
-
/** The one tilde table both drivers consume: ASCII `~`, its lookalikes, and the NFKC preimage of U+007E. */
|
|
31
|
-
const TILDE_TABLE_URL = new URL("../../crates/bumbledb-log/conformance/v3/keys/tilde-family.json", import.meta.url)
|
|
32
|
-
|
|
33
|
-
const CODE_POINT_SPELLING = regex("^U\\+[0-9A-F]{4,6}$")
|
|
34
|
-
|
|
35
|
-
function loadTildeFamily(): Set<string> {
|
|
36
|
-
const raw = fs.readFileSync(TILDE_TABLE_URL, "utf8")
|
|
37
|
-
const table: unknown = JSON.parse(raw)
|
|
38
|
-
if (typeof table !== "object" || table === null || !("codePoints" in table)) {
|
|
39
|
-
throw errors.new(`tilde table is not a codePoints document: ${TILDE_TABLE_URL.pathname}`)
|
|
40
|
-
}
|
|
41
|
-
const { codePoints } = table
|
|
42
|
-
if (!Array.isArray(codePoints) || codePoints.length === 0) {
|
|
43
|
-
throw errors.new(`tilde table codePoints is not a nonempty array: ${TILDE_TABLE_URL.pathname}`)
|
|
44
|
-
}
|
|
45
|
-
const family = new Set<string>()
|
|
46
|
-
for (const spelling of codePoints) {
|
|
47
|
-
if (typeof spelling !== "string" || !CODE_POINT_SPELLING.test(spelling)) {
|
|
48
|
-
throw errors.new(`tilde table entry is not a U+XXXX code point: ${String(spelling)}`)
|
|
49
|
-
}
|
|
50
|
-
family.add(String.fromCodePoint(Number.parseInt(spelling.slice(2), 16)))
|
|
51
|
-
}
|
|
52
|
-
if (!family.has("~")) {
|
|
53
|
-
throw errors.new(`tilde table does not reserve ASCII tilde: ${TILDE_TABLE_URL.pathname}`)
|
|
54
|
-
}
|
|
55
|
-
return family
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** First-segment code points that spell a reserved name. The table is the set; no local spelling. */
|
|
59
|
-
const TILDE_FAMILY = loadTildeFamily()
|
|
60
|
-
|
|
61
|
-
const FORMAT_OR_SEPARATOR = regex("[\\p{Cc}\\p{Cf}\\p{Zl}\\p{Zp}\\p{Zs}]", "u")
|
|
62
|
-
const SLASH_TRIM = regex("^/+|/+$", "g")
|
|
63
|
-
|
|
64
|
-
declare const storeKeyBrand: unique symbol
|
|
65
|
-
type StoreKey = string & { readonly [storeKeyBrand]: typeof storeKeyBrand }
|
|
66
|
-
|
|
67
|
-
declare const generationBrand: unique symbol
|
|
68
|
-
type Generation = bigint & { readonly [generationBrand]: typeof generationBrand }
|
|
69
|
-
|
|
70
|
-
function firstCodePoint(seg: string): string | undefined {
|
|
71
|
-
for (const ch of seg) {
|
|
72
|
-
return ch
|
|
73
|
-
}
|
|
74
|
-
return undefined
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function tildeFamilyPrefix(seg: string): boolean {
|
|
78
|
-
const first = firstCodePoint(seg)
|
|
79
|
-
if (first === undefined) {
|
|
80
|
-
return false
|
|
81
|
-
}
|
|
82
|
-
return TILDE_FAMILY.has(first)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** One path segment of a key or a tenant id: the same grammar. */
|
|
86
|
-
function segmentOk(seg: string): boolean {
|
|
87
|
-
if (seg.length === 0 || seg.includes("/") || seg === "." || seg === "..") {
|
|
88
|
-
return false
|
|
89
|
-
}
|
|
90
|
-
if (FORMAT_OR_SEPARATOR.test(seg)) {
|
|
91
|
-
return false
|
|
92
|
-
}
|
|
93
|
-
return !tildeFamilyPrefix(seg) && !seg.endsWith(LOCK_SUFFIX)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function storeKey(raw: string): StoreKey {
|
|
97
|
-
const wellFormed = raw.length > 0 && !raw.startsWith("/") && !raw.endsWith("/") && raw.split("/").every(segmentOk)
|
|
98
|
-
if (!wellFormed) {
|
|
99
|
-
throw errors.new(`store key is not a slash path: ${raw}`)
|
|
100
|
-
}
|
|
101
|
-
return raw as StoreKey
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** Empty, or a StoreKey spelling (the same segment grammar, no leading or trailing slash). */
|
|
105
|
-
function parsePrefix(raw: string): string {
|
|
106
|
-
if (raw.length === 0) {
|
|
107
|
-
return ""
|
|
108
|
-
}
|
|
109
|
-
return storeKey(raw.replace(SLASH_TRIM, ""))
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function reservedTemp(pid: number, seq: number): string {
|
|
113
|
-
return `${TEMP_NAMESPACE}/${String(pid)}.${String(seq)}`
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function reservedLease(key: string, token: bigint): string {
|
|
117
|
-
return `${LEASE_NAMESPACE}/${key}/${String(token)}`
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** A slash path whose first segment is `~tmp` or `~lease`. Not a StoreKey. */
|
|
121
|
-
function reservedName(raw: string): string {
|
|
122
|
-
const segs = raw.split("/")
|
|
123
|
-
if (segs.length === 0 || segs.some((seg) => seg.length === 0 || seg === "." || seg === "..")) {
|
|
124
|
-
throw errors.new(`reserved name is not a slash path: ${raw}`)
|
|
125
|
-
}
|
|
126
|
-
const first = segs[0]
|
|
127
|
-
if (first !== TEMP_NAMESPACE && first !== LEASE_NAMESPACE) {
|
|
128
|
-
throw errors.new(`reserved name is not under ${TEMP_NAMESPACE} or ${LEASE_NAMESPACE}: ${raw}`)
|
|
129
|
-
}
|
|
130
|
-
return raw
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/** Known scratch-lease document under `~lease`. One path, no LIST. */
|
|
134
|
-
const CKPT_SCRATCH_LEASE = "ckpt-scratch"
|
|
135
|
-
|
|
136
|
-
/** The reserved scratch name: `~lease/ckpt-scratch`. */
|
|
137
|
-
function scratchCkptName(): string {
|
|
138
|
-
return reservedName(`${LEASE_NAMESPACE}/${CKPT_SCRATCH_LEASE}`)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/** The scratch-lease body, spelled by the one grammar (`crates/bumbledb-log`). */
|
|
142
|
-
function encodeCkptScratch(digest: Digest32): Uint8Array {
|
|
143
|
-
return internalLogRenderCkptScratch(digest)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/** The digest a scratch-lease body names, or null — the one grammar's
|
|
147
|
-
* parse, branded; the refusal is undifferentiated by law. */
|
|
148
|
-
function parseCkptScratch(bytes: Uint8Array): Digest32 | null {
|
|
149
|
-
const named = internalLogParseCkptScratch(bytes)
|
|
150
|
-
if (named === null) {
|
|
151
|
-
return null
|
|
152
|
-
}
|
|
153
|
-
return digest32(named)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function generation(raw: bigint): Generation {
|
|
157
|
-
if (raw < 0n || raw > U64_MAX) {
|
|
158
|
-
throw errors.new(`generation is a u64: ${raw}`)
|
|
159
|
-
}
|
|
160
|
-
return raw as Generation
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function hex16(g: Generation): string {
|
|
164
|
-
return g.toString(16).padStart(16, "0")
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function assemble(prefix: string, rest: string): StoreKey {
|
|
168
|
-
return storeKey(prefix.length === 0 ? rest : `${prefix}/${rest}`)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function manifestKey(prefix: string): StoreKey {
|
|
172
|
-
return assemble(prefix, "manifest")
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function logKey(prefix: string, braid: Braid, g: Generation): StoreKey {
|
|
176
|
-
return assemble(prefix, `log/${braid}/${hex16(g)}`)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function checkpointMdbKey(prefix: string, digest: Digest32): StoreKey {
|
|
180
|
-
return assemble(prefix, `ckpt/${hex32(digest)}.mdb`)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function ckptDocKey(prefix: string, digest: Digest32): StoreKey {
|
|
184
|
-
return assemble(prefix, `ckpt/${hex32(digest)}`)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function idsKey(prefix: string, relation: number, field: number): StoreKey {
|
|
188
|
-
return assemble(prefix, `ids/${relation.toString(16).padStart(8, "0")}/${field.toString(16).padStart(4, "0")}`)
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function tenantPrefix(root: string, tenant: string): string {
|
|
192
|
-
if (!segmentOk(tenant)) {
|
|
193
|
-
throw errors.new(`tenant id is not a single path segment: ${tenant}`)
|
|
194
|
-
}
|
|
195
|
-
return assemble(root, `t/${tenant}`)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export type { Generation, StoreKey }
|
|
199
|
-
export {
|
|
200
|
-
CKPT_SCRATCH_LEASE,
|
|
201
|
-
checkpointMdbKey,
|
|
202
|
-
ckptDocKey,
|
|
203
|
-
encodeCkptScratch,
|
|
204
|
-
generation,
|
|
205
|
-
idsKey,
|
|
206
|
-
LEASE_NAMESPACE,
|
|
207
|
-
logKey,
|
|
208
|
-
manifestKey,
|
|
209
|
-
parseCkptScratch,
|
|
210
|
-
parsePrefix,
|
|
211
|
-
reservedLease,
|
|
212
|
-
reservedName,
|
|
213
|
-
reservedTemp,
|
|
214
|
-
scratchCkptName,
|
|
215
|
-
storeKey,
|
|
216
|
-
TEMP_NAMESPACE,
|
|
217
|
-
tenantPrefix
|
|
218
|
-
}
|
package/src/manifest.ts
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The protocol's one mutable object and its checkpoint, as typed
|
|
3
|
-
* payloads. The byte grammar has one implementation —
|
|
4
|
-
* `crates/bumbledb-log` behind the napi bridge — so parse and render
|
|
5
|
-
* here are marshal walks: branded `Digest32` and `Braid` values in,
|
|
6
|
-
* bridge-tagged payloads across, never a second reader of the bytes.
|
|
7
|
-
* The vector algebra over checkpoint heads and the seed transition's
|
|
8
|
-
* catalog audit are machine logic and stay here.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type { LogCheckpointDoc, LogCheckpointKind, LogCodecHandle, LogManifestKind } from "@bjornpagen/bumbledb"
|
|
12
|
-
import {
|
|
13
|
-
internalLogParseCheckpoint,
|
|
14
|
-
internalLogParseManifest,
|
|
15
|
-
internalLogRenderCheckpoint,
|
|
16
|
-
internalLogRenderManifest
|
|
17
|
-
} from "@bjornpagen/bumbledb"
|
|
18
|
-
import type { Digest32 } from "#bytes.ts"
|
|
19
|
-
import { bytesEqual, digest32, hex32 } from "#bytes.ts"
|
|
20
|
-
import type { Braid } from "#descriptor.ts"
|
|
21
|
-
import { braidHex } from "#descriptor.ts"
|
|
22
|
-
import { refuse } from "#errors.ts"
|
|
23
|
-
import type { Generation } from "#keys.ts"
|
|
24
|
-
import { generation } from "#keys.ts"
|
|
25
|
-
import { Vector } from "#vector.ts"
|
|
26
|
-
|
|
27
|
-
interface Manifest {
|
|
28
|
-
readonly fingerprint: Digest32
|
|
29
|
-
readonly checkpoint: Digest32 | null
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Remints a bridge refusal row as the driver's typed refusal. The
|
|
34
|
-
* boundary carries `{ kind, message }` only: the kind is the log
|
|
35
|
-
* core's own identity string, so the cause payload holds the data this
|
|
36
|
-
* side owns — the document's length, its version byte (byte 0 of every
|
|
37
|
-
* v:3 document) — and nothing invented. The raw braid id of an
|
|
38
|
-
* `UnknownBraid` rides the message; the drifted set of a `BraidSet`
|
|
39
|
-
* rides the message the same way.
|
|
40
|
-
*/
|
|
41
|
-
function refuseBridged(kind: LogManifestKind | LogCheckpointKind, message: string, bytes: Uint8Array): never {
|
|
42
|
-
switch (kind) {
|
|
43
|
-
case "Version":
|
|
44
|
-
return refuse({ kind: "Version", version: bytes[0] ?? 0 }, message)
|
|
45
|
-
case "Overflow":
|
|
46
|
-
return refuse({ kind: "Overflow" }, message)
|
|
47
|
-
case "UnknownBraid":
|
|
48
|
-
return refuse({ kind: "UnknownBraid" }, message)
|
|
49
|
-
case "BraidSet":
|
|
50
|
-
return refuse({ kind: "BraidSet" }, message)
|
|
51
|
-
case "Malformed":
|
|
52
|
-
return refuse({ kind: "Malformed", at: bytes.length }, message)
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function braidIdOf(id: Braid): number {
|
|
57
|
-
return Number.parseInt(id.slice(1), 16)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function renderManifest(manifest: Manifest): Uint8Array {
|
|
61
|
-
return internalLogRenderManifest(
|
|
62
|
-
manifest.checkpoint === null
|
|
63
|
-
? { fingerprint: manifest.fingerprint }
|
|
64
|
-
: { fingerprint: manifest.fingerprint, checkpoint: manifest.checkpoint }
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function parseManifest(bytes: Uint8Array): Manifest {
|
|
69
|
-
const parsed = internalLogParseManifest(bytes)
|
|
70
|
-
if (!parsed.ok) {
|
|
71
|
-
refuseBridged(parsed.kind, parsed.message, bytes)
|
|
72
|
-
}
|
|
73
|
-
return {
|
|
74
|
-
fingerprint: digest32(parsed.value.fingerprint),
|
|
75
|
-
checkpoint: parsed.value.checkpoint === undefined ? null : digest32(parsed.value.checkpoint)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
interface CheckpointHead {
|
|
80
|
-
readonly g: Generation
|
|
81
|
-
readonly hash: Digest32
|
|
82
|
-
readonly ts: bigint
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
interface CheckpointFacts {
|
|
86
|
-
readonly braids: ReadonlyMap<Braid, CheckpointHead>
|
|
87
|
-
readonly catalog: Digest32
|
|
88
|
-
readonly writer: bigint
|
|
89
|
-
readonly prev: Digest32 | null
|
|
90
|
-
readonly sum: bigint
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function vectorOfHeads(braids: ReadonlyMap<Braid, CheckpointHead>): Vector {
|
|
94
|
-
const counts = new Map<Braid, bigint>()
|
|
95
|
-
for (const [id, head] of braids) {
|
|
96
|
-
counts.set(id, head.g)
|
|
97
|
-
}
|
|
98
|
-
return Vector.from(counts)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function checkpointVector(facts: CheckpointFacts): Vector {
|
|
102
|
-
return vectorOfHeads(facts.braids)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function renderCheckpoint(codec: LogCodecHandle, facts: CheckpointFacts): Uint8Array {
|
|
106
|
-
const braids = [...facts.braids.entries()]
|
|
107
|
-
.sort(function ascending(a, b) {
|
|
108
|
-
return braidIdOf(a[0]) - braidIdOf(b[0])
|
|
109
|
-
})
|
|
110
|
-
.map(function headOf([id, head]) {
|
|
111
|
-
return { braid: braidIdOf(id), g: head.g, hash: head.hash, ts: head.ts }
|
|
112
|
-
})
|
|
113
|
-
const doc: LogCheckpointDoc =
|
|
114
|
-
facts.prev === null
|
|
115
|
-
? { braids, catalog: facts.catalog, writer: facts.writer }
|
|
116
|
-
: { braids, catalog: facts.catalog, writer: facts.writer, prev: facts.prev }
|
|
117
|
-
return internalLogRenderCheckpoint(codec, doc)
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function parseCheckpoint(codec: LogCodecHandle, bytes: Uint8Array): CheckpointFacts {
|
|
121
|
-
const parsed = internalLogParseCheckpoint(codec, bytes)
|
|
122
|
-
if (!parsed.ok) {
|
|
123
|
-
refuseBridged(parsed.kind, parsed.message, bytes)
|
|
124
|
-
}
|
|
125
|
-
const braids = new Map<Braid, CheckpointHead>()
|
|
126
|
-
for (const head of parsed.value.braids) {
|
|
127
|
-
braids.set(braidHex(head.braid), { g: generation(head.g), hash: digest32(head.hash), ts: head.ts })
|
|
128
|
-
}
|
|
129
|
-
/** The core refuses `Overflow` at parse; the narrowing here is the
|
|
130
|
-
* derived sum's own type gate over the same heads. */
|
|
131
|
-
const summed = vectorOfHeads(braids).sum()
|
|
132
|
-
if (typeof summed !== "bigint") {
|
|
133
|
-
refuse({ kind: "Overflow" }, "checkpoint vector sum overflows u64")
|
|
134
|
-
}
|
|
135
|
-
return {
|
|
136
|
-
braids,
|
|
137
|
-
catalog: digest32(parsed.value.catalog),
|
|
138
|
-
writer: parsed.value.writer,
|
|
139
|
-
prev: parsed.value.prev === undefined ? null : digest32(parsed.value.prev),
|
|
140
|
-
sum: summed
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/** The seed transition's catalog claim: a present checkpoint
|
|
145
|
-
* compares the opened store's `catalog_digest` against the document.
|
|
146
|
-
* Genesis (no checkpoint) has no claim. A mismatch names the publisher. */
|
|
147
|
-
function auditCatalog(facts: CheckpointFacts | null, computed: Digest32): void {
|
|
148
|
-
if (facts === null) {
|
|
149
|
-
return
|
|
150
|
-
}
|
|
151
|
-
if (bytesEqual(facts.catalog, computed)) {
|
|
152
|
-
return
|
|
153
|
-
}
|
|
154
|
-
refuse(
|
|
155
|
-
{ kind: "CheckpointDigest", expected: hex32(facts.catalog), computed: hex32(computed) },
|
|
156
|
-
`checkpoint catalog ${hex32(facts.catalog)} disagrees with the opened store ${hex32(computed)}; publisher ${facts.writer}`
|
|
157
|
-
)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export type { CheckpointFacts, CheckpointHead }
|
|
161
|
-
export { auditCatalog, checkpointVector, parseCheckpoint, parseManifest, renderCheckpoint, renderManifest }
|