@ternent/ledger 0.1.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/SPEC.md ADDED
@@ -0,0 +1,304 @@
1
+ # `@ternent/ledger` v2 Specification
2
+
3
+ ## 1. Purpose
4
+
5
+ `@ternent/ledger` is the append-only truth engine beneath Concord.
6
+
7
+ It owns:
8
+
9
+ - staged and committed truth
10
+ - entry authoring
11
+ - signed commit authoring
12
+ - deterministic replay
13
+ - verification of commit linkage, commit proofs, entry proofs, and payload hashes
14
+ - persistence of committed truth plus staged truth
15
+
16
+ It does not own:
17
+
18
+ - app-domain command APIs
19
+ - framework runtime behavior
20
+ - storage authority
21
+ - mutable CRUD semantics
22
+
23
+ ## 2. Architectural thesis
24
+
25
+ The corrected Ledger model is:
26
+
27
+ - entries are units of meaning
28
+ - commits are the primary authored integrity boundary
29
+ - commits are signed
30
+ - each commit references the previous commit id
31
+ - chain integrity is carried by signed commits plus parent linkage
32
+ - staged and committed truth remain explicit
33
+
34
+ Entry-level authenticity is not sufficient to define the integrity of the full ledger history.
35
+ Entries carry meaning. Signed commits carry authored history integrity.
36
+
37
+ ## 3. Layering
38
+
39
+ - `@ternent/concord-protocol` owns deterministic canonicalization and commit-chain traversal helpers
40
+ - `@ternent/ledger` owns staged/committed truth, signed commits, replay, and verification
41
+ - `@ternent/concord` owns command ergonomics and plugin projection above Ledger
42
+
43
+ ## 4. Truth model
44
+
45
+ ### 4.1 Container
46
+
47
+ ```ts
48
+ type LedgerContainer = {
49
+ format: "concord-ledger";
50
+ version: "1";
51
+ commits: Record<string, LedgerCommitRecord>;
52
+ entries: Record<string, LedgerEntryRecord>;
53
+ head: string;
54
+ };
55
+ ```
56
+
57
+ The container exposes committed truth only.
58
+ Staged truth is held separately.
59
+
60
+ ### 4.2 Entry record
61
+
62
+ ```ts
63
+ type LedgerEntryRecord = {
64
+ entryId: string;
65
+ kind: string;
66
+ authoredAt: string;
67
+ author: string;
68
+ meta: Record<string, unknown> | null;
69
+ payload: LedgerPayloadRecord;
70
+ seal: SealProof;
71
+ };
72
+ ```
73
+
74
+ Entries are the smallest unit of meaning.
75
+ They may carry plaintext or encrypted payloads.
76
+ They may be individually sealed.
77
+
78
+ ### 4.3 Commit record
79
+
80
+ ```ts
81
+ type LedgerCommitRecord = {
82
+ commitId: string;
83
+ parentCommitId: string | null;
84
+ committedAt: string;
85
+ metadata: Record<string, unknown> | null;
86
+ entryIds: string[];
87
+ seal: SealProof;
88
+ };
89
+ ```
90
+
91
+ Commits are first-class authored records.
92
+ They are signed records, not unsigned grouping metadata.
93
+
94
+ ## 5. Signed commit model
95
+
96
+ ### 5.1 Unsigned commit subject
97
+
98
+ Ledger must centralize construction of the unsigned commit subject.
99
+
100
+ The unsigned commit subject includes:
101
+
102
+ - `parentCommitId`
103
+ - `committedAt`
104
+ - `metadata`
105
+ - ordered `entryIds`
106
+
107
+ It excludes:
108
+
109
+ - `commitId`
110
+ - `seal`
111
+ - any circular self-reference
112
+
113
+ ### 5.2 Commit id derivation and signing
114
+
115
+ Commit creation must follow this order:
116
+
117
+ 1. build the canonical unsigned commit subject
118
+ 2. derive `commitId` from that unsigned canonical subject
119
+ 3. sign the same unsigned canonical subject bytes
120
+ 4. attach the resulting proof as `seal`
121
+
122
+ Commit proof bytes must never participate in commit id derivation.
123
+
124
+ ## 6. Append and commit behavior
125
+
126
+ ### 6.1 Append
127
+
128
+ ```ts
129
+ append(input: LedgerAppendInput): Promise<LedgerAppendResult>;
130
+ appendMany(inputs: LedgerAppendInput[]): Promise<LedgerAppendResult[]>;
131
+ ```
132
+
133
+ Append stages fully formed `LedgerEntryRecord` values.
134
+
135
+ Append does not mutate committed history.
136
+
137
+ ### 6.2 Commit
138
+
139
+ ```ts
140
+ type LedgerCommitInput = {
141
+ metadata?: Record<string, unknown>;
142
+ };
143
+
144
+ type LedgerCommitResult = {
145
+ commit: LedgerCommitRecord;
146
+ committedEntries: LedgerEntryRecord[];
147
+ committedEntryIds: string[];
148
+ };
149
+ ```
150
+
151
+ Rules:
152
+
153
+ - `commit()` fails when no staged entries exist
154
+ - staged entries are grouped in deterministic staged order
155
+ - the new commit signs the unsigned commit subject
156
+ - the committed chain advances only after successful signed commit creation
157
+ - staged entries clear only after commit success
158
+
159
+ ## 7. Replay
160
+
161
+ ```ts
162
+ replay(options?: LedgerReplayOptions): Promise<P>;
163
+ ```
164
+
165
+ Replay order is:
166
+
167
+ 1. committed commits in chain order
168
+ 2. committed entries in commit order
169
+ 3. staged entries last for the in-memory working view
170
+
171
+ Replay produces derived projection only.
172
+ Projection is never persisted as truth.
173
+
174
+ ## 8. Verification
175
+
176
+ ```ts
177
+ type LedgerVerificationResult = {
178
+ valid: boolean;
179
+ committedHistoryValid: boolean;
180
+ commitChainValid: boolean;
181
+ commitProofsValid: boolean;
182
+ entriesValid: boolean;
183
+ entryProofsValid: boolean;
184
+ payloadHashesValid: boolean;
185
+ proofsValid: boolean;
186
+ invalidCommitIds: string[];
187
+ invalidEntryIds: string[];
188
+ };
189
+ ```
190
+
191
+ Verification must validate:
192
+
193
+ - committed container structure
194
+ - commit-chain linkage
195
+ - commit id derivation from unsigned commit subjects
196
+ - commit proof validity against unsigned commit subject bytes
197
+ - entry id derivation
198
+ - entry proof validity
199
+ - encrypted payload hashes when present
200
+
201
+ `commitChainValid` remains a structural diagnostic about commit linkage and commit id derivation.
202
+ `committedHistoryValid` is the strict integrity boundary for the reachable committed document.
203
+
204
+ `committedHistoryValid` is true only if:
205
+
206
+ - reachable commit linkage is valid
207
+ - reachable commit proofs are valid
208
+ - all entries referenced by reachable commits are present and valid
209
+ - all encrypted payload hashes for reachable committed entries are valid
210
+
211
+ `valid` must equal `committedHistoryValid`.
212
+
213
+ That means any invalid committed byte in reachable history makes the entire document globally invalid, even though verification still reports a precise local breakdown through the other fields.
214
+
215
+ The combined `proofsValid` field is the conjunction of commit-proof and entry-proof validity.
216
+
217
+ ## 9. Public API
218
+
219
+ ```ts
220
+ type LedgerInstance<P> = {
221
+ create(params?: CreateLedgerParams): Promise<void>;
222
+ load(container: LedgerContainer): Promise<void>;
223
+ loadFromStorage(): Promise<boolean>;
224
+
225
+ append(input: LedgerAppendInput): Promise<LedgerAppendResult>;
226
+ appendMany(inputs: LedgerAppendInput[]): Promise<LedgerAppendResult[]>;
227
+ commit(input?: LedgerCommitInput): Promise<LedgerCommitResult>;
228
+
229
+ replay(options?: LedgerReplayOptions): Promise<P>;
230
+ recompute(): Promise<P>;
231
+ verify(options?: LedgerVerifyOptions): Promise<LedgerVerificationResult>;
232
+
233
+ export(): Promise<LedgerContainer>;
234
+ import(container: LedgerContainer): Promise<void>;
235
+
236
+ getState(): Readonly<LedgerState<P>>;
237
+ subscribe(listener: (state: Readonly<LedgerState<P>>) => void): () => void;
238
+ clearStaged(): Promise<void>;
239
+ destroy(): Promise<void>;
240
+ };
241
+ ```
242
+
243
+ ## 10. State and persistence
244
+
245
+ ```ts
246
+ type LedgerState<P> = {
247
+ container: LedgerContainer | null;
248
+ staged: LedgerEntryRecord[];
249
+ projection: P;
250
+ verification: LedgerVerificationSnapshot | null;
251
+ };
252
+ ```
253
+
254
+ Storage persists:
255
+
256
+ - committed container
257
+ - staged entries
258
+
259
+ Storage does not persist:
260
+
261
+ - projection
262
+ - signer or decryptor capability context
263
+ - verification caches as authority
264
+
265
+ ## 11. Example lifecycle
266
+
267
+ ```ts
268
+ await ledger.create({
269
+ metadata: {
270
+ surface: "workspace",
271
+ },
272
+ });
273
+
274
+ await ledger.append({
275
+ kind: "todo.item.created",
276
+ payload: { id: "todo_123", title: "Buy milk" },
277
+ });
278
+
279
+ await ledger.append({
280
+ kind: "todo.item.renamed",
281
+ payload: { id: "todo_123", title: "Buy oat milk" },
282
+ });
283
+
284
+ await ledger.commit({
285
+ metadata: {
286
+ message: "Create and refine first todo",
287
+ },
288
+ });
289
+
290
+ const projection = await ledger.replay();
291
+ const verification = await ledger.verify();
292
+ const exported = await ledger.export();
293
+ ```
294
+
295
+ ## 12. Acceptance criteria
296
+
297
+ Ledger is correct when:
298
+
299
+ - commit ids derive from unsigned commit subjects only
300
+ - commit proofs validate against those same unsigned commit subject bytes
301
+ - tampering with parent linkage or commit content fails verification
302
+ - staged entries remain uncommitted until explicit commit
303
+ - replay order remains committed history first, staged truth last
304
+ - exported ledgers show signed commit records as the authored chain boundary
@@ -0,0 +1,3 @@
1
+ export * from "./ledger.js";
2
+ export * from "./types.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}