@book.dev/sdk 3.5.0 → 3.6.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/backup.d.ts +75 -1
- package/dist/backup.js +8 -1
- package/dist/backup.js.map +1 -1
- package/dist/client.d.ts +134 -2
- package/dist/client.js +171 -0
- package/dist/client.js.map +1 -1
- package/dist/csv.d.ts +49 -0
- package/dist/csv.js +116 -0
- package/dist/csv.js.map +1 -0
- package/dist/importAssets.d.ts +10 -0
- package/dist/importAssets.js +17 -0
- package/dist/importAssets.js.map +1 -1
- package/dist/index.d.ts +10 -4
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/ledger.d.ts +772 -0
- package/dist/ledger.js +493 -0
- package/dist/ledger.js.map +1 -0
- package/dist/ledgerBeancount.d.ts +117 -0
- package/dist/ledgerBeancount.js +309 -0
- package/dist/ledgerBeancount.js.map +1 -0
- package/dist/ledgerBeancountFixture.d.ts +61 -0
- package/dist/ledgerBeancountFixture.js +459 -0
- package/dist/ledgerBeancountFixture.js.map +1 -0
- package/dist/ledgerCsv.d.ts +76 -0
- package/dist/ledgerCsv.js +197 -0
- package/dist/ledgerCsv.js.map +1 -0
- package/dist/money.d.ts +179 -0
- package/dist/money.js +283 -0
- package/dist/money.js.map +1 -0
- package/dist/notionImport.d.ts +5 -3
- package/dist/notionImport.js +6 -56
- package/dist/notionImport.js.map +1 -1
- package/dist/plugins.d.ts +24 -0
- package/dist/plugins.js +25 -0
- package/dist/plugins.js.map +1 -1
- package/dist/provenance.d.ts +17 -0
- package/dist/provenance.js.map +1 -1
- package/dist/routes.d.ts +121 -0
- package/dist/routes.js +122 -0
- package/dist/routes.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/dist/ledger.d.ts
ADDED
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ledger contract (LGR-3): the shared types, typed errors, and pure audit-replay
|
|
3
|
+
* reducer for the server-enforced double-entry ledger.
|
|
4
|
+
*
|
|
5
|
+
* Data model: ledger data lives in FOUR server-managed OpenBook databases
|
|
6
|
+
* (accounts / transactions / postings / reconciliations) seeded on a restricted
|
|
7
|
+
* host page. Rows are ordinary pages; every ledger value is a database property.
|
|
8
|
+
* The server's `LedgerStore` is the ONLY writer — the generic page/row mutation
|
|
9
|
+
* surface (HTTP routes AND direct store calls) rejects writes to ledger rows, so
|
|
10
|
+
* the invariants hold in both server mode and browser-local (PGlite) mode.
|
|
11
|
+
*
|
|
12
|
+
* Money: amounts are SIGNED INTEGER MINOR UNITS (LGR-2, see `./money`). Amounts
|
|
13
|
+
* on the wire are integers — never parsed strings.
|
|
14
|
+
*/
|
|
15
|
+
/** Machine-readable ledger error categories. */
|
|
16
|
+
export type LedgerErrorCode = 'not-initialized' | 'not-found' | 'managed' | 'immutable' | 'unbalanced' | 'too-few-postings' | 'invalid-amount' | 'account-not-found' | 'account-closed' | 'currency-mismatch' | 'invalid-state' | 'nonzero-balance' | 'reconciled-locked' | 'reconciliation-exists' | 'reconciliation-unbalanced' | 'posting-not-reconcilable' | 'reconciliation-too-large' | 'period-closed' | 'period-overlap' | 'period-out-of-order' | 'period-close-conflict' | 'evidence-required' | 'invalid-input';
|
|
17
|
+
/**
|
|
18
|
+
* HTTP status the API maps each {@link LedgerErrorCode} to.
|
|
19
|
+
*
|
|
20
|
+
* The three LGR-11 reconciliation rejections are 409, not 400: they are all
|
|
21
|
+
* "not valid for the CURRENT state of the book" rather than malformed requests.
|
|
22
|
+
* The identical call becomes legal once the open reconciliation is finished,
|
|
23
|
+
* once the difference reaches zero, or once the posting is posted — which is
|
|
24
|
+
* what tells a client to re-read and retry instead of fixing its payload.
|
|
25
|
+
*/
|
|
26
|
+
export declare function ledgerErrorStatus(code: LedgerErrorCode): 400 | 403 | 404 | 409;
|
|
27
|
+
/**
|
|
28
|
+
* Typed ledger error. The server REJECTS invariant violations with these (never
|
|
29
|
+
* advises); the HTTP layer maps them to `{error, code}` bodies via
|
|
30
|
+
* {@link ledgerErrorStatus} and `HttpDataClient` re-materializes them, so a
|
|
31
|
+
* caller catches the same error class over either transport.
|
|
32
|
+
*/
|
|
33
|
+
export declare class LedgerError extends Error {
|
|
34
|
+
readonly code: LedgerErrorCode;
|
|
35
|
+
constructor(code: LedgerErrorCode, message: string);
|
|
36
|
+
}
|
|
37
|
+
/** The set of valid codes, for wire-body → {@link LedgerError} re-materialization. */
|
|
38
|
+
export declare const LEDGER_ERROR_CODES: readonly LedgerErrorCode[];
|
|
39
|
+
export declare const LEDGER_ACCOUNT_TYPES: readonly ["asset", "liability", "equity", "revenue", "expense"];
|
|
40
|
+
export type LedgerAccountType = (typeof LEDGER_ACCOUNT_TYPES)[number];
|
|
41
|
+
export declare const LEDGER_ACCOUNT_STATUSES: readonly ["open", "closed"];
|
|
42
|
+
export type LedgerAccountStatus = (typeof LEDGER_ACCOUNT_STATUSES)[number];
|
|
43
|
+
export declare const LEDGER_TRANSACTION_STATES: readonly ["draft", "posted", "void"];
|
|
44
|
+
export type LedgerTransactionState = (typeof LEDGER_TRANSACTION_STATES)[number];
|
|
45
|
+
export declare const LEDGER_CLEARED_STATES: readonly ["pending", "cleared", "reconciled"];
|
|
46
|
+
export type LedgerClearedState = (typeof LEDGER_CLEARED_STATES)[number];
|
|
47
|
+
/**
|
|
48
|
+
* A reconciliation's lifecycle (LGR-11 + LGR-22).
|
|
49
|
+
*
|
|
50
|
+
* `abandoned` is a TERMINAL status, not a deletion: a statement someone opened
|
|
51
|
+
* against the wrong closing balance still happened, and the record of it — with
|
|
52
|
+
* the audit events that amended and ended it — is what lets a later reader tell
|
|
53
|
+
* "this account was never reconciled" apart from "this attempt was given up on".
|
|
54
|
+
* It is invisible to the one-open-per-account rule (which asks only for `open`),
|
|
55
|
+
* so abandoning immediately frees the account for a fresh start.
|
|
56
|
+
*/
|
|
57
|
+
export declare const LEDGER_RECONCILIATION_STATUSES: readonly ["open", "finished", "abandoned"];
|
|
58
|
+
export type LedgerReconciliationStatus = (typeof LEDGER_RECONCILIATION_STATUSES)[number];
|
|
59
|
+
/**
|
|
60
|
+
* A period's lifecycle (LGR-12). `reopened` is TERMINAL for the RECORD, not for
|
|
61
|
+
* the range: reopening keeps the row (with the reversal that voided its closing
|
|
62
|
+
* entry) so the settings UI and the audit trail can show that a close happened
|
|
63
|
+
* and was undone; closing the range again writes a NEW period record. Only
|
|
64
|
+
* `closed` periods lock dates.
|
|
65
|
+
*/
|
|
66
|
+
export declare const LEDGER_PERIOD_STATUSES: readonly ["closed", "reopened"];
|
|
67
|
+
export type LedgerPeriodStatus = (typeof LEDGER_PERIOD_STATUSES)[number];
|
|
68
|
+
/**
|
|
69
|
+
* The account types a period close sweeps into retained earnings (LGR-12): the
|
|
70
|
+
* income-statement ("flow") accounts. ONE list, exported, because the server's
|
|
71
|
+
* closing-entry generator and any report fold that classifies flow accounts
|
|
72
|
+
* must agree — this epic's recurring defect is two copies of one fact.
|
|
73
|
+
*/
|
|
74
|
+
export declare const LEDGER_INCOME_STATEMENT_ACCOUNT_TYPES: readonly ["revenue", "expense"];
|
|
75
|
+
/** Whether `type` names an income-statement (flow) account. */
|
|
76
|
+
export declare function isIncomeStatementAccountType(type: unknown): type is 'revenue' | 'expense';
|
|
77
|
+
/**
|
|
78
|
+
* The server's HARD CAP on one `listTransactions` page: a larger `limit` is
|
|
79
|
+
* clamped to this, silently.
|
|
80
|
+
*
|
|
81
|
+
* Exported because a reader that totals what it fetched — a report — cannot
|
|
82
|
+
* tell a full page from a complete book without it, and a duplicated literal
|
|
83
|
+
* would rot: if this cap ever dropped, a report asking for the old number would
|
|
84
|
+
* be clamped, conclude it had read everything, and render a PARTIAL total as a
|
|
85
|
+
* complete one. Both the server clamp and the ledger plugin's report read this
|
|
86
|
+
* one constant, and `ledger.test.ts` asserts the clamp actually holds.
|
|
87
|
+
*/
|
|
88
|
+
export declare const LEDGER_MAX_TRANSACTION_LIMIT = 1000;
|
|
89
|
+
/** The page size `listTransactions` uses when the caller names none. */
|
|
90
|
+
export declare const LEDGER_DEFAULT_TRANSACTION_LIMIT = 500;
|
|
91
|
+
/** Stable property ids for the seeded ledger database schemas. */
|
|
92
|
+
export declare const LEDGER_PROP: {
|
|
93
|
+
readonly account: {
|
|
94
|
+
readonly type: "lp_type";
|
|
95
|
+
readonly status: "lp_status";
|
|
96
|
+
readonly currency: "lp_currency";
|
|
97
|
+
/** LGR-14: `true` on an account that refuses to accept a posting from an
|
|
98
|
+
* entry with no evidence attached; absent everywhere else (additive — no
|
|
99
|
+
* migration, pre-LGR-14 accounts read back `false`). */
|
|
100
|
+
readonly evidenceRequired: "lp_evidence_required";
|
|
101
|
+
};
|
|
102
|
+
readonly transaction: {
|
|
103
|
+
readonly date: "lp_date";
|
|
104
|
+
readonly description: "lp_description";
|
|
105
|
+
readonly state: "lp_state";
|
|
106
|
+
readonly postedAt: "lp_posted_at";
|
|
107
|
+
readonly postedBy: "lp_posted_by";
|
|
108
|
+
readonly reverses: "lp_reverses";
|
|
109
|
+
readonly evidence: "lp_evidence";
|
|
110
|
+
readonly entryNo: "lp_entry_no";
|
|
111
|
+
/** LGR-12: `'closing'` on a period-close entry; absent on ordinary entries. */
|
|
112
|
+
readonly kind: "lp_kind";
|
|
113
|
+
};
|
|
114
|
+
readonly posting: {
|
|
115
|
+
readonly transaction: "lp_transaction";
|
|
116
|
+
readonly account: "lp_account";
|
|
117
|
+
readonly amount: "lp_amount_minor";
|
|
118
|
+
readonly cleared: "lp_cleared";
|
|
119
|
+
readonly reconciliation: "lp_reconciliation";
|
|
120
|
+
readonly memo: "lp_memo";
|
|
121
|
+
};
|
|
122
|
+
readonly reconciliation: {
|
|
123
|
+
readonly account: "lp_account";
|
|
124
|
+
readonly statementDate: "lp_statement_date";
|
|
125
|
+
readonly statementBalance: "lp_statement_balance_minor";
|
|
126
|
+
readonly status: "lp_status";
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* One piece of evidence attached to a transaction (LGR-14): the post-time
|
|
131
|
+
* manifest entry. `sha256` IS the asset-store id (assets are content-addressed
|
|
132
|
+
* — the id is the SHA-256 hex of the bytes), so "which file" and "which bytes"
|
|
133
|
+
* are one fact: replacing the stored bytes without changing this hash is
|
|
134
|
+
* impossible through any API, and detecting a direct-SQL replacement is one
|
|
135
|
+
* re-hash (the verifier's evidence check). `filename` is the display name the
|
|
136
|
+
* uploader gave it; `size` is the asset store's byte count, resolved
|
|
137
|
+
* server-side at attach time — never client-supplied.
|
|
138
|
+
*
|
|
139
|
+
* On a POSTED transaction the manifest is frozen with the rest of the entry
|
|
140
|
+
* (it is inside `transactionContent`, so inside the audit before/after hashes).
|
|
141
|
+
*/
|
|
142
|
+
export interface LedgerEvidence {
|
|
143
|
+
filename: string;
|
|
144
|
+
sha256: string;
|
|
145
|
+
size: number;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* One evidence attachment as a CLIENT names it (LGR-14): the content-hash id
|
|
149
|
+
* of an already-uploaded asset plus a display filename. No `size` — the server
|
|
150
|
+
* resolves it from the asset store (measuring the bytes, not trusting a cached
|
|
151
|
+
* column), so a manifest can never claim a byte count the store does not hold.
|
|
152
|
+
*
|
|
153
|
+
* CONFIDENTIALITY (accepted, stated): an asset inherits the read gate of EVERY
|
|
154
|
+
* page that references it. Attaching refs the asset to the ledger's own
|
|
155
|
+
* transaction row, but the UPLOAD already ref'd it to the page it was uploaded
|
|
156
|
+
* from — so a receipt uploaded from a widely-shared page stays readable to
|
|
157
|
+
* that page's audience for as long as that page references it. That is the
|
|
158
|
+
* platform's standing asset semantics; this feature puts receipts into them.
|
|
159
|
+
* Upload sensitive receipts from pages whose audience is the ledger's.
|
|
160
|
+
*/
|
|
161
|
+
export interface LedgerEvidenceInput {
|
|
162
|
+
/** The asset-store id: 64 lowercase hex chars — the SHA-256 of the bytes. */
|
|
163
|
+
sha256: string;
|
|
164
|
+
filename: string;
|
|
165
|
+
}
|
|
166
|
+
/** A ledger account. `name` is hierarchical, colon-delimited (`Assets:Bank:Checking`). */
|
|
167
|
+
export interface LedgerAccount {
|
|
168
|
+
id: string;
|
|
169
|
+
name: string;
|
|
170
|
+
type: LedgerAccountType;
|
|
171
|
+
status: LedgerAccountStatus;
|
|
172
|
+
/** ISO-4217-shaped currency code. Defaults to `USD`. */
|
|
173
|
+
currency: string;
|
|
174
|
+
/**
|
|
175
|
+
* LGR-14: when `true`, posting an entry with a leg on this account is
|
|
176
|
+
* REJECTED (`evidence-required`) unless the entry has evidence attached.
|
|
177
|
+
* `false` is what every account written before LGR-14 reads back as (the
|
|
178
|
+
* stored key is simply absent — additive, no migration). Reversals and
|
|
179
|
+
* server-generated closing entries are exempt: a reversal CARRIES its
|
|
180
|
+
* original's manifest (F1), and a closing entry is derived arithmetic.
|
|
181
|
+
*
|
|
182
|
+
* PRESENCE-ONLY, by design: the gate asserts that some file was attached at
|
|
183
|
+
* post time, not that it is the right one — the badge and the manifest read
|
|
184
|
+
* as "this entry can answer with what was filed", never as attestation that
|
|
185
|
+
* the filing is correct or even relevant. The verifier's
|
|
186
|
+
* `evidence-required-missing` advisory reports entries that do not satisfy
|
|
187
|
+
* the CURRENT policy — turning this flag on flags history, deliberately.
|
|
188
|
+
*/
|
|
189
|
+
evidenceRequired: boolean;
|
|
190
|
+
createdAt: string;
|
|
191
|
+
updatedAt: string;
|
|
192
|
+
}
|
|
193
|
+
/** One leg of a transaction. `amountMinor` is a signed integer of minor units. */
|
|
194
|
+
export interface LedgerPosting {
|
|
195
|
+
id: string;
|
|
196
|
+
transactionId: string;
|
|
197
|
+
accountId: string;
|
|
198
|
+
amountMinor: number;
|
|
199
|
+
cleared: LedgerClearedState;
|
|
200
|
+
reconciliationId: string | null;
|
|
201
|
+
/**
|
|
202
|
+
* Free-text note on THIS LEG (LGR-16) — "gross wages", the bank's raw
|
|
203
|
+
* statement line, the invoice number. Distinct from the transaction's
|
|
204
|
+
* `description`, which describes the entry as a whole; a compound entry's
|
|
205
|
+
* legs each carry their own. Length-capped exactly like `description`.
|
|
206
|
+
*
|
|
207
|
+
* `null` means no memo, and is what every posting written before LGR-16
|
|
208
|
+
* reads back as — the field is additive, so no migration is required.
|
|
209
|
+
*/
|
|
210
|
+
memo: string | null;
|
|
211
|
+
}
|
|
212
|
+
/** A journal entry with its postings. Compound (n-ary) entries are first-class. */
|
|
213
|
+
export interface LedgerTransaction {
|
|
214
|
+
id: string;
|
|
215
|
+
/** ISO date (`YYYY-MM-DD`). */
|
|
216
|
+
date: string;
|
|
217
|
+
description: string;
|
|
218
|
+
state: LedgerTransactionState;
|
|
219
|
+
/** Set exactly once at posting; never mutated afterwards. */
|
|
220
|
+
postedAt: string | null;
|
|
221
|
+
postedBy: string | null;
|
|
222
|
+
/** The transaction this one reverses, when it is a reversal. */
|
|
223
|
+
reverses: string | null;
|
|
224
|
+
/** Server-assigned monotonic entry number (per library), set at posting. */
|
|
225
|
+
entryNo: number | null;
|
|
226
|
+
/**
|
|
227
|
+
* Entry kind (LGR-12). `'closing'` marks the server-generated period-close
|
|
228
|
+
* entry — the one that sweeps income-statement balances into retained
|
|
229
|
+
* earnings. `null` on every ordinary entry AND on every entry written before
|
|
230
|
+
* LGR-12 (the field is additive; no migration). Reports use it (plus the
|
|
231
|
+
* `reverses` link, for the reversal a reopen posts) to keep closing entries
|
|
232
|
+
* out of income-statement arithmetic.
|
|
233
|
+
*/
|
|
234
|
+
kind: 'closing' | null;
|
|
235
|
+
/**
|
|
236
|
+
* The evidence manifest (LGR-14). On a DRAFT: what is attached so far, live.
|
|
237
|
+
* On a POSTED entry: the manifest snapshotted at post time — frozen with the
|
|
238
|
+
* entry (it is part of the audited content), never mutated afterwards.
|
|
239
|
+
* A REVERSAL carries its original's manifest verbatim (F1) — "a reversal's
|
|
240
|
+
* evidence is the original entry it undoes", made literal, so a reversal
|
|
241
|
+
* chain stays answerable end to end and double-reversing cannot launder the
|
|
242
|
+
* receipts off a live entry. Empty on entries written before LGR-14, on
|
|
243
|
+
* reversals of bare entries, and on server-generated closing entries.
|
|
244
|
+
*/
|
|
245
|
+
evidence: LedgerEvidence[];
|
|
246
|
+
postings: LedgerPosting[];
|
|
247
|
+
createdAt: string;
|
|
248
|
+
updatedAt: string;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* One statement reconciliation (LGR-11): an account, the statement it is being
|
|
252
|
+
* matched against, and whether that match has been FINISHED.
|
|
253
|
+
*
|
|
254
|
+
* `finished` is only reachable at a difference of exactly zero (statement
|
|
255
|
+
* balance − cleared balance = 0), and finishing FREEZES the postings it matched
|
|
256
|
+
* (`cleared: 'reconciled'` + `reconciliationId` set — invariant 4). Reopening is
|
|
257
|
+
* an explicit, audited step that unfreezes them again.
|
|
258
|
+
*
|
|
259
|
+
* While OPEN it is also correctable and abandonable (LGR-22): the target itself
|
|
260
|
+
* can be mistyped, and a wrong target is unreachable by definition — the
|
|
261
|
+
* difference can never be driven to zero, `finish` is therefore unreachable, and
|
|
262
|
+
* before LGR-22 the account could never be reconciled again. See
|
|
263
|
+
* {@link LedgerReconciliationPatch}.
|
|
264
|
+
*/
|
|
265
|
+
export interface LedgerReconciliation {
|
|
266
|
+
id: string;
|
|
267
|
+
accountId: string;
|
|
268
|
+
/** ISO date (`YYYY-MM-DD`) the statement closes on. */
|
|
269
|
+
statementDate: string;
|
|
270
|
+
/**
|
|
271
|
+
* The statement's closing balance in SIGNED INTEGER MINOR UNITS, on the
|
|
272
|
+
* ledger's debit-positive convention (LGR-2) — the same convention every
|
|
273
|
+
* posting amount uses, so the difference is one subtraction and never a
|
|
274
|
+
* re-signing. A UI that asks for it on the account's NORMAL side is
|
|
275
|
+
* responsible for converting before it gets here.
|
|
276
|
+
*/
|
|
277
|
+
statementBalanceMinor: number;
|
|
278
|
+
status: LedgerReconciliationStatus;
|
|
279
|
+
createdAt: string;
|
|
280
|
+
/** Bumped by every start/toggle-free mutation — finish and reopen included. */
|
|
281
|
+
updatedAt: string;
|
|
282
|
+
}
|
|
283
|
+
export interface LedgerReconciliationInput {
|
|
284
|
+
accountId: string;
|
|
285
|
+
/** ISO date (`YYYY-MM-DD`). */
|
|
286
|
+
statementDate: string;
|
|
287
|
+
/** Signed integer minor units, debit-positive. Never a parsed string. */
|
|
288
|
+
statementBalanceMinor: number;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* AMEND an OPEN reconciliation (LGR-22): correct the statement it is being
|
|
292
|
+
* matched against, without touching a single posting.
|
|
293
|
+
*
|
|
294
|
+
* The account is deliberately NOT amendable. Changing it would leave the ticks
|
|
295
|
+
* already made pointing at another account's postings, which is not a
|
|
296
|
+
* correction but a different reconciliation — abandon this one and start that
|
|
297
|
+
* one. Everything here is optional, but a patch that names nothing is rejected
|
|
298
|
+
* (`invalid-input`): a mutation that changes nothing must not write an audit
|
|
299
|
+
* event claiming it did.
|
|
300
|
+
*/
|
|
301
|
+
export interface LedgerReconciliationPatch {
|
|
302
|
+
/** ISO date (`YYYY-MM-DD`). */
|
|
303
|
+
statementDate?: string;
|
|
304
|
+
/** Signed integer minor units, debit-positive — the same convention as
|
|
305
|
+
* {@link LedgerReconciliationInput.statementBalanceMinor}. */
|
|
306
|
+
statementBalanceMinor?: number;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* What a reconciliation looks like once its postings are counted — the exact
|
|
310
|
+
* arithmetic a bookkeeper checks, computed server-side so the Finish gate and
|
|
311
|
+
* the on-screen readout can never disagree.
|
|
312
|
+
*/
|
|
313
|
+
export interface LedgerReconciliationSummary {
|
|
314
|
+
reconciliation: LedgerReconciliation;
|
|
315
|
+
/** Σ of every `cleared`/`reconciled` posting on the account, debit-positive. */
|
|
316
|
+
clearedBalanceMinor: number;
|
|
317
|
+
/** `statementBalanceMinor − clearedBalanceMinor`. Finish requires exactly 0. */
|
|
318
|
+
differenceMinor: number;
|
|
319
|
+
/** The postings this reconciliation matched (only meaningful once finished). */
|
|
320
|
+
matchedPostingIds: string[];
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* One accounting period close (LGR-12): an inclusive date range the store has
|
|
324
|
+
* LOCKED — no posting and no reversal may be DATED inside a `closed` period —
|
|
325
|
+
* plus the closing entry that swept the income-statement balances into
|
|
326
|
+
* retained earnings when the range was closed.
|
|
327
|
+
*
|
|
328
|
+
* Storage: the periods live in ONE `settings` row (`ledgerPeriods`), not in a
|
|
329
|
+
* fifth managed database. See `LedgerStore.closePeriod` for why (short form: a
|
|
330
|
+
* book seeded before LGR-12 can never grow a fifth database — the seed's adopt
|
|
331
|
+
* check returns early — and the row IS the lock the close flow serializes on,
|
|
332
|
+
* at the `settings` slot the proven lock order already holds).
|
|
333
|
+
*/
|
|
334
|
+
export interface LedgerPeriod {
|
|
335
|
+
id: string;
|
|
336
|
+
/** Inclusive ISO date (`YYYY-MM-DD`) the period starts on. */
|
|
337
|
+
start: string;
|
|
338
|
+
/** Inclusive ISO date (`YYYY-MM-DD`) the period ends on. */
|
|
339
|
+
end: string;
|
|
340
|
+
status: LedgerPeriodStatus;
|
|
341
|
+
/**
|
|
342
|
+
* The closing entry posted when this period was closed, or `null` when the
|
|
343
|
+
* range held no income-statement balance to close (the range still locks).
|
|
344
|
+
* Kept after a reopen (the entry is `void` then — history, not a live claim).
|
|
345
|
+
*/
|
|
346
|
+
closingEntryId: string | null;
|
|
347
|
+
/** The reversal that voided the closing entry at reopen; `null` until then. */
|
|
348
|
+
reopenEntryId: string | null;
|
|
349
|
+
closedAt: string;
|
|
350
|
+
closedBy: string;
|
|
351
|
+
reopenedAt: string | null;
|
|
352
|
+
reopenedBy: string | null;
|
|
353
|
+
}
|
|
354
|
+
/** `POST /api/ledger/periods` — close a period. */
|
|
355
|
+
export interface LedgerPeriodCloseInput {
|
|
356
|
+
/** Inclusive ISO date (`YYYY-MM-DD`). */
|
|
357
|
+
start: string;
|
|
358
|
+
/** Inclusive ISO date (`YYYY-MM-DD`); must not precede `start`. */
|
|
359
|
+
end: string;
|
|
360
|
+
/**
|
|
361
|
+
* The equity account the closing entry credits. Defaults to the account
|
|
362
|
+
* named `Equity:RetainedEarnings`; rejected `account-not-found` when neither
|
|
363
|
+
* is resolvable (create the account first — the starter chart carries it).
|
|
364
|
+
*/
|
|
365
|
+
retainedEarningsAccountId?: string;
|
|
366
|
+
}
|
|
367
|
+
/** What a period close returns: the lock, the entry, and the WARNING list. */
|
|
368
|
+
export interface LedgerPeriodCloseResult {
|
|
369
|
+
period: LedgerPeriod;
|
|
370
|
+
/** `null` when the range had no income-statement balance to close. */
|
|
371
|
+
closingEntry: LedgerTransaction | null;
|
|
372
|
+
/**
|
|
373
|
+
* Reconciliations still OPEN with a statement dated on or before the close
|
|
374
|
+
* (warn-not-block by design): the close proceeds, and this names what is
|
|
375
|
+
* unfinished so the UI can say so instead of gating.
|
|
376
|
+
*/
|
|
377
|
+
openReconciliations: LedgerReconciliation[];
|
|
378
|
+
}
|
|
379
|
+
/** What a period reopen returns: the unlocked period and the voiding reversal. */
|
|
380
|
+
export interface LedgerPeriodReopenResult {
|
|
381
|
+
period: LedgerPeriod;
|
|
382
|
+
/** `null` when the period was closed without a closing entry. */
|
|
383
|
+
reversal: LedgerTransaction | null;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* THE period predicate (LGR-12), shared by the store's post/reverse guards and
|
|
387
|
+
* every UI surface that wants to say WHY a date is refused: the `closed` period
|
|
388
|
+
* containing `date`, or `null`. Pure; string comparison is safe because both
|
|
389
|
+
* sides are ISO `YYYY-MM-DD`.
|
|
390
|
+
*/
|
|
391
|
+
export declare function closedPeriodContaining(periods: readonly LedgerPeriod[], date: string): LedgerPeriod | null;
|
|
392
|
+
/**
|
|
393
|
+
* Every CLOSED period overlapping the inclusive range `[from, to]` (`''` means
|
|
394
|
+
* open at that end) — the display-only "this report crosses a closed period"
|
|
395
|
+
* marker the report blocks render. Sorted by start date so the marker reads in
|
|
396
|
+
* calendar order.
|
|
397
|
+
*/
|
|
398
|
+
export declare function closedPeriodsOverlapping(periods: readonly LedgerPeriod[], from: string, to: string): LedgerPeriod[];
|
|
399
|
+
/** The seeded ledger database ids + host page. */
|
|
400
|
+
export interface LedgerDatabases {
|
|
401
|
+
accounts: string;
|
|
402
|
+
transactions: string;
|
|
403
|
+
postings: string;
|
|
404
|
+
reconciliations: string;
|
|
405
|
+
}
|
|
406
|
+
/** `GET /api/ledger` — whether the ledger is initialized, and where it lives. */
|
|
407
|
+
export interface LedgerInfo {
|
|
408
|
+
exists: boolean;
|
|
409
|
+
hostPageId: string | null;
|
|
410
|
+
databases: LedgerDatabases | null;
|
|
411
|
+
}
|
|
412
|
+
export interface LedgerAccountInput {
|
|
413
|
+
name: string;
|
|
414
|
+
type: LedgerAccountType;
|
|
415
|
+
/** ISO-4217-shaped code; defaults to `USD`. */
|
|
416
|
+
currency?: string;
|
|
417
|
+
/** LGR-14: refuse postings from entries with no evidence. Default `false`. */
|
|
418
|
+
evidenceRequired?: boolean;
|
|
419
|
+
}
|
|
420
|
+
export interface LedgerAccountPatch {
|
|
421
|
+
name?: string;
|
|
422
|
+
/** `closed` is rejected while the account's posted balance is nonzero. */
|
|
423
|
+
status?: LedgerAccountStatus;
|
|
424
|
+
/**
|
|
425
|
+
* LGR-14: turn the evidence requirement on or off. Forward-looking only —
|
|
426
|
+
* flipping it on does not (and cannot) retro-flag entries already posted;
|
|
427
|
+
* the gate runs at post time.
|
|
428
|
+
*/
|
|
429
|
+
evidenceRequired?: boolean;
|
|
430
|
+
}
|
|
431
|
+
export interface LedgerPostingInput {
|
|
432
|
+
accountId: string;
|
|
433
|
+
/** Signed integer minor units. Never a parsed string. */
|
|
434
|
+
amountMinor: number;
|
|
435
|
+
/** Initial cleared state; `reconciled` is not settable here. Default `pending`. */
|
|
436
|
+
cleared?: 'pending' | 'cleared';
|
|
437
|
+
/**
|
|
438
|
+
* Free-text note on this leg (LGR-16). Omitted, `undefined`, `null` and `''`
|
|
439
|
+
* all store as `null`; longer than the `description` cap is rejected with
|
|
440
|
+
* `invalid-input`.
|
|
441
|
+
*/
|
|
442
|
+
memo?: string | null;
|
|
443
|
+
}
|
|
444
|
+
export interface LedgerDraftInput {
|
|
445
|
+
/** ISO date (`YYYY-MM-DD`). */
|
|
446
|
+
date: string;
|
|
447
|
+
description?: string;
|
|
448
|
+
postings?: LedgerPostingInput[];
|
|
449
|
+
/**
|
|
450
|
+
* Evidence to attach at creation (LGR-14). Each named asset must already be
|
|
451
|
+
* in the content-addressed asset store (upload first, attach by hash);
|
|
452
|
+
* `size` is resolved server-side. Duplicate hashes are rejected — the
|
|
453
|
+
* manifest is a set.
|
|
454
|
+
*/
|
|
455
|
+
evidence?: LedgerEvidenceInput[];
|
|
456
|
+
}
|
|
457
|
+
export interface LedgerDraftPatch {
|
|
458
|
+
date?: string;
|
|
459
|
+
description?: string;
|
|
460
|
+
/** When present, REPLACES the draft's postings wholesale. */
|
|
461
|
+
postings?: LedgerPostingInput[];
|
|
462
|
+
/** When present, REPLACES the draft's evidence wholesale (LGR-14) — the
|
|
463
|
+
* same replacement contract `postings` uses. `[]` detaches everything. */
|
|
464
|
+
evidence?: LedgerEvidenceInput[];
|
|
465
|
+
}
|
|
466
|
+
export interface LedgerReverseOptions {
|
|
467
|
+
/** ISO date for the reversing entry; defaults to the original's date. */
|
|
468
|
+
date?: string;
|
|
469
|
+
description?: string;
|
|
470
|
+
}
|
|
471
|
+
export type LedgerAuditAction = 'ledger.init' | 'ledger.acl' | 'account.create' | 'account.update' | 'transaction.create' | 'transaction.update' | 'transaction.delete' | 'transaction.post' | 'transaction.reverse' | 'posting.cleared'
|
|
472
|
+
/** A statement reconciliation was opened on an account (LGR-11). */
|
|
473
|
+
| 'reconciliation.start'
|
|
474
|
+
/**
|
|
475
|
+
* A reconciliation reached a zero difference and was FINISHED (LGR-11): its
|
|
476
|
+
* matched postings froze at `reconciled`. ONE event covers the reconciliation
|
|
477
|
+
* row and every posting it froze — the `transaction.reverse` precedent, and
|
|
478
|
+
* for the same reason: they commit together or not at all.
|
|
479
|
+
*/
|
|
480
|
+
| 'reconciliation.finish'
|
|
481
|
+
/** A finished reconciliation was explicitly REOPENED, unfreezing its postings. */
|
|
482
|
+
| 'reconciliation.reopen'
|
|
483
|
+
/**
|
|
484
|
+
* An OPEN reconciliation's statement date/balance was CORRECTED (LGR-22).
|
|
485
|
+
* Touches the reconciliation row and nothing else — no posting changes ride
|
|
486
|
+
* along, which is exactly why this is not folded into `.start`: the trail has
|
|
487
|
+
* to show that the target moved, and to what.
|
|
488
|
+
*/
|
|
489
|
+
| 'reconciliation.amend'
|
|
490
|
+
/**
|
|
491
|
+
* An OPEN reconciliation was ABANDONED (LGR-22) — given up on rather than
|
|
492
|
+
* balanced. Terminal, and posting-neutral: every tick keeps the cleared state
|
|
493
|
+
* it had, because a tick records that the posting appeared on the bank, which
|
|
494
|
+
* abandoning the match does not un-observe.
|
|
495
|
+
*/
|
|
496
|
+
| 'reconciliation.abandon'
|
|
497
|
+
/**
|
|
498
|
+
* A period was CLOSED (LGR-12): the date range locked, and — when the range
|
|
499
|
+
* held income-statement balances — the closing entry posted. ONE event covers
|
|
500
|
+
* the period record and the entry it posted (the `transaction.reverse` /
|
|
501
|
+
* `reconciliation.finish` precedent: they commit together or not at all).
|
|
502
|
+
* The payload's `openReconciliationIds` names what the warn-not-block check
|
|
503
|
+
* surfaced; advisory context, not entity content.
|
|
504
|
+
*/
|
|
505
|
+
| 'period.close'
|
|
506
|
+
/**
|
|
507
|
+
* A closed period was explicitly REOPENED (LGR-12): the range unlocked and
|
|
508
|
+
* the closing entry voided via a reversal posted in the SAME event —
|
|
509
|
+
* `transaction`/`originalId` carry the reversal pair exactly as
|
|
510
|
+
* `transaction.reverse` does.
|
|
511
|
+
*/
|
|
512
|
+
| 'period.reopen'
|
|
513
|
+
/** The ledger auto-export target was set or cleared (LGR-7). Policy, not
|
|
514
|
+
* ledger content — it touches no entity, so a replay ignores it, but it is
|
|
515
|
+
* recorded here so the book itself carries evidence of where copies go. */
|
|
516
|
+
| 'ledger.autoExportPath'
|
|
517
|
+
/**
|
|
518
|
+
* A backup bundle's ledger history was RESTORED into this library (LGR-15).
|
|
519
|
+
* Appended ON TOP of the restored tail — chained from it — naming the actor
|
|
520
|
+
* and the bundle's content hash, so an installed history is always bracketed
|
|
521
|
+
* by an attributable event rather than ending exactly where the bundle ends.
|
|
522
|
+
* Touches no entity (a replay ignores it), but its recorded afterHash is
|
|
523
|
+
* re-derived from its own payload by the verifier, like `autoExportPath`.
|
|
524
|
+
*
|
|
525
|
+
* VERSION NOTE (deliberate): builds that predate this action REFUSE to read
|
|
526
|
+
* streams containing it — the unknown-action rejection in `auditFromRow` is
|
|
527
|
+
* the fail-closed posture, so a restored library is not silently
|
|
528
|
+
* mis-replayed by an old build; restore such a bundle on a current build.
|
|
529
|
+
*/
|
|
530
|
+
| 'ledger.restore';
|
|
531
|
+
/**
|
|
532
|
+
* Every known audit action — the validation set for a stored `action` value.
|
|
533
|
+
*
|
|
534
|
+
* `as const satisfies` (not a widened annotation) so the array's element type is
|
|
535
|
+
* the literal union: adding a member to {@link LedgerAuditAction} without adding
|
|
536
|
+
* it here is then caught by the exhaustiveness check below at COMPILE time,
|
|
537
|
+
* rather than surfacing at runtime as a log this build refuses to interpret.
|
|
538
|
+
*/
|
|
539
|
+
export declare const LEDGER_AUDIT_ACTIONS: readonly ["ledger.init", "ledger.acl", "account.create", "account.update", "transaction.create", "transaction.update", "transaction.delete", "transaction.post", "transaction.reverse", "posting.cleared", "reconciliation.start", "reconciliation.finish", "reconciliation.reopen", "reconciliation.amend", "reconciliation.abandon", "period.close", "period.reopen", "ledger.autoExportPath", "ledger.restore"];
|
|
540
|
+
/**
|
|
541
|
+
* One append-only audit event. `payload` carries the full after-content of the
|
|
542
|
+
* touched entity (so the stream is REPLAYABLE — see {@link replayLedgerAudit});
|
|
543
|
+
* `beforeHash`/`afterHash` are SHA-256 hex of the canonical entity content
|
|
544
|
+
* before/after the mutation (`null` where there is no before/after state).
|
|
545
|
+
*/
|
|
546
|
+
export interface LedgerAuditEvent {
|
|
547
|
+
/** Server-assigned, strictly increasing sequence (the pagination cursor). */
|
|
548
|
+
seq: number;
|
|
549
|
+
id: string;
|
|
550
|
+
actorSubject: string;
|
|
551
|
+
actorName: string;
|
|
552
|
+
action: LedgerAuditAction;
|
|
553
|
+
/** The entity ids this event touches (transaction + original for a reverse). */
|
|
554
|
+
entityIds: string[];
|
|
555
|
+
payload: Record<string, unknown>;
|
|
556
|
+
beforeHash: string | null;
|
|
557
|
+
afterHash: string | null;
|
|
558
|
+
/**
|
|
559
|
+
* TAMPER-EVIDENCE (LGR-3): the {@link ledgerAuditEventHash} of the immediately
|
|
560
|
+
* preceding event, written in the SAME transaction as this one — so the log is
|
|
561
|
+
* a hash chain, not merely an append-only table. `null` only for the genesis
|
|
562
|
+
* event. Append-only-by-construction stops the API; the chain additionally
|
|
563
|
+
* makes an UNRECOMPUTED database-level edit or middle deletion detectable, and
|
|
564
|
+
* lets a BIGSERIAL gap be told apart from a rolled-back transaction. It does
|
|
565
|
+
* NOT detect head/tail truncation or a fully recomputed rewrite — see
|
|
566
|
+
* {@link verifyLedgerAuditChain} for the precise guarantee before relying on
|
|
567
|
+
* a green result.
|
|
568
|
+
*/
|
|
569
|
+
prevHash: string | null;
|
|
570
|
+
createdAt: string;
|
|
571
|
+
}
|
|
572
|
+
/** The state a replay of the audit stream reconstructs. */
|
|
573
|
+
export interface LedgerReplayState {
|
|
574
|
+
initialized: boolean;
|
|
575
|
+
accounts: Record<string, LedgerAccount>;
|
|
576
|
+
transactions: Record<string, LedgerTransaction>;
|
|
577
|
+
/** Statement reconciliations (LGR-11), keyed by id. */
|
|
578
|
+
reconciliations: Record<string, LedgerReconciliation>;
|
|
579
|
+
/** Closed/reopened periods (LGR-12), keyed by id. */
|
|
580
|
+
periods: Record<string, LedgerPeriod>;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* One posting's cleared state as a `reconciliation.finish` / `.reopen` event
|
|
584
|
+
* records it. The event carries the FULL after-state of every posting it
|
|
585
|
+
* touched, because that is what makes the stream replayable: a reader that only
|
|
586
|
+
* knew "these ids were frozen" could not reconstruct the `reconciliationId` a
|
|
587
|
+
* reopen must clear again.
|
|
588
|
+
*/
|
|
589
|
+
export interface LedgerReconciliationPostingChange {
|
|
590
|
+
postingId: string;
|
|
591
|
+
transactionId: string;
|
|
592
|
+
cleared: LedgerClearedState;
|
|
593
|
+
reconciliationId: string | null;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Pure reducer: fold the audit event stream (ascending `seq`) into the expected
|
|
597
|
+
* current ledger state. Deterministic and side-effect free — the replay test
|
|
598
|
+
* compares this against the live store to prove the audit log is a complete
|
|
599
|
+
* record of every ledger mutation.
|
|
600
|
+
*/
|
|
601
|
+
export declare function replayLedgerAudit(events: Iterable<LedgerAuditEvent>): LedgerReplayState;
|
|
602
|
+
/**
|
|
603
|
+
* The tamper-evidence hash of ONE audit event: SHA-256 over its canonical
|
|
604
|
+
* content INCLUDING `prevHash`, which is what links each event to its
|
|
605
|
+
* predecessor. Async because it uses WebCrypto (isomorphic: Node, browser,
|
|
606
|
+
* sidecar). `seq` is deliberately part of the hashed content, so renumbering
|
|
607
|
+
* events breaks the chain just as rewriting one does.
|
|
608
|
+
*/
|
|
609
|
+
export declare function ledgerAuditEventHash(event: LedgerAuditEvent): Promise<string>;
|
|
610
|
+
/** The verdict of {@link verifyLedgerAuditChain}. */
|
|
611
|
+
export interface LedgerAuditChainResult {
|
|
612
|
+
ok: boolean;
|
|
613
|
+
/** How many events were checked. */
|
|
614
|
+
checked: number;
|
|
615
|
+
/** The `seq` of the first event whose link is broken, when `ok` is false. */
|
|
616
|
+
brokenAtSeq: number | null;
|
|
617
|
+
reason: string | null;
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Verify the audit log's hash chain over `events` (ASCENDING `seq`, contiguous
|
|
621
|
+
* from the genesis event). Each event must carry its predecessor's
|
|
622
|
+
* {@link ledgerAuditEventHash} in `prevHash`; the first must carry `null`.
|
|
623
|
+
*
|
|
624
|
+
* THE PRECISE GUARANTEE — a green result is NOT "this log is authentic":
|
|
625
|
+
*
|
|
626
|
+
* - DETECTED: any edit, reordering, or middle deletion whose perpetrator does
|
|
627
|
+
* not also recompute every following link. That covers the realistic
|
|
628
|
+
* accident-and-opportunist cases (a stray `UPDATE`, a row deleted to hide one
|
|
629
|
+
* entry), and it distinguishes a deleted event from the innocuous BIGSERIAL
|
|
630
|
+
* gap a rolled-back transaction leaves.
|
|
631
|
+
* - NOT DETECTED: truncation of the HEAD or the TAIL — a prefix or suffix of
|
|
632
|
+
* the log removed wholesale still verifies clean, because nothing in the data
|
|
633
|
+
* states where the chain is supposed to start or end. Nor a WHOLESALE REWRITE:
|
|
634
|
+
* this hash is unkeyed and this function is exported, so an actor with
|
|
635
|
+
* database access can rewrite every event and recompute every link.
|
|
636
|
+
*
|
|
637
|
+
* Detecting those requires an off-box ANCHOR — periodically publishing the tail
|
|
638
|
+
* hash somewhere the database's owner cannot rewrite — which is tracked
|
|
639
|
+
* separately (LGR-18) and is not implemented here. Treat this as integrity
|
|
640
|
+
* against tampering that does not control the whole log, not as a signature.
|
|
641
|
+
*
|
|
642
|
+
* Verifying a PAGE of the log (not starting at genesis) is supported: pass the
|
|
643
|
+
* slice and it checks the links WITHIN it, reporting the first break.
|
|
644
|
+
*/
|
|
645
|
+
export declare function verifyLedgerAuditChain(events: readonly LedgerAuditEvent[]): Promise<LedgerAuditChainResult>;
|
|
646
|
+
/**
|
|
647
|
+
* Canonical JSON of an entity for audit content-hashing: keys sorted at every
|
|
648
|
+
* depth, so a byte-identical serialization is independent of insertion order.
|
|
649
|
+
*/
|
|
650
|
+
export declare function canonicalLedgerJson(value: unknown): string;
|
|
651
|
+
/**
|
|
652
|
+
* Validate a hierarchical, colon-delimited account name (`Assets:Bank:Checking`):
|
|
653
|
+
* one or more non-empty, non-whitespace-only segments separated by single colons.
|
|
654
|
+
*/
|
|
655
|
+
export declare function isValidLedgerAccountName(name: unknown): name is string;
|
|
656
|
+
/** Validate an ISO `YYYY-MM-DD` date string (a real calendar date). */
|
|
657
|
+
export declare function isValidLedgerDate(date: unknown): date is string;
|
|
658
|
+
/** Finding categories the independent verifier can report. */
|
|
659
|
+
export type LedgerVerifyCode = 'unbalanced' | 'too-few-postings' | 'invalid-amount' | 'audit-chain-broken' | 'audit-hash-forged' | 'posted-hash-mismatch' | 'orphan-posting' | 'unknown-account' | 'replay-divergence' | 'entry-no-gap' | 'entry-no-duplicate' | 'entry-no-missing'
|
|
660
|
+
/** A `period.close`/`period.reopen` payload posting is not `pending`/unowned
|
|
661
|
+
* (LGR-12) — the writer always emits closing and reversal legs born
|
|
662
|
+
* `cleared: 'pending'`, `reconciliationId: null`, so a frozen payload saying
|
|
663
|
+
* otherwise was rewritten. This is what covers the workflow fields the
|
|
664
|
+
* period hash chain deliberately excludes. */
|
|
665
|
+
| 'closing-posting-forged'
|
|
666
|
+
/** An evidence manifest item on a posted/void entry is not `{filename,
|
|
667
|
+
* sha256, size}`-shaped (LGR-14) — the writer validates the shape at attach
|
|
668
|
+
* AND at post, so a malformed stored item was written out-of-band. The
|
|
669
|
+
* structural checks below cannot run on it, which is why it is a finding
|
|
670
|
+
* and not a skip. */
|
|
671
|
+
| 'evidence-manifest-invalid'
|
|
672
|
+
/** A posted/void entry's manifest names an asset the content-addressed store
|
|
673
|
+
* no longer holds (LGR-14). The manifest itself GC-protects its assets (the
|
|
674
|
+
* hash in the row's `properties` is what the GC's document scan keeps, and
|
|
675
|
+
* the tx-row `asset_refs` edge backs it up), and no API deletes asset rows —
|
|
676
|
+
* so a missing asset means the store was mutated underneath the ledger,
|
|
677
|
+
* never that the evidence "never existed": `post` re-resolves every item
|
|
678
|
+
* against the store inside the posting transaction. */
|
|
679
|
+
| 'evidence-asset-missing'
|
|
680
|
+
/** The stored bytes no longer hash to the manifest's SHA-256 (LGR-14) — a
|
|
681
|
+
* receipt was REPLACED in place by direct surgery on the `assets` row.
|
|
682
|
+
* Unreachable through any API (the id is derived from the bytes at upload;
|
|
683
|
+
* nothing updates `bytes`), so this re-hash is the only detector — with the
|
|
684
|
+
* row and every ledger hash untouched, no other check even looks. */
|
|
685
|
+
| 'evidence-asset-replaced'
|
|
686
|
+
/** The stored byte count differs from the manifest's `size` (LGR-14) while
|
|
687
|
+
* the hash still matches — the store's metadata was doctored (`size` is a
|
|
688
|
+
* cached column), or the manifest's size was rewritten in step with the row.
|
|
689
|
+
* Either way the manifest and the store disagree about the same bytes. */
|
|
690
|
+
| 'evidence-size-mismatch'
|
|
691
|
+
/**
|
|
692
|
+
* CURRENT-POLICY ADVISORY, not a tamper finding (LGR-14 F2): a posted/void
|
|
693
|
+
* entry has an EMPTY manifest while an account one of its legs touches
|
|
694
|
+
* CURRENTLY has `evidenceRequired`. This is what makes the toggle
|
|
695
|
+
* verifier-observable — without it, SQL-off the flag, post bare through the
|
|
696
|
+
* ordinary API, SQL it back on, and the book verified clean with
|
|
697
|
+
* `checkedEvidence: 0`: the feature's one traceless bypass.
|
|
698
|
+
*
|
|
699
|
+
* Read it as "the book does not satisfy TODAY'S policy", never "someone
|
|
700
|
+
* tampered": turning the flag on flags history — every bare entry posted
|
|
701
|
+
* before the requirement existed — and that is BY DESIGN (the operator asked
|
|
702
|
+
* "which posted entries can't answer for themselves under the current
|
|
703
|
+
* rules?", and pre-toggle history is exactly part of the answer). Its
|
|
704
|
+
* messages carry the `policy advisory` prefix so a report reader (and any
|
|
705
|
+
* alerting built on findings) can band it separately from the tamper codes.
|
|
706
|
+
*
|
|
707
|
+
* Carve-outs: reversals (`reverses !== null`) and closing entries — the
|
|
708
|
+
* post-time gate exempts both, so their bareness is never a policy breach.
|
|
709
|
+
* Since F1 a reversal CARRIES its original's manifest, so this carve-out in
|
|
710
|
+
* practice only shields reversals of bare legacy originals — and there the
|
|
711
|
+
* policy claim belongs to the ORIGINAL entry, which this same check flags
|
|
712
|
+
* (void entries are in scope precisely so a reversed bare entry stays
|
|
713
|
+
* visible).
|
|
714
|
+
*/
|
|
715
|
+
| 'evidence-required-missing'
|
|
716
|
+
/**
|
|
717
|
+
* The LINEAR audit hash chain (`prev_hash`, migration 0021) fails to verify
|
|
718
|
+
* (LGR-15): some event's `prev_hash` is not the hash of its predecessor, or
|
|
719
|
+
* the genesis link is wrong. Distinct from `audit-chain-broken` (the
|
|
720
|
+
* per-entity before/after linkage): this is the whole-log tamper-evidence
|
|
721
|
+
* chain — an edited, reordered, or middle-deleted event whose perpetrator
|
|
722
|
+
* did not recompute every following link. Previously `verifyAuditChain`
|
|
723
|
+
* existed but had NO production caller; folding it into this report is what
|
|
724
|
+
* makes the documented check actually run at the route, the CLI, and the
|
|
725
|
+
* restore door.
|
|
726
|
+
*/
|
|
727
|
+
| 'audit-prev-hash-broken';
|
|
728
|
+
/**
|
|
729
|
+
* The ADVISORY band of the finding-code union (LGR-14 Q3): codes that report
|
|
730
|
+
* "the book does not satisfy today's POLICY", never "storage was tampered
|
|
731
|
+
* with". Every other code is the tamper band. Keyed off the code union — not
|
|
732
|
+
* message text — because exit policies and alerting hang off this distinction:
|
|
733
|
+
* `--verify-ledger` exits 0 on an advisory-only report (still printed), so a
|
|
734
|
+
* healthy book that enables evidence-required over bare history does not turn
|
|
735
|
+
* every backup-verification script permanently red and teach operators to
|
|
736
|
+
* ignore the one alarm that matters. Any future advisory code MUST land here.
|
|
737
|
+
*/
|
|
738
|
+
export declare const LEDGER_VERIFY_ADVISORY_CODES: readonly ["evidence-required-missing"];
|
|
739
|
+
/** Whether `code` is an advisory (current-policy) finding, not a tamper one. */
|
|
740
|
+
export declare function isLedgerVerifyAdvisory(code: LedgerVerifyCode): boolean;
|
|
741
|
+
/** One invariant violation the verifier found against raw storage. */
|
|
742
|
+
export interface LedgerVerifyFinding {
|
|
743
|
+
code: LedgerVerifyCode;
|
|
744
|
+
/** Human-readable, entity-id-bearing description of the violation. */
|
|
745
|
+
message: string;
|
|
746
|
+
/** The primary entity (transaction / posting / account / audit seq) at fault. */
|
|
747
|
+
entityId?: string;
|
|
748
|
+
}
|
|
749
|
+
/** The verifier's report: what was checked, and every finding (empty = clean). */
|
|
750
|
+
export interface LedgerVerifyReport {
|
|
751
|
+
/** False when the ledger has never been seeded — trivially clean. */
|
|
752
|
+
initialized: boolean;
|
|
753
|
+
checkedTransactions: number;
|
|
754
|
+
checkedPostings: number;
|
|
755
|
+
checkedAccounts: number;
|
|
756
|
+
checkedAuditEvents: number;
|
|
757
|
+
/** Period records checked against the audit stream (LGR-12). */
|
|
758
|
+
checkedPeriods: number;
|
|
759
|
+
/** Evidence manifest items re-checked against the asset store (LGR-14). */
|
|
760
|
+
checkedEvidence: number;
|
|
761
|
+
/** Reconciliation records checked against the audit stream (LGR-15). */
|
|
762
|
+
checkedReconciliations: number;
|
|
763
|
+
/**
|
|
764
|
+
* The linear `prev_hash` chain verdict (LGR-15) — `verifyLedgerAuditChain`
|
|
765
|
+
* over the full stream. Additive and absent on an uninitialized ledger. A
|
|
766
|
+
* broken chain also lands in `findings` as `audit-prev-hash-broken`, so
|
|
767
|
+
* severity-aware consumers (the CLI exit code) need no special casing.
|
|
768
|
+
*/
|
|
769
|
+
auditChain?: LedgerAuditChainResult;
|
|
770
|
+
/** Empty = every invariant holds against raw storage. */
|
|
771
|
+
findings: LedgerVerifyFinding[];
|
|
772
|
+
}
|