iterate-kernel 0.1.0-draft.1
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/README.md +56 -0
- package/dist/canonical-json.d.ts +21 -0
- package/dist/canonical-json.d.ts.map +1 -0
- package/dist/canonical-json.js +31 -0
- package/dist/canonical-json.js.map +1 -0
- package/dist/decision-log-entry.d.ts +34 -0
- package/dist/decision-log-entry.d.ts.map +1 -0
- package/dist/decision-log-entry.js +17 -0
- package/dist/decision-log-entry.js.map +1 -0
- package/dist/decision-log.d.ts +142 -0
- package/dist/decision-log.d.ts.map +1 -0
- package/dist/decision-log.js +288 -0
- package/dist/decision-log.js.map +1 -0
- package/dist/dimension-context.d.ts +402 -0
- package/dist/dimension-context.d.ts.map +1 -0
- package/dist/dimension-context.js +226 -0
- package/dist/dimension-context.js.map +1 -0
- package/dist/errors.d.ts +15 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +18 -0
- package/dist/errors.js.map +1 -0
- package/dist/evidence-decision.d.ts +44 -0
- package/dist/evidence-decision.d.ts.map +1 -0
- package/dist/evidence-decision.js +88 -0
- package/dist/evidence-decision.js.map +1 -0
- package/dist/evidence-pack.d.ts +1884 -0
- package/dist/evidence-pack.d.ts.map +1 -0
- package/dist/evidence-pack.js +243 -0
- package/dist/evidence-pack.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/parse.d.ts +22 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +53 -0
- package/dist/parse.js.map +1 -0
- package/dist/recipe-config.d.ts +48 -0
- package/dist/recipe-config.d.ts.map +1 -0
- package/dist/recipe-config.js +20 -0
- package/dist/recipe-config.js.map +1 -0
- package/dist/schemas.d.ts +11 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +19 -0
- package/dist/schemas.js.map +1 -0
- package/package.json +84 -0
- package/schemas/decision-log-entry.schema.json +45 -0
- package/schemas/evidence-pack.schema.json +359 -0
- package/schemas/recipe-config.schema.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# iterate-kernel
|
|
2
|
+
|
|
3
|
+
The iterate ecosystem's shared semantic kernel: schemas, the evidence → decision
|
|
4
|
+
transcription, the decision-log audit chain, and public validators.
|
|
5
|
+
|
|
6
|
+
**What it is not.** It carries no verification reasoning. Judging whether a claim is true
|
|
7
|
+
belongs to the engine that produced the evidence; this package transcribes what that engine
|
|
8
|
+
already decided, so that every shell in the ecosystem speaks the same language instead of
|
|
9
|
+
each inventing its own.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install iterate-kernel
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Import per module, not through the barrel
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { decisionOutcomeFromEvidence } from "iterate-kernel/evidence-decision"
|
|
21
|
+
import { appendDecisionEntry } from "iterate-kernel/decision-log"
|
|
22
|
+
import { parseEvidencePack } from "iterate-kernel/parse"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Each module is its own subpath export. Importing the barrel (`iterate-kernel`) also pulls
|
|
26
|
+
in `schemas`, which reads the JSON Schema files next to the package at load time — fine in a
|
|
27
|
+
repository, but a runtime file read is a trap for anyone bundling to a single file. The
|
|
28
|
+
subpaths avoid it, and they keep a consumer's dependency on this package as narrow as the
|
|
29
|
+
APIs it actually uses.
|
|
30
|
+
|
|
31
|
+
## The pieces
|
|
32
|
+
|
|
33
|
+
| Module | What it is for |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| `evidence-pack` / `parse` | the evidence pack shape, and parsing/validating one |
|
|
36
|
+
| `evidence-decision` | the **transcription**: evidence (attribution level, circuit-breaker level, pass/fail/inconclusive) → a decision outcome and a human summary. It reads fields the engine already decided; it never infers. |
|
|
37
|
+
| `decision-log-entry` / `decision-log` | the append-only decision-log entry and the `opID ↔ entry` hash chain that makes it auditable |
|
|
38
|
+
| `errors` / `schemas` / `canonical-json` / `recipe-config` | `KernelSchemaError`, the JSON Schemas, canonical serialisation, recipe validation |
|
|
39
|
+
|
|
40
|
+
## Versioning
|
|
41
|
+
|
|
42
|
+
`0.1.x` is pre-stable. The schemas and the transcription rules are the contract every
|
|
43
|
+
consumer depends on, so a change to either is a minor-version change at minimum, and the
|
|
44
|
+
fixtures in `fixtures/` are the executable statement of what the contract means.
|
|
45
|
+
|
|
46
|
+
## Consumers
|
|
47
|
+
|
|
48
|
+
Vendored or installed, a consumer's kernel is one of two things, and the difference is a
|
|
49
|
+
distribution decision rather than a semantic one:
|
|
50
|
+
|
|
51
|
+
- **vendored source**, hash-pinned per file, with a probe that asks whether the canonical
|
|
52
|
+
branch has moved (what the GlassPane Harness fork does), or
|
|
53
|
+
- **this package**, pinned by semver.
|
|
54
|
+
|
|
55
|
+
Either way, run the fixtures: they are how you find out that the kernel you have is not the
|
|
56
|
+
kernel you think you have.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical JSON serialization — the cross-language form used by the C35
|
|
3
|
+
* dual-binding gate.
|
|
4
|
+
*
|
|
5
|
+
* Rules (identical to `engine/Sources/GlassPaneEngine/CanonicalJSON.swift`,
|
|
6
|
+
* which is the independent Swift implementation; divergence between the two
|
|
7
|
+
* shows up as a fixture mismatch, not as a silently different digest):
|
|
8
|
+
* - object keys sorted by UTF-16 code unit order;
|
|
9
|
+
* - no insignificant whitespace;
|
|
10
|
+
* - numbers in the host's shortest roundtrip form (integral values print
|
|
11
|
+
* without a fraction on both sides);
|
|
12
|
+
* - scalars quoted exactly as `JSON.stringify` does (control chars as
|
|
13
|
+
* `\u00xx`, `/` and non-ASCII left raw).
|
|
14
|
+
*
|
|
15
|
+
* The decision log hashes this form (see `decision-log.ts`), so the digest of
|
|
16
|
+
* an entry is reproducible from TypeScript, Swift, or `printf … | sha256sum`
|
|
17
|
+
* given the same bytes. That property is the point: an audit chain nobody else
|
|
18
|
+
* can recompute is a claim, not evidence.
|
|
19
|
+
*/
|
|
20
|
+
export declare function canonicalJson(value: unknown): string;
|
|
21
|
+
//# sourceMappingURL=canonical-json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical-json.d.ts","sourceRoot":"","sources":["../src/canonical-json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAUpD"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical JSON serialization — the cross-language form used by the C35
|
|
3
|
+
* dual-binding gate.
|
|
4
|
+
*
|
|
5
|
+
* Rules (identical to `engine/Sources/GlassPaneEngine/CanonicalJSON.swift`,
|
|
6
|
+
* which is the independent Swift implementation; divergence between the two
|
|
7
|
+
* shows up as a fixture mismatch, not as a silently different digest):
|
|
8
|
+
* - object keys sorted by UTF-16 code unit order;
|
|
9
|
+
* - no insignificant whitespace;
|
|
10
|
+
* - numbers in the host's shortest roundtrip form (integral values print
|
|
11
|
+
* without a fraction on both sides);
|
|
12
|
+
* - scalars quoted exactly as `JSON.stringify` does (control chars as
|
|
13
|
+
* `\u00xx`, `/` and non-ASCII left raw).
|
|
14
|
+
*
|
|
15
|
+
* The decision log hashes this form (see `decision-log.ts`), so the digest of
|
|
16
|
+
* an entry is reproducible from TypeScript, Swift, or `printf … | sha256sum`
|
|
17
|
+
* given the same bytes. That property is the point: an audit chain nobody else
|
|
18
|
+
* can recompute is a claim, not evidence.
|
|
19
|
+
*/
|
|
20
|
+
export function canonicalJson(value) {
|
|
21
|
+
if (value === null || typeof value !== "object") {
|
|
22
|
+
return JSON.stringify(value);
|
|
23
|
+
}
|
|
24
|
+
if (Array.isArray(value)) {
|
|
25
|
+
return "[" + value.map((item) => canonicalJson(item)).join(",") + "]";
|
|
26
|
+
}
|
|
27
|
+
const record = value;
|
|
28
|
+
const keys = Object.keys(record).sort();
|
|
29
|
+
return "{" + keys.map((key) => JSON.stringify(key) + ":" + canonicalJson(record[key])).join(",") + "}";
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=canonical-json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical-json.js","sourceRoot":"","sources":["../src/canonical-json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACxE,CAAC;IACD,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IACxC,OAAO,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;AACzG,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const DECISION_ENTRY_ID_PATTERN: RegExp;
|
|
3
|
+
/** SHA-256 full hex (64 lowercase chars), or the empty string for the genesis entry. */
|
|
4
|
+
export declare const PREV_ENTRY_HASH_PATTERN: RegExp;
|
|
5
|
+
export declare const SUMMARY_MAX_LENGTH = 2048;
|
|
6
|
+
export declare const DecisionOutcomeSchema: z.ZodEnum<["pass", "fail", "blocked", "inconclusive"]>;
|
|
7
|
+
export type DecisionOutcome = z.infer<typeof DecisionOutcomeSchema>;
|
|
8
|
+
export declare const DecisionLogEntrySchema: z.ZodObject<{
|
|
9
|
+
entryId: z.ZodString;
|
|
10
|
+
sequence: z.ZodNumber;
|
|
11
|
+
prevEntryHash: z.ZodString;
|
|
12
|
+
operationId: z.ZodOptional<z.ZodString>;
|
|
13
|
+
summary: z.ZodString;
|
|
14
|
+
outcome: z.ZodEnum<["pass", "fail", "blocked", "inconclusive"]>;
|
|
15
|
+
createdAt: z.ZodString;
|
|
16
|
+
}, "strict", z.ZodTypeAny, {
|
|
17
|
+
createdAt: string;
|
|
18
|
+
entryId: string;
|
|
19
|
+
sequence: number;
|
|
20
|
+
prevEntryHash: string;
|
|
21
|
+
summary: string;
|
|
22
|
+
outcome: "pass" | "fail" | "blocked" | "inconclusive";
|
|
23
|
+
operationId?: string | undefined;
|
|
24
|
+
}, {
|
|
25
|
+
createdAt: string;
|
|
26
|
+
entryId: string;
|
|
27
|
+
sequence: number;
|
|
28
|
+
prevEntryHash: string;
|
|
29
|
+
summary: string;
|
|
30
|
+
outcome: "pass" | "fail" | "blocked" | "inconclusive";
|
|
31
|
+
operationId?: string | undefined;
|
|
32
|
+
}>;
|
|
33
|
+
export type DecisionLogEntry = z.infer<typeof DecisionLogEntrySchema>;
|
|
34
|
+
//# sourceMappingURL=decision-log-entry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decision-log-entry.d.ts","sourceRoot":"","sources":["../src/decision-log-entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,yBAAyB,QAAgC,CAAC;AAEvE,wFAAwF;AACxF,eAAO,MAAM,uBAAuB,QAAsB,CAAC;AAE3D,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAEvC,eAAO,MAAM,qBAAqB,wDAAsD,CAAC;AACzF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;EAQjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ISO_MILLIS_PATTERN, OPERATION_ID_PATTERN } from "./evidence-pack.js";
|
|
3
|
+
export const DECISION_ENTRY_ID_PATTERN = /^dl_[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
4
|
+
/** SHA-256 full hex (64 lowercase chars), or the empty string for the genesis entry. */
|
|
5
|
+
export const PREV_ENTRY_HASH_PATTERN = /^([0-9a-f]{64})?$/;
|
|
6
|
+
export const SUMMARY_MAX_LENGTH = 2048;
|
|
7
|
+
export const DecisionOutcomeSchema = z.enum(["pass", "fail", "blocked", "inconclusive"]);
|
|
8
|
+
export const DecisionLogEntrySchema = z.strictObject({
|
|
9
|
+
entryId: z.string().regex(DECISION_ENTRY_ID_PATTERN),
|
|
10
|
+
sequence: z.number().int().min(0),
|
|
11
|
+
prevEntryHash: z.string().regex(PREV_ENTRY_HASH_PATTERN),
|
|
12
|
+
operationId: z.string().regex(OPERATION_ID_PATTERN).optional(),
|
|
13
|
+
summary: z.string().max(SUMMARY_MAX_LENGTH),
|
|
14
|
+
outcome: DecisionOutcomeSchema,
|
|
15
|
+
createdAt: z.string().regex(ISO_MILLIS_PATTERN)
|
|
16
|
+
});
|
|
17
|
+
//# sourceMappingURL=decision-log-entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decision-log-entry.js","sourceRoot":"","sources":["../src/decision-log-entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE9E,MAAM,CAAC,MAAM,yBAAyB,GAAG,6BAA6B,CAAC;AAEvE,wFAAwF;AACxF,MAAM,CAAC,MAAM,uBAAuB,GAAG,mBAAmB,CAAC;AAE3D,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEvC,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC,CAAC;AAGzF,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,YAAY,CAAC;IACnD,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,yBAAyB,CAAC;IACpD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACjC,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,uBAAuB,CAAC;IACxD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC,QAAQ,EAAE;IAC9D,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAC3C,OAAO,EAAE,qBAAqB;IAC9B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,kBAAkB,CAAC;CAChD,CAAC,CAAC"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { type DecisionLogEntry, type DecisionOutcome } from "./decision-log-entry.js";
|
|
2
|
+
/**
|
|
3
|
+
* The decision log: an append-only JSONL ledger whose entries are linked by
|
|
4
|
+
* `prevEntryHash` — the opID↔decision one-way audit chain that
|
|
5
|
+
* `kernel/schemas/decision-log-entry.schema.json` describes at the entry level.
|
|
6
|
+
*
|
|
7
|
+
* Chain rule, stated so an auditor can recompute it without reading TypeScript:
|
|
8
|
+
*
|
|
9
|
+
* line i = canonical JSON of entry i (keys sorted, no whitespace)
|
|
10
|
+
* sequence = i (genesis is 0, strictly +1 — no gaps, no reuse)
|
|
11
|
+
* prevEntryHash = "" for i = 0, otherwise sha256_hex(canonical JSON of entry i-1)
|
|
12
|
+
*
|
|
13
|
+
* Because a line's bytes *are* the canonical form,
|
|
14
|
+
* `printf '%s' "$(sed -n '2p' ledger.jsonl)" | sha256sum` reproduces the
|
|
15
|
+
* `prevEntryHash` that entry 3 must carry. A chain only one language can
|
|
16
|
+
* recompute is a claim, not evidence.
|
|
17
|
+
*
|
|
18
|
+
* The write discipline copies the ledgers this ecosystem already runs
|
|
19
|
+
* (`ApprovalGate.swift`, `mcp-shell/src/project-registry.ts`) instead of
|
|
20
|
+
* inventing a third: validate before touching disk, refuse on a corrupt tail
|
|
21
|
+
* rather than "repairing" it, keep the file 0600 in a directory that is not
|
|
22
|
+
* group/other-writable, fsync, then read back and verify — and never truncate,
|
|
23
|
+
* because the ledger's job is to record what happened, including a failed write.
|
|
24
|
+
*/
|
|
25
|
+
export declare const DECISION_LOG_CODES: {
|
|
26
|
+
readonly path: "KERNEL_E_DECISION_LOG_PATH";
|
|
27
|
+
readonly corrupt: "KERNEL_E_DECISION_LOG_CORRUPT";
|
|
28
|
+
readonly chain: "KERNEL_E_DECISION_LOG_CHAIN";
|
|
29
|
+
readonly location: "KERNEL_E_DECISION_LOG_LOCATION";
|
|
30
|
+
readonly write: "KERNEL_E_DECISION_LOG_WRITE";
|
|
31
|
+
readonly draft: "KERNEL_E_DECISION_LOG_DRAFT";
|
|
32
|
+
};
|
|
33
|
+
export type KernelDecisionLogCode = (typeof DECISION_LOG_CODES)[keyof typeof DECISION_LOG_CODES];
|
|
34
|
+
/**
|
|
35
|
+
* Every failure carries `code` + `message` + `remedy`, the same three-field shape
|
|
36
|
+
* the engine's error frames use: whoever catches this must be able to act on it
|
|
37
|
+
* without reading this file.
|
|
38
|
+
*/
|
|
39
|
+
export declare class KernelDecisionLogError extends Error {
|
|
40
|
+
readonly code: KernelDecisionLogCode;
|
|
41
|
+
readonly path: string;
|
|
42
|
+
readonly line?: number;
|
|
43
|
+
readonly remedy: string;
|
|
44
|
+
constructor(code: KernelDecisionLogCode, path: string, message: string, remedy: string, line?: number);
|
|
45
|
+
}
|
|
46
|
+
/** What a producer supplies; the chain fields are derived from the ledger itself. */
|
|
47
|
+
export interface DecisionLogDraft {
|
|
48
|
+
readonly entryId: string;
|
|
49
|
+
readonly summary: string;
|
|
50
|
+
readonly outcome: DecisionOutcome;
|
|
51
|
+
readonly createdAt: string;
|
|
52
|
+
readonly operationId?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The draft contract, strict. An unknown key is refused rather than dropped:
|
|
56
|
+
* a producer that spells a field wrong (`operation_id`) would otherwise lose
|
|
57
|
+
* that information silently into a ledger whose whole purpose is to say what
|
|
58
|
+
* was decided and why. `sequence`/`prevEntryHash` are deliberately *not*
|
|
59
|
+
* accepted here — the ledger owns them (see `appendDecisionLogEntry`).
|
|
60
|
+
*/
|
|
61
|
+
export declare const DecisionLogDraftSchema: import("zod").ZodObject<Omit<{
|
|
62
|
+
entryId: import("zod").ZodString;
|
|
63
|
+
sequence: import("zod").ZodNumber;
|
|
64
|
+
prevEntryHash: import("zod").ZodString;
|
|
65
|
+
operationId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
66
|
+
summary: import("zod").ZodString;
|
|
67
|
+
outcome: import("zod").ZodEnum<["pass", "fail", "blocked", "inconclusive"]>;
|
|
68
|
+
createdAt: import("zod").ZodString;
|
|
69
|
+
}, "sequence" | "prevEntryHash">, "strict", import("zod").ZodTypeAny, {
|
|
70
|
+
createdAt: string;
|
|
71
|
+
entryId: string;
|
|
72
|
+
summary: string;
|
|
73
|
+
outcome: "pass" | "fail" | "blocked" | "inconclusive";
|
|
74
|
+
operationId?: string | undefined;
|
|
75
|
+
}, {
|
|
76
|
+
createdAt: string;
|
|
77
|
+
entryId: string;
|
|
78
|
+
summary: string;
|
|
79
|
+
outcome: "pass" | "fail" | "blocked" | "inconclusive";
|
|
80
|
+
operationId?: string | undefined;
|
|
81
|
+
}>;
|
|
82
|
+
export interface AppendedDecisionLogEntry {
|
|
83
|
+
readonly entry: DecisionLogEntry;
|
|
84
|
+
/** sha256 of the entry's canonical JSON — what the *next* entry must carry as `prevEntryHash`. */
|
|
85
|
+
readonly hash: string;
|
|
86
|
+
/** 1-based line the entry now occupies. */
|
|
87
|
+
readonly line: number;
|
|
88
|
+
readonly bytesWritten: number;
|
|
89
|
+
}
|
|
90
|
+
export interface DecisionLogVerification {
|
|
91
|
+
readonly ok: boolean;
|
|
92
|
+
readonly entries: readonly DecisionLogEntry[];
|
|
93
|
+
/** 1-based line of the first problem, when there is one. */
|
|
94
|
+
readonly firstBrokenLine?: number;
|
|
95
|
+
/** `corrupt` = bytes that are not a valid entry; `chain` = a valid entry that does not link. */
|
|
96
|
+
readonly brokenKind?: "corrupt" | "chain";
|
|
97
|
+
readonly reason?: string;
|
|
98
|
+
}
|
|
99
|
+
/** Canonical JSON of an entry — the exact bytes that are hashed and stored. */
|
|
100
|
+
export declare function serializeDecisionLogEntry(entry: DecisionLogEntry): string;
|
|
101
|
+
/** sha256 over `serializeDecisionLogEntry`, lowercase full hex. */
|
|
102
|
+
export declare function decisionLogEntryHash(entry: DecisionLogEntry): string;
|
|
103
|
+
/**
|
|
104
|
+
* Crockford-base32 ULID (26 chars; alphabet excludes I/L/O/U) with the `dl_`
|
|
105
|
+
* prefix the schema requires: 48-bit ms timestamp + 80-bit randomness. Same
|
|
106
|
+
* shape as the engine's `op_` ids so a reader can sort and compare them.
|
|
107
|
+
*/
|
|
108
|
+
export declare function newDecisionEntryId(timestampMs?: number): string;
|
|
109
|
+
/**
|
|
110
|
+
* Parse and verify ledger *text* — the pure half, so a reader can verify a string
|
|
111
|
+
* obtained anywhere (a CI artifact, a pasted excerpt, a Swift-written file).
|
|
112
|
+
* Never throws: a broken chain is a result, because the auditor's job is to say
|
|
113
|
+
* where it broke.
|
|
114
|
+
*/
|
|
115
|
+
export declare function verifyDecisionLogText(text: string): DecisionLogVerification;
|
|
116
|
+
/** Read + verify a ledger file. A missing file is an empty chain, not an error. */
|
|
117
|
+
export declare function readDecisionLog(logPath: string): DecisionLogVerification;
|
|
118
|
+
/** The chain fields the next entry must carry, taken from the ledger itself. */
|
|
119
|
+
export declare function decisionLogHead(logPath: string): {
|
|
120
|
+
sequence: number;
|
|
121
|
+
prevEntryHash: string;
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Validate a draft plus its chain fields into a strict `DecisionLogEntry`, before
|
|
125
|
+
* any disk access. `operationId` is omitted rather than written as null, matching
|
|
126
|
+
* the schema and the engine's Codable binding.
|
|
127
|
+
*/
|
|
128
|
+
export declare function buildDecisionLogEntry(draft: DecisionLogDraft, chain: {
|
|
129
|
+
sequence: number;
|
|
130
|
+
prevEntryHash: string;
|
|
131
|
+
}): DecisionLogEntry;
|
|
132
|
+
/**
|
|
133
|
+
* Append one entry and return what was written. The chain fields come from the
|
|
134
|
+
* ledger, so a caller cannot choose them.
|
|
135
|
+
*
|
|
136
|
+
* Single-writer assumption, stated plainly: concurrent appends from two
|
|
137
|
+
* processes can interleave. This function does not pretend to solve that — it
|
|
138
|
+
* detects it (read-back + verify) and reports it, and the next writer refuses to
|
|
139
|
+
* build on the damaged tail.
|
|
140
|
+
*/
|
|
141
|
+
export declare function appendDecisionLogEntry(logPath: string, draft: DecisionLogDraft): AppendedDecisionLogEntry;
|
|
142
|
+
//# sourceMappingURL=decision-log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decision-log.d.ts","sourceRoot":"","sources":["../src/decision-log.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { closeSync, fchmodSync, fsyncSync, openSync, readFileSync, statSync, writeSync } from "node:fs";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { canonicalJson } from "./canonical-json.js";
|
|
5
|
+
import { DecisionLogEntrySchema } from "./decision-log-entry.js";
|
|
6
|
+
import { parseDecisionLogEntry } from "./parse.js";
|
|
7
|
+
/**
|
|
8
|
+
* The decision log: an append-only JSONL ledger whose entries are linked by
|
|
9
|
+
* `prevEntryHash` — the opID↔decision one-way audit chain that
|
|
10
|
+
* `kernel/schemas/decision-log-entry.schema.json` describes at the entry level.
|
|
11
|
+
*
|
|
12
|
+
* Chain rule, stated so an auditor can recompute it without reading TypeScript:
|
|
13
|
+
*
|
|
14
|
+
* line i = canonical JSON of entry i (keys sorted, no whitespace)
|
|
15
|
+
* sequence = i (genesis is 0, strictly +1 — no gaps, no reuse)
|
|
16
|
+
* prevEntryHash = "" for i = 0, otherwise sha256_hex(canonical JSON of entry i-1)
|
|
17
|
+
*
|
|
18
|
+
* Because a line's bytes *are* the canonical form,
|
|
19
|
+
* `printf '%s' "$(sed -n '2p' ledger.jsonl)" | sha256sum` reproduces the
|
|
20
|
+
* `prevEntryHash` that entry 3 must carry. A chain only one language can
|
|
21
|
+
* recompute is a claim, not evidence.
|
|
22
|
+
*
|
|
23
|
+
* The write discipline copies the ledgers this ecosystem already runs
|
|
24
|
+
* (`ApprovalGate.swift`, `mcp-shell/src/project-registry.ts`) instead of
|
|
25
|
+
* inventing a third: validate before touching disk, refuse on a corrupt tail
|
|
26
|
+
* rather than "repairing" it, keep the file 0600 in a directory that is not
|
|
27
|
+
* group/other-writable, fsync, then read back and verify — and never truncate,
|
|
28
|
+
* because the ledger's job is to record what happened, including a failed write.
|
|
29
|
+
*/
|
|
30
|
+
export const DECISION_LOG_CODES = {
|
|
31
|
+
path: "KERNEL_E_DECISION_LOG_PATH",
|
|
32
|
+
corrupt: "KERNEL_E_DECISION_LOG_CORRUPT",
|
|
33
|
+
chain: "KERNEL_E_DECISION_LOG_CHAIN",
|
|
34
|
+
location: "KERNEL_E_DECISION_LOG_LOCATION",
|
|
35
|
+
write: "KERNEL_E_DECISION_LOG_WRITE",
|
|
36
|
+
draft: "KERNEL_E_DECISION_LOG_DRAFT",
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Every failure carries `code` + `message` + `remedy`, the same three-field shape
|
|
40
|
+
* the engine's error frames use: whoever catches this must be able to act on it
|
|
41
|
+
* without reading this file.
|
|
42
|
+
*/
|
|
43
|
+
export class KernelDecisionLogError extends Error {
|
|
44
|
+
code;
|
|
45
|
+
path;
|
|
46
|
+
line;
|
|
47
|
+
remedy;
|
|
48
|
+
constructor(code, path, message, remedy, line) {
|
|
49
|
+
super(`${code}: ${message}`);
|
|
50
|
+
this.name = "KernelDecisionLogError";
|
|
51
|
+
this.code = code;
|
|
52
|
+
this.path = path;
|
|
53
|
+
this.remedy = remedy;
|
|
54
|
+
if (line !== undefined)
|
|
55
|
+
this.line = line;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The draft contract, strict. An unknown key is refused rather than dropped:
|
|
60
|
+
* a producer that spells a field wrong (`operation_id`) would otherwise lose
|
|
61
|
+
* that information silently into a ledger whose whole purpose is to say what
|
|
62
|
+
* was decided and why. `sequence`/`prevEntryHash` are deliberately *not*
|
|
63
|
+
* accepted here — the ledger owns them (see `appendDecisionLogEntry`).
|
|
64
|
+
*/
|
|
65
|
+
export const DecisionLogDraftSchema = DecisionLogEntrySchema.omit({
|
|
66
|
+
sequence: true,
|
|
67
|
+
prevEntryHash: true,
|
|
68
|
+
});
|
|
69
|
+
/** Canonical JSON of an entry — the exact bytes that are hashed and stored. */
|
|
70
|
+
export function serializeDecisionLogEntry(entry) {
|
|
71
|
+
return canonicalJson(entry);
|
|
72
|
+
}
|
|
73
|
+
/** sha256 over `serializeDecisionLogEntry`, lowercase full hex. */
|
|
74
|
+
export function decisionLogEntryHash(entry) {
|
|
75
|
+
return createHash("sha256").update(serializeDecisionLogEntry(entry), "utf8").digest("hex");
|
|
76
|
+
}
|
|
77
|
+
const CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
78
|
+
/**
|
|
79
|
+
* Crockford-base32 ULID (26 chars; alphabet excludes I/L/O/U) with the `dl_`
|
|
80
|
+
* prefix the schema requires: 48-bit ms timestamp + 80-bit randomness. Same
|
|
81
|
+
* shape as the engine's `op_` ids so a reader can sort and compare them.
|
|
82
|
+
*/
|
|
83
|
+
export function newDecisionEntryId(timestampMs = Date.now()) {
|
|
84
|
+
if (!Number.isInteger(timestampMs) || timestampMs < 0 || timestampMs > 0xffffffffffff) {
|
|
85
|
+
throw new RangeError(`entryId timestamp must be an integer in [0, 2^48): got ${timestampMs}`);
|
|
86
|
+
}
|
|
87
|
+
let out = "";
|
|
88
|
+
let value = timestampMs;
|
|
89
|
+
for (let i = 0; i < 10; i++) {
|
|
90
|
+
out = CROCKFORD.charAt(value & 0x1f) + out;
|
|
91
|
+
value = Math.floor(value / 32);
|
|
92
|
+
}
|
|
93
|
+
for (const byte of randomBytes(10)) {
|
|
94
|
+
out += CROCKFORD.charAt((byte & 0xe0) >> 5) + CROCKFORD.charAt(byte & 0x1f);
|
|
95
|
+
}
|
|
96
|
+
return `dl_${out.slice(0, 26)}`;
|
|
97
|
+
}
|
|
98
|
+
function broken(entries, kind, line, reason) {
|
|
99
|
+
return { ok: false, entries, firstBrokenLine: line, brokenKind: kind, reason };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Parse and verify ledger *text* — the pure half, so a reader can verify a string
|
|
103
|
+
* obtained anywhere (a CI artifact, a pasted excerpt, a Swift-written file).
|
|
104
|
+
* Never throws: a broken chain is a result, because the auditor's job is to say
|
|
105
|
+
* where it broke.
|
|
106
|
+
*/
|
|
107
|
+
export function verifyDecisionLogText(text) {
|
|
108
|
+
const body = text.endsWith("\n") ? text.slice(0, -1) : text;
|
|
109
|
+
if (body.length === 0) {
|
|
110
|
+
return { ok: true, entries: [] };
|
|
111
|
+
}
|
|
112
|
+
const lines = body.split("\n");
|
|
113
|
+
const entries = [];
|
|
114
|
+
for (let i = 0; i < lines.length; i++) {
|
|
115
|
+
const lineNumber = i + 1;
|
|
116
|
+
const raw = lines[i] ?? "";
|
|
117
|
+
let parsed;
|
|
118
|
+
try {
|
|
119
|
+
parsed = JSON.parse(raw);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
return broken(entries, "corrupt", lineNumber, `line ${lineNumber} is not valid JSON (${error.message})`);
|
|
123
|
+
}
|
|
124
|
+
let entry;
|
|
125
|
+
try {
|
|
126
|
+
entry = parseDecisionLogEntry(parsed);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
return broken(entries, "corrupt", lineNumber, `line ${lineNumber} is not a valid DecisionLogEntry: ${error.message}`);
|
|
130
|
+
}
|
|
131
|
+
if (entry.sequence !== i) {
|
|
132
|
+
return broken(entries, "chain", lineNumber, `line ${lineNumber} declares sequence ${entry.sequence}, expected ${i} (sequences start at 0 and rise by one)`);
|
|
133
|
+
}
|
|
134
|
+
if (canonicalJson(entry) !== raw) {
|
|
135
|
+
return broken(entries, "corrupt", lineNumber, `line ${lineNumber} is not stored in canonical JSON form, so its hash cannot be recomputed from the bytes on disk`);
|
|
136
|
+
}
|
|
137
|
+
const previous = i === 0 ? undefined : entries[i - 1];
|
|
138
|
+
const expectedPrev = previous === undefined ? "" : decisionLogEntryHash(previous);
|
|
139
|
+
if (entry.prevEntryHash !== expectedPrev) {
|
|
140
|
+
return broken(entries, "chain", lineNumber, i === 0
|
|
141
|
+
? `line 1 is the genesis entry and must carry an empty prevEntryHash, found "${entry.prevEntryHash}"`
|
|
142
|
+
: `line ${lineNumber} links to ${entry.prevEntryHash}, but the entry on line ${lineNumber - 1} hashes to ${expectedPrev}`);
|
|
143
|
+
}
|
|
144
|
+
entries.push(entry);
|
|
145
|
+
}
|
|
146
|
+
return { ok: true, entries };
|
|
147
|
+
}
|
|
148
|
+
/** Read + verify a ledger file. A missing file is an empty chain, not an error. */
|
|
149
|
+
export function readDecisionLog(logPath) {
|
|
150
|
+
requireAbsolutePath(logPath);
|
|
151
|
+
let text;
|
|
152
|
+
try {
|
|
153
|
+
text = readFileSync(logPath, "utf8");
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
if (error.code === "ENOENT") {
|
|
157
|
+
return { ok: true, entries: [] };
|
|
158
|
+
}
|
|
159
|
+
throw new KernelDecisionLogError(DECISION_LOG_CODES.location, logPath, `cannot read the ledger (${error.message})`, `fix the permissions or ownership on ${logPath} — this is evidence, so repair access rather than deleting the file`);
|
|
160
|
+
}
|
|
161
|
+
return verifyDecisionLogText(text);
|
|
162
|
+
}
|
|
163
|
+
/** The chain fields the next entry must carry, taken from the ledger itself. */
|
|
164
|
+
export function decisionLogHead(logPath) {
|
|
165
|
+
const verified = readDecisionLog(logPath);
|
|
166
|
+
if (!verified.ok) {
|
|
167
|
+
throw new KernelDecisionLogError(verified.brokenKind === "chain" ? DECISION_LOG_CODES.chain : DECISION_LOG_CODES.corrupt, logPath, verified.reason ?? "the ledger is not verifiable", `the ledger stops being trustworthy at line ${verified.firstBrokenLine}; preserve the file and investigate — appending on top of a broken link would forge its continuity`, verified.firstBrokenLine);
|
|
168
|
+
}
|
|
169
|
+
const tail = verified.entries[verified.entries.length - 1];
|
|
170
|
+
return {
|
|
171
|
+
sequence: verified.entries.length,
|
|
172
|
+
prevEntryHash: tail === undefined ? "" : decisionLogEntryHash(tail),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function requireAbsolutePath(logPath) {
|
|
176
|
+
if (typeof logPath !== "string" || logPath.length === 0) {
|
|
177
|
+
throw new KernelDecisionLogError(DECISION_LOG_CODES.path, String(logPath), "the ledger path must be an explicit non-empty string", "pass the resolved absolute path the caller was configured with; this API deliberately has no default location");
|
|
178
|
+
}
|
|
179
|
+
if (!logPath.startsWith("/")) {
|
|
180
|
+
throw new KernelDecisionLogError(DECISION_LOG_CODES.path, logPath, "the ledger path must be absolute", `resolve '${logPath}' against a configured data directory first — a CWD-relative ledger silently lands inside whatever repository the agent happened to run from`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Validate a draft plus its chain fields into a strict `DecisionLogEntry`, before
|
|
185
|
+
* any disk access. `operationId` is omitted rather than written as null, matching
|
|
186
|
+
* the schema and the engine's Codable binding.
|
|
187
|
+
*/
|
|
188
|
+
export function buildDecisionLogEntry(draft, chain) {
|
|
189
|
+
const checked = DecisionLogDraftSchema.safeParse(draft);
|
|
190
|
+
if (!checked.success) {
|
|
191
|
+
const detail = checked.error.issues
|
|
192
|
+
.map((issue) => `${issue.path.length > 0 ? issue.path.join(".") : "<root>"}: ${issue.message}`)
|
|
193
|
+
.join("; ");
|
|
194
|
+
throw new KernelDecisionLogError(DECISION_LOG_CODES.draft, "", `refusing to log a decision whose draft is not schema-valid: ${detail}`, "fix the producer — `sequence` and `prevEntryHash` are derived from the ledger and must not be sent, and an unrecognised key means the field name is wrong, not that it is optional");
|
|
195
|
+
}
|
|
196
|
+
const candidate = {
|
|
197
|
+
entryId: checked.data.entryId,
|
|
198
|
+
sequence: chain.sequence,
|
|
199
|
+
prevEntryHash: chain.prevEntryHash,
|
|
200
|
+
summary: checked.data.summary,
|
|
201
|
+
outcome: checked.data.outcome,
|
|
202
|
+
createdAt: checked.data.createdAt,
|
|
203
|
+
};
|
|
204
|
+
if (checked.data.operationId !== undefined)
|
|
205
|
+
candidate.operationId = checked.data.operationId;
|
|
206
|
+
const parsed = DecisionLogEntrySchema.safeParse(candidate);
|
|
207
|
+
if (!parsed.success) {
|
|
208
|
+
const detail = parsed.error.issues
|
|
209
|
+
.map((issue) => `${issue.path.length > 0 ? issue.path.join(".") : "<root>"}: ${issue.message}`)
|
|
210
|
+
.join("; ");
|
|
211
|
+
throw new KernelDecisionLogError(DECISION_LOG_CODES.draft, "", `refusing to log a decision that is not schema-valid: ${detail}`, "fix the entry at the producer — a ledger line that fails the schema can never be verified again");
|
|
212
|
+
}
|
|
213
|
+
return parsed.data;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Append one entry and return what was written. The chain fields come from the
|
|
217
|
+
* ledger, so a caller cannot choose them.
|
|
218
|
+
*
|
|
219
|
+
* Single-writer assumption, stated plainly: concurrent appends from two
|
|
220
|
+
* processes can interleave. This function does not pretend to solve that — it
|
|
221
|
+
* detects it (read-back + verify) and reports it, and the next writer refuses to
|
|
222
|
+
* build on the damaged tail.
|
|
223
|
+
*/
|
|
224
|
+
export function appendDecisionLogEntry(logPath, draft) {
|
|
225
|
+
requireAbsolutePath(logPath);
|
|
226
|
+
// 1. Read and verify first: refuse to append onto a tail we cannot re-hash.
|
|
227
|
+
const head = decisionLogHead(logPath);
|
|
228
|
+
// 2. Validate in memory. Nothing touches the disk if the entry is not valid.
|
|
229
|
+
const entry = buildDecisionLogEntry(draft, head);
|
|
230
|
+
// 3. Location hygiene. The ledger is audit data, so it must not be
|
|
231
|
+
// group/other-readable, and a group/other-writable *directory* would let
|
|
232
|
+
// anyone swap the file out from under the chain.
|
|
233
|
+
const parent = dirname(logPath);
|
|
234
|
+
let dirMode;
|
|
235
|
+
try {
|
|
236
|
+
dirMode = statSync(parent).mode;
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
throw new KernelDecisionLogError(DECISION_LOG_CODES.location, logPath, `the ledger's directory ${parent} does not exist`, "create it (0700) or point the ledger at a directory you control; this API will not create state directories behind the caller's back");
|
|
240
|
+
}
|
|
241
|
+
if ((dirMode & 0o022) !== 0) {
|
|
242
|
+
throw new KernelDecisionLogError(DECISION_LOG_CODES.location, logPath, `${parent} is writable by group or other (mode ${(dirMode & 0o777).toString(8)})`, "an audit ledger in a group/other-writable directory can be replaced wholesale; move it, or chmod 0700 the directory");
|
|
243
|
+
}
|
|
244
|
+
const line = serializeDecisionLogEntry(entry);
|
|
245
|
+
const payload = `${line}\n`;
|
|
246
|
+
const expectedBytes = Buffer.byteLength(payload, "utf8");
|
|
247
|
+
let fd;
|
|
248
|
+
try {
|
|
249
|
+
fd = openSync(logPath, "a", 0o600);
|
|
250
|
+
fchmodSync(fd, 0o600);
|
|
251
|
+
const written = writeSync(fd, payload, null, "utf8");
|
|
252
|
+
fsyncSync(fd);
|
|
253
|
+
if (written !== expectedBytes) {
|
|
254
|
+
throw new KernelDecisionLogError(DECISION_LOG_CODES.write, logPath, `short write: ${written} of ${expectedBytes} bytes reached the fd`, `the tail may be torn — do not delete ${logPath}; call readDecisionLog() to see where verification stops, then investigate before producing more decisions`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
if (error instanceof KernelDecisionLogError)
|
|
259
|
+
throw error;
|
|
260
|
+
throw new KernelDecisionLogError(DECISION_LOG_CODES.write, logPath, `append failed: ${error.message}`, `nothing was recorded for ${draft.entryId}; report this as a failed write instead of continuing as if the decision had been logged`);
|
|
261
|
+
}
|
|
262
|
+
finally {
|
|
263
|
+
if (fd !== undefined)
|
|
264
|
+
closeSync(fd);
|
|
265
|
+
}
|
|
266
|
+
// 4. Read back and verify. A ledger that lies about having been written is
|
|
267
|
+
// worse than one that refuses; so on mismatch we keep the bytes and shout.
|
|
268
|
+
const after = (readFileSync(logPath, "utf8").match(/^.*$/gm) ?? []).filter((value, index, all) => !(index === all.length - 1 && value === ""));
|
|
269
|
+
const lineNumber = after.length;
|
|
270
|
+
const tailLine = after[lineNumber - 1] ?? "";
|
|
271
|
+
if (tailLine !== line) {
|
|
272
|
+
throw new KernelDecisionLogError(DECISION_LOG_CODES.write, logPath, `read-back mismatch at line ${lineNumber}: the bytes on disk are not what this call wrote`, `another writer may have interleaved — preserve ${logPath} and re-verify the chain from line ${lineNumber}`, lineNumber);
|
|
273
|
+
}
|
|
274
|
+
const verified = verifyDecisionLogText(after.join("\n") + "\n");
|
|
275
|
+
if (!verified.ok) {
|
|
276
|
+
throw new KernelDecisionLogError(DECISION_LOG_CODES.write, logPath, `the ledger fails its own verification immediately after a successful append (line ${verified.firstBrokenLine}): ${verified.reason}`, `preserve the file; check the file mode (${modeOf(logPath).toString(8)}) and the raw tail before anything else`, verified.firstBrokenLine);
|
|
277
|
+
}
|
|
278
|
+
return { entry, hash: decisionLogEntryHash(entry), line: lineNumber, bytesWritten: expectedBytes };
|
|
279
|
+
}
|
|
280
|
+
function modeOf(path) {
|
|
281
|
+
try {
|
|
282
|
+
return statSync(path).mode & 0o777;
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return 0;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
//# sourceMappingURL=decision-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decision-log.js","sourceRoot":"","sources":["../src/decision-log.ts"],"names":[],"mappings":"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"}
|