@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 +304 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +963 -0
- package/dist/index.js.map +1 -0
- package/dist/ledger.d.ts +4 -0
- package/dist/ledger.d.ts.map +1 -0
- package/dist/seal-cli.d.ts +27 -0
- package/dist/types.d.ts +262 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +41 -0
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
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
|