@forgeax/engine-ddc 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/LICENSE +202 -0
- package/README.md +136 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/concurrency.integration.test.d.ts +2 -0
- package/dist/__tests__/concurrency.integration.test.d.ts.map +1 -0
- package/dist/__tests__/consumer-path.integration.test.d.ts +2 -0
- package/dist/__tests__/consumer-path.integration.test.d.ts.map +1 -0
- package/dist/__tests__/crash-recovery.integration.test.d.ts +2 -0
- package/dist/__tests__/crash-recovery.integration.test.d.ts.map +1 -0
- package/dist/__tests__/entry-store.integration.test.d.ts +2 -0
- package/dist/__tests__/entry-store.integration.test.d.ts.map +1 -0
- package/dist/__tests__/errors.unit.test.d.ts +2 -0
- package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
- package/dist/__tests__/key.unit.test.d.ts +2 -0
- package/dist/__tests__/key.unit.test.d.ts.map +1 -0
- package/dist/__tests__/layout.unit.test.d.ts +2 -0
- package/dist/__tests__/layout.unit.test.d.ts.map +1 -0
- package/dist/__tests__/lifecycle.unit.test.d.ts +2 -0
- package/dist/__tests__/lifecycle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/multiprocess-gc.integration.test.d.ts +2 -0
- package/dist/__tests__/multiprocess-gc.integration.test.d.ts.map +1 -0
- package/dist/__tests__/multiprocess-lifecycle.integration.test.d.ts +2 -0
- package/dist/__tests__/multiprocess-lifecycle.integration.test.d.ts.map +1 -0
- package/dist/__tests__/multiprocess-worker.d.ts +2 -0
- package/dist/__tests__/multiprocess-worker.d.ts.map +1 -0
- package/dist/__tests__/status-root-kind-owner.test-d.d.ts +2 -0
- package/dist/__tests__/status-root-kind-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/status.unit.test.d.ts +2 -0
- package/dist/__tests__/status.unit.test.d.ts.map +1 -0
- package/dist/entry-store.d.ts +52 -0
- package/dist/entry-store.d.ts.map +1 -0
- package/dist/entry-store.mjs +339 -0
- package/dist/entry-store.mjs.map +1 -0
- package/dist/errors.d.ts +58 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.mjs +109 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/gc.d.ts +13 -0
- package/dist/gc.d.ts.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +896 -0
- package/dist/index.mjs.map +1 -0
- package/dist/key.d.ts +13 -0
- package/dist/key.d.ts.map +1 -0
- package/dist/key.mjs +38 -0
- package/dist/key.mjs.map +1 -0
- package/dist/layout.d.ts +51 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.mjs +68 -0
- package/dist/layout.mjs.map +1 -0
- package/dist/lifecycle.d.ts +55 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/runtime-scope.d.ts +10 -0
- package/dist/runtime-scope.d.ts.map +1 -0
- package/dist/status.d.ts +39 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.mjs +20 -0
- package/dist/status.mjs.map +1 -0
- package/package.json +98 -0
- package/src/__tests__/concurrency.integration.test.ts +53 -0
- package/src/__tests__/consumer-path.integration.test.ts +61 -0
- package/src/__tests__/crash-recovery.integration.test.ts +84 -0
- package/src/__tests__/entry-store.integration.test.ts +83 -0
- package/src/__tests__/errors.unit.test.ts +54 -0
- package/src/__tests__/key.unit.test.ts +49 -0
- package/src/__tests__/layout.unit.test.ts +50 -0
- package/src/__tests__/lifecycle.unit.test.ts +258 -0
- package/src/__tests__/multiprocess-gc.integration.test.ts +36 -0
- package/src/__tests__/multiprocess-lifecycle.integration.test.ts +109 -0
- package/src/__tests__/multiprocess-worker.ts +41 -0
- package/src/__tests__/status-root-kind-owner.test-d.ts +42 -0
- package/src/__tests__/status.unit.test.ts +56 -0
- package/src/entry-store.ts +307 -0
- package/src/errors.ts +164 -0
- package/src/gc.ts +34 -0
- package/src/index.ts +65 -0
- package/src/key.ts +46 -0
- package/src/layout.ts +132 -0
- package/src/lifecycle.ts +549 -0
- package/src/runtime-scope.ts +87 -0
- package/src/status.ts +58 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { DdcStoreError } from './errors.js';
|
|
2
|
+
export { DdcStoreError } from './errors.js';
|
|
3
|
+
export interface DdcArtifact {
|
|
4
|
+
readonly mediaType: string;
|
|
5
|
+
readonly bytes: Uint8Array;
|
|
6
|
+
}
|
|
7
|
+
export interface DdcReceipt {
|
|
8
|
+
readonly guid: string;
|
|
9
|
+
readonly key: string;
|
|
10
|
+
readonly producer: string;
|
|
11
|
+
readonly inputFingerprint: string;
|
|
12
|
+
readonly outputDigest: string;
|
|
13
|
+
}
|
|
14
|
+
export interface DdcEntry {
|
|
15
|
+
readonly key: string;
|
|
16
|
+
readonly guid: string;
|
|
17
|
+
readonly payload: unknown;
|
|
18
|
+
readonly refs: readonly string[];
|
|
19
|
+
readonly artifacts: Readonly<Record<string, DdcArtifact>>;
|
|
20
|
+
readonly receipt: DdcReceipt;
|
|
21
|
+
}
|
|
22
|
+
export interface StagedDdcEntry {
|
|
23
|
+
readonly key: string;
|
|
24
|
+
readonly path: string;
|
|
25
|
+
}
|
|
26
|
+
export interface PublishDdcEntryResult {
|
|
27
|
+
readonly result: 'published' | 'existing' | 'conflict';
|
|
28
|
+
readonly key: string;
|
|
29
|
+
}
|
|
30
|
+
/** The one output identity shared by receipt validation and DDC publication. */
|
|
31
|
+
export declare function ddcOutputDigest(entry: Pick<DdcEntry, 'guid' | 'payload' | 'refs' | 'artifacts'>): string;
|
|
32
|
+
export declare class DdcEntryStore {
|
|
33
|
+
private readonly entries;
|
|
34
|
+
private readonly staging;
|
|
35
|
+
constructor(root: string);
|
|
36
|
+
stage(entry: DdcEntry): Promise<StagedDdcEntry>;
|
|
37
|
+
publish(staged: StagedDdcEntry): Promise<PublishDdcEntryResult>;
|
|
38
|
+
write(entry: DdcEntry): Promise<PublishDdcEntryResult>;
|
|
39
|
+
read(key: string): Promise<DdcEntry | null>;
|
|
40
|
+
listKeys(): Promise<readonly string[]>;
|
|
41
|
+
remove(key: string): Promise<void>;
|
|
42
|
+
/** Read with a machine-readable corruption result for recovery tooling. */
|
|
43
|
+
readChecked(key: string): Promise<{
|
|
44
|
+
readonly ok: true;
|
|
45
|
+
readonly value: DdcEntry | null;
|
|
46
|
+
} | {
|
|
47
|
+
readonly ok: false;
|
|
48
|
+
readonly error: DdcStoreError;
|
|
49
|
+
}>;
|
|
50
|
+
private readDirectory;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=entry-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-store.d.ts","sourceRoot":"","sources":["../src/entry-store.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;IAC1D,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,UAAU,GAAG,UAAU,CAAC;IACvD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AA2CD,gFAAgF;AAChF,wBAAgB,eAAe,CAC7B,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,CAAC,GAC/D,MAAM,CAiBR;AAoDD,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAEd,IAAI,EAAE,MAAM;IAKlB,KAAK,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC;IAwB/C,OAAO,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAyB/D,KAAK,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAKtD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAS3C,QAAQ,IAAI,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC;IAQtC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK/C,2EAA2E;IAC9D,WAAW,CACtB,GAAG,EAAE,MAAM,GACV,OAAO,CACN;QAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAA;KAAE,GACtD;QAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAA;KAAE,CACxD;YA0Ba,aAAa;CAoC5B"}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { randomUUID, createHash } from 'crypto';
|
|
2
|
+
import { mkdir, writeFile, stat, rm, rename, readdir, readFile } from 'fs/promises';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
|
|
5
|
+
// src/entry-store.ts
|
|
6
|
+
|
|
7
|
+
// src/errors.ts
|
|
8
|
+
var defaultHints = {
|
|
9
|
+
"ddc-project-root-required": "inject the canonical projectDdcRoot before serving or publishing",
|
|
10
|
+
"ddc-object-conflict": "keep the verified object and reject the competing publication",
|
|
11
|
+
"ddc-head-conflict": "inspect the current head and retry with a fresh revision",
|
|
12
|
+
"ddc-generation-conflict": "allocate a new persistent generation; do not edit the counter",
|
|
13
|
+
"ddc-scope-mismatch": "reinspect the canonical root and allocate a new scope",
|
|
14
|
+
"ddc-lease-expired": "discard the stale commit and acquire a new lease",
|
|
15
|
+
"ddc-entry-incomplete": "discard the partial entry and cold-cook from author authority",
|
|
16
|
+
"ddc-entry-invalid": "preserve last-known-good, inspect the entry, then cold-cook",
|
|
17
|
+
"ddc-entry-conflict": "keep the verified entry and reject the competing publication"
|
|
18
|
+
};
|
|
19
|
+
function defaultExpected(code) {
|
|
20
|
+
if (code === "ddc-project-root-required") return "projectDdcRoot";
|
|
21
|
+
if (code === "ddc-entry-incomplete" || code === "ddc-entry-invalid") {
|
|
22
|
+
return "a complete DDC entry whose receipt and integrity digests validate";
|
|
23
|
+
}
|
|
24
|
+
return "a valid DDC owner, scope, generation, revision, and lease";
|
|
25
|
+
}
|
|
26
|
+
function defaultActions(code) {
|
|
27
|
+
switch (code) {
|
|
28
|
+
case "ddc-project-root-required":
|
|
29
|
+
case "ddc-scope-mismatch":
|
|
30
|
+
return [
|
|
31
|
+
{ kind: "inspect", executable: true },
|
|
32
|
+
{ kind: "retry", executable: true }
|
|
33
|
+
];
|
|
34
|
+
case "ddc-generation-conflict":
|
|
35
|
+
return [{ kind: "allocate-generation", executable: true }];
|
|
36
|
+
case "ddc-lease-expired":
|
|
37
|
+
return [
|
|
38
|
+
{ kind: "release-lease", executable: true },
|
|
39
|
+
{ kind: "retry", executable: true }
|
|
40
|
+
];
|
|
41
|
+
case "ddc-object-conflict":
|
|
42
|
+
case "ddc-head-conflict":
|
|
43
|
+
return [
|
|
44
|
+
{ kind: "inspect", executable: true },
|
|
45
|
+
{ kind: "retry", executable: true }
|
|
46
|
+
];
|
|
47
|
+
case "ddc-entry-incomplete":
|
|
48
|
+
case "ddc-entry-invalid":
|
|
49
|
+
return [{ kind: "cold-rebuild", executable: true }];
|
|
50
|
+
case "ddc-entry-conflict":
|
|
51
|
+
return [{ kind: "inspect", executable: true }];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
var DdcStoreError = class extends Error {
|
|
55
|
+
code;
|
|
56
|
+
detail;
|
|
57
|
+
hint;
|
|
58
|
+
expected;
|
|
59
|
+
actual;
|
|
60
|
+
owner;
|
|
61
|
+
rootKind;
|
|
62
|
+
scope;
|
|
63
|
+
generation;
|
|
64
|
+
lease;
|
|
65
|
+
revision;
|
|
66
|
+
recoveryActions;
|
|
67
|
+
hostPath;
|
|
68
|
+
constructor(inputOrCode, legacyDetail) {
|
|
69
|
+
const input = typeof inputOrCode === "string" ? { code: inputOrCode, detail: legacyDetail ?? inputOrCode } : inputOrCode;
|
|
70
|
+
super(input.detail);
|
|
71
|
+
this.name = "DdcStoreError";
|
|
72
|
+
this.code = input.code;
|
|
73
|
+
this.detail = input.detail;
|
|
74
|
+
this.hint = input.hint ?? defaultHints[input.code];
|
|
75
|
+
this.expected = input.expected ?? defaultExpected(input.code);
|
|
76
|
+
this.actual = input.actual;
|
|
77
|
+
this.owner = input.owner;
|
|
78
|
+
this.rootKind = input.rootKind;
|
|
79
|
+
this.scope = input.scope;
|
|
80
|
+
this.generation = input.generation;
|
|
81
|
+
this.lease = input.lease;
|
|
82
|
+
this.revision = input.revision;
|
|
83
|
+
this.recoveryActions = input.recoveryActions ?? defaultActions(input.code);
|
|
84
|
+
this.hostPath = input.hostPath;
|
|
85
|
+
}
|
|
86
|
+
toBrowserProjection() {
|
|
87
|
+
return {
|
|
88
|
+
code: this.code,
|
|
89
|
+
hint: this.hint,
|
|
90
|
+
expected: this.expected,
|
|
91
|
+
...this.actual === void 0 ? {} : { actual: this.actual },
|
|
92
|
+
...this.owner === void 0 ? {} : { owner: this.owner },
|
|
93
|
+
...this.rootKind === void 0 ? {} : { rootKind: this.rootKind },
|
|
94
|
+
...this.scope === void 0 ? {} : { scope: this.scope },
|
|
95
|
+
...this.generation === void 0 ? {} : { generation: this.generation },
|
|
96
|
+
...this.lease === void 0 ? {} : { lease: this.lease },
|
|
97
|
+
...this.revision === void 0 ? {} : { revision: this.revision },
|
|
98
|
+
recoveryActions: this.recoveryActions.map(
|
|
99
|
+
({ exactTarget: _exactTarget, ...action }) => action
|
|
100
|
+
)
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
function canonicalDdcJson(value) {
|
|
105
|
+
const sorted = sortValue(value);
|
|
106
|
+
return JSON.stringify(sorted) ?? "null";
|
|
107
|
+
}
|
|
108
|
+
function sortValue(value) {
|
|
109
|
+
if (value instanceof Uint8Array) {
|
|
110
|
+
return { encoding: "base64", bytes: Buffer.from(value).toString("base64") };
|
|
111
|
+
}
|
|
112
|
+
if (Array.isArray(value)) return value.map(sortValue);
|
|
113
|
+
if (value !== null && typeof value === "object") {
|
|
114
|
+
const result = {};
|
|
115
|
+
for (const key of Object.keys(value).sort()) {
|
|
116
|
+
result[key] = sortValue(value[key]);
|
|
117
|
+
}
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/entry-store.ts
|
|
124
|
+
function digest(value) {
|
|
125
|
+
return createHash("sha256").update(value).digest("hex");
|
|
126
|
+
}
|
|
127
|
+
function artifactFile(key) {
|
|
128
|
+
if (/^[A-Za-z0-9._-]+$/.test(key)) return key;
|
|
129
|
+
return Buffer.from(key, "utf8").toString("base64url");
|
|
130
|
+
}
|
|
131
|
+
function validateKey(key) {
|
|
132
|
+
if (!/^[a-f0-9]{64}$/.test(key)) {
|
|
133
|
+
throw new DdcStoreError("ddc-entry-invalid", `invalid semantic key: ${key}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function errorCode(error) {
|
|
137
|
+
if (error !== null && typeof error === "object" && "code" in error) {
|
|
138
|
+
const code = error.code;
|
|
139
|
+
return typeof code === "string" ? code : void 0;
|
|
140
|
+
}
|
|
141
|
+
return void 0;
|
|
142
|
+
}
|
|
143
|
+
function sortedArtifacts(entry) {
|
|
144
|
+
return Object.entries(entry.artifacts).sort(([left], [right]) => left.localeCompare(right));
|
|
145
|
+
}
|
|
146
|
+
function ddcOutputDigest(entry) {
|
|
147
|
+
return `sha256:${digest(
|
|
148
|
+
canonicalDdcJson({
|
|
149
|
+
guid: entry.guid,
|
|
150
|
+
payload: entry.payload,
|
|
151
|
+
refs: entry.refs,
|
|
152
|
+
artifacts: Object.fromEntries(
|
|
153
|
+
sortedArtifacts(entry).map(([key, artifact]) => [
|
|
154
|
+
key,
|
|
155
|
+
{
|
|
156
|
+
mediaType: artifact.mediaType,
|
|
157
|
+
bytes: artifact.bytes
|
|
158
|
+
}
|
|
159
|
+
])
|
|
160
|
+
)
|
|
161
|
+
})
|
|
162
|
+
)}`;
|
|
163
|
+
}
|
|
164
|
+
function existingResult(existing, candidate, key) {
|
|
165
|
+
return canonicalDdcJson(existing) === canonicalDdcJson(candidate) ? { result: "existing", key } : { result: "conflict", key };
|
|
166
|
+
}
|
|
167
|
+
function entryShape(entry) {
|
|
168
|
+
return {
|
|
169
|
+
key: entry.key,
|
|
170
|
+
guid: entry.guid,
|
|
171
|
+
payload: entry.payload,
|
|
172
|
+
refs: entry.refs,
|
|
173
|
+
receipt: entry.receipt,
|
|
174
|
+
artifacts: Object.fromEntries(
|
|
175
|
+
sortedArtifacts(entry).map(([key, artifact]) => [
|
|
176
|
+
key,
|
|
177
|
+
{ mediaType: artifact.mediaType, byteLength: artifact.bytes.byteLength }
|
|
178
|
+
])
|
|
179
|
+
)
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function integrityFor(entry) {
|
|
183
|
+
const artifacts = Object.fromEntries(
|
|
184
|
+
sortedArtifacts(entry).map(([key, artifact]) => [
|
|
185
|
+
key,
|
|
186
|
+
{
|
|
187
|
+
mediaType: artifact.mediaType,
|
|
188
|
+
byteLength: artifact.bytes.byteLength,
|
|
189
|
+
digest: digest(artifact.bytes)
|
|
190
|
+
}
|
|
191
|
+
])
|
|
192
|
+
);
|
|
193
|
+
return {
|
|
194
|
+
payload: digest(canonicalDdcJson(entry.payload)),
|
|
195
|
+
refs: digest(canonicalDdcJson(entry.refs)),
|
|
196
|
+
receipt: digest(canonicalDdcJson(entry.receipt)),
|
|
197
|
+
artifacts,
|
|
198
|
+
entry: digest(canonicalDdcJson(entryShape(entry)))
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
async function readJson(path) {
|
|
202
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
203
|
+
}
|
|
204
|
+
var DdcEntryStore = class {
|
|
205
|
+
entries;
|
|
206
|
+
staging;
|
|
207
|
+
constructor(root) {
|
|
208
|
+
this.entries = join(root, "entries");
|
|
209
|
+
this.staging = join(root, "staging");
|
|
210
|
+
}
|
|
211
|
+
async stage(entry) {
|
|
212
|
+
validateKey(entry.key);
|
|
213
|
+
if (entry.receipt.key !== entry.key || entry.receipt.guid !== entry.guid) {
|
|
214
|
+
throw new DdcStoreError("ddc-entry-invalid", "receipt identity does not match entry");
|
|
215
|
+
}
|
|
216
|
+
if (entry.receipt.outputDigest !== ddcOutputDigest(entry)) {
|
|
217
|
+
throw new DdcStoreError("ddc-entry-invalid", "receipt output digest does not match entry");
|
|
218
|
+
}
|
|
219
|
+
const path = join(this.staging, `${entry.key}-${randomUUID()}`);
|
|
220
|
+
await mkdir(join(path, "artifacts"), { recursive: true });
|
|
221
|
+
await writeFile(join(path, "payload.json"), canonicalDdcJson(entry.payload));
|
|
222
|
+
await writeFile(join(path, "refs.json"), canonicalDdcJson(entry.refs));
|
|
223
|
+
await writeFile(join(path, "receipt.json"), canonicalDdcJson(entry.receipt));
|
|
224
|
+
const artifacts = {};
|
|
225
|
+
for (const [key, artifact] of sortedArtifacts(entry)) {
|
|
226
|
+
const file = artifactFile(key);
|
|
227
|
+
artifacts[key] = { mediaType: artifact.mediaType, file };
|
|
228
|
+
await writeFile(join(path, "artifacts", `${file}.bin`), artifact.bytes);
|
|
229
|
+
}
|
|
230
|
+
await writeFile(join(path, "artifacts.json"), canonicalDdcJson(artifacts));
|
|
231
|
+
await writeFile(join(path, "integrity.json"), canonicalDdcJson(integrityFor(entry)));
|
|
232
|
+
return { key: entry.key, path };
|
|
233
|
+
}
|
|
234
|
+
async publish(staged) {
|
|
235
|
+
validateKey(staged.key);
|
|
236
|
+
const candidate = await this.readDirectory(staged.path, true);
|
|
237
|
+
const target = join(this.entries, staged.key);
|
|
238
|
+
await mkdir(this.entries, { recursive: true });
|
|
239
|
+
try {
|
|
240
|
+
await stat(target);
|
|
241
|
+
const existing = await this.readDirectory(target, false);
|
|
242
|
+
await rm(staged.path, { recursive: true, force: true });
|
|
243
|
+
return existingResult(existing, candidate, staged.key);
|
|
244
|
+
} catch (error) {
|
|
245
|
+
if (error instanceof DdcStoreError) throw error;
|
|
246
|
+
if (errorCode(error) !== "ENOENT") throw error;
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
await rename(staged.path, target);
|
|
250
|
+
return { result: "published", key: staged.key };
|
|
251
|
+
} catch (error) {
|
|
252
|
+
if (!["EEXIST", "ENOTEMPTY", "EISDIR"].includes(errorCode(error) ?? "")) throw error;
|
|
253
|
+
const existing = await this.readDirectory(target, false);
|
|
254
|
+
await rm(staged.path, { recursive: true, force: true });
|
|
255
|
+
return existingResult(existing, candidate, staged.key);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async write(entry) {
|
|
259
|
+
const staged = await this.stage(entry);
|
|
260
|
+
return this.publish(staged);
|
|
261
|
+
}
|
|
262
|
+
async read(key) {
|
|
263
|
+
try {
|
|
264
|
+
validateKey(key);
|
|
265
|
+
return await this.readDirectory(join(this.entries, key), false);
|
|
266
|
+
} catch {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
async listKeys() {
|
|
271
|
+
const entries = await readdir(this.entries, { withFileTypes: true }).catch(() => []);
|
|
272
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
273
|
+
}
|
|
274
|
+
async remove(key) {
|
|
275
|
+
validateKey(key);
|
|
276
|
+
await rm(join(this.entries, key), { recursive: true, force: false });
|
|
277
|
+
}
|
|
278
|
+
/** Read with a machine-readable corruption result for recovery tooling. */
|
|
279
|
+
async readChecked(key) {
|
|
280
|
+
try {
|
|
281
|
+
validateKey(key);
|
|
282
|
+
try {
|
|
283
|
+
await stat(join(this.entries, key));
|
|
284
|
+
} catch (error) {
|
|
285
|
+
if (errorCode(error) === "ENOENT") return { ok: true, value: null };
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
return { ok: true, value: await this.readDirectory(join(this.entries, key), false) };
|
|
290
|
+
} catch (error) {
|
|
291
|
+
if (error instanceof DdcStoreError && error.code === "ddc-entry-incomplete") {
|
|
292
|
+
return { ok: true, value: null };
|
|
293
|
+
}
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
} catch (error) {
|
|
297
|
+
const normalized = error instanceof DdcStoreError ? error : new DdcStoreError("ddc-entry-invalid", "entry is unreadable");
|
|
298
|
+
return { ok: false, error: normalized };
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async readDirectory(path, incompleteIsError) {
|
|
302
|
+
try {
|
|
303
|
+
const payload = await readJson(join(path, "payload.json"));
|
|
304
|
+
const refs = await readJson(join(path, "refs.json"));
|
|
305
|
+
const receipt = await readJson(join(path, "receipt.json"));
|
|
306
|
+
const manifest = await readJson(join(path, "artifacts.json"));
|
|
307
|
+
const integrity = await readJson(join(path, "integrity.json"));
|
|
308
|
+
const artifacts = {};
|
|
309
|
+
for (const [key, descriptor] of Object.entries(manifest).sort(
|
|
310
|
+
([left], [right]) => left.localeCompare(right)
|
|
311
|
+
)) {
|
|
312
|
+
const bytes = new Uint8Array(
|
|
313
|
+
await readFile(join(path, "artifacts", `${descriptor.file}.bin`))
|
|
314
|
+
);
|
|
315
|
+
artifacts[key] = { mediaType: descriptor.mediaType, bytes };
|
|
316
|
+
}
|
|
317
|
+
const entry = { key: receipt.key, guid: receipt.guid, payload, refs, artifacts, receipt };
|
|
318
|
+
if (entry.receipt.outputDigest !== ddcOutputDigest(entry)) {
|
|
319
|
+
throw new DdcStoreError("ddc-entry-invalid", "receipt output digest does not match entry");
|
|
320
|
+
}
|
|
321
|
+
const actual = integrityFor(entry);
|
|
322
|
+
if (canonicalDdcJson(actual) !== canonicalDdcJson(integrity)) {
|
|
323
|
+
throw new DdcStoreError("ddc-entry-invalid", "entry integrity mismatch");
|
|
324
|
+
}
|
|
325
|
+
return entry;
|
|
326
|
+
} catch (error) {
|
|
327
|
+
if (incompleteIsError) {
|
|
328
|
+
if (error instanceof DdcStoreError) throw error;
|
|
329
|
+
throw new DdcStoreError("ddc-entry-incomplete", "staged entry is incomplete");
|
|
330
|
+
}
|
|
331
|
+
if (error instanceof DdcStoreError) throw error;
|
|
332
|
+
throw new DdcStoreError("ddc-entry-invalid", "entry is unreadable");
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
export { DdcEntryStore, DdcStoreError, ddcOutputDigest };
|
|
338
|
+
//# sourceMappingURL=entry-store.mjs.map
|
|
339
|
+
//# sourceMappingURL=entry-store.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/key.ts","../src/entry-store.ts"],"names":["createHash"],"mappings":"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","file":"entry-store.mjs","sourcesContent":["export const DDC_ERROR_CODES = [\n 'ddc-project-root-required',\n 'ddc-object-conflict',\n 'ddc-head-conflict',\n 'ddc-generation-conflict',\n 'ddc-scope-mismatch',\n 'ddc-lease-expired',\n] as const;\n\nexport type DdcContractErrorCode = (typeof DDC_ERROR_CODES)[number];\nexport type DdcEntryErrorCode = 'ddc-entry-incomplete' | 'ddc-entry-invalid' | 'ddc-entry-conflict';\nexport type DdcErrorCode = DdcContractErrorCode | DdcEntryErrorCode;\n\nexport type DdcErrorRootKind = 'build-cache' | 'project-ddc' | 'runtime';\n\nexport interface DdcRecoveryAction {\n readonly kind:\n | 'inspect'\n | 'allocate-generation'\n | 'retry'\n | 'cold-rebuild'\n | 'prune'\n | 'release-lease';\n readonly executable: boolean;\n readonly exactTarget?: string;\n readonly reason?: string;\n}\n\nexport interface DdcStoreErrorInit {\n readonly code: DdcErrorCode;\n readonly detail: string;\n readonly hint?: string;\n readonly expected?: unknown;\n readonly actual?: unknown;\n readonly owner?: string;\n readonly rootKind?: DdcErrorRootKind;\n readonly scope?: string;\n readonly generation?: number;\n readonly lease?: string;\n readonly revision?: number;\n readonly recoveryActions?: readonly DdcRecoveryAction[];\n readonly hostPath?: string;\n}\n\nexport interface DdcBrowserError {\n readonly code: DdcErrorCode;\n readonly hint: string;\n readonly expected: unknown;\n readonly actual?: unknown;\n readonly owner?: string;\n readonly rootKind?: DdcErrorRootKind;\n readonly scope?: string;\n readonly generation?: number;\n readonly lease?: string;\n readonly revision?: number;\n readonly recoveryActions: readonly Omit<DdcRecoveryAction, 'exactTarget'>[];\n}\n\nconst defaultHints: Record<DdcErrorCode, string> = {\n 'ddc-project-root-required': 'inject the canonical projectDdcRoot before serving or publishing',\n 'ddc-object-conflict': 'keep the verified object and reject the competing publication',\n 'ddc-head-conflict': 'inspect the current head and retry with a fresh revision',\n 'ddc-generation-conflict': 'allocate a new persistent generation; do not edit the counter',\n 'ddc-scope-mismatch': 'reinspect the canonical root and allocate a new scope',\n 'ddc-lease-expired': 'discard the stale commit and acquire a new lease',\n 'ddc-entry-incomplete': 'discard the partial entry and cold-cook from author authority',\n 'ddc-entry-invalid': 'preserve last-known-good, inspect the entry, then cold-cook',\n 'ddc-entry-conflict': 'keep the verified entry and reject the competing publication',\n};\n\nfunction defaultExpected(code: DdcErrorCode): unknown {\n if (code === 'ddc-project-root-required') return 'projectDdcRoot';\n if (code === 'ddc-entry-incomplete' || code === 'ddc-entry-invalid') {\n return 'a complete DDC entry whose receipt and integrity digests validate';\n }\n return 'a valid DDC owner, scope, generation, revision, and lease';\n}\n\nfunction defaultActions(code: DdcErrorCode): readonly DdcRecoveryAction[] {\n switch (code) {\n case 'ddc-project-root-required':\n case 'ddc-scope-mismatch':\n return [\n { kind: 'inspect', executable: true },\n { kind: 'retry', executable: true },\n ];\n case 'ddc-generation-conflict':\n return [{ kind: 'allocate-generation', executable: true }];\n case 'ddc-lease-expired':\n return [\n { kind: 'release-lease', executable: true },\n { kind: 'retry', executable: true },\n ];\n case 'ddc-object-conflict':\n case 'ddc-head-conflict':\n return [\n { kind: 'inspect', executable: true },\n { kind: 'retry', executable: true },\n ];\n case 'ddc-entry-incomplete':\n case 'ddc-entry-invalid':\n return [{ kind: 'cold-rebuild', executable: true }];\n case 'ddc-entry-conflict':\n return [{ kind: 'inspect', executable: true }];\n }\n}\n\nexport class DdcStoreError extends Error {\n public readonly code: DdcErrorCode;\n public readonly detail: string;\n public readonly hint: string;\n public readonly expected: unknown;\n public readonly actual: unknown;\n public readonly owner: string | undefined;\n public readonly rootKind: DdcErrorRootKind | undefined;\n public readonly scope: string | undefined;\n public readonly generation: number | undefined;\n public readonly lease: string | undefined;\n public readonly revision: number | undefined;\n public readonly recoveryActions: readonly DdcRecoveryAction[];\n public readonly hostPath: string | undefined;\n\n public constructor(code: DdcErrorCode, detail: string);\n public constructor(input: DdcStoreErrorInit);\n public constructor(inputOrCode: DdcStoreErrorInit | DdcErrorCode, legacyDetail?: string) {\n const input: DdcStoreErrorInit =\n typeof inputOrCode === 'string'\n ? { code: inputOrCode, detail: legacyDetail ?? inputOrCode }\n : inputOrCode;\n super(input.detail);\n this.name = 'DdcStoreError';\n this.code = input.code;\n this.detail = input.detail;\n this.hint = input.hint ?? defaultHints[input.code];\n this.expected = input.expected ?? defaultExpected(input.code);\n this.actual = input.actual;\n this.owner = input.owner;\n this.rootKind = input.rootKind;\n this.scope = input.scope;\n this.generation = input.generation;\n this.lease = input.lease;\n this.revision = input.revision;\n this.recoveryActions = input.recoveryActions ?? defaultActions(input.code);\n this.hostPath = input.hostPath;\n }\n\n public toBrowserProjection(): DdcBrowserError {\n return {\n code: this.code,\n hint: this.hint,\n expected: this.expected,\n ...(this.actual === undefined ? {} : { actual: this.actual }),\n ...(this.owner === undefined ? {} : { owner: this.owner }),\n ...(this.rootKind === undefined ? {} : { rootKind: this.rootKind }),\n ...(this.scope === undefined ? {} : { scope: this.scope }),\n ...(this.generation === undefined ? {} : { generation: this.generation }),\n ...(this.lease === undefined ? {} : { lease: this.lease }),\n ...(this.revision === undefined ? {} : { revision: this.revision }),\n recoveryActions: this.recoveryActions.map(\n ({ exactTarget: _exactTarget, ...action }) => action,\n ),\n };\n }\n}\n","import { createHash } from 'node:crypto';\n\nexport interface SemanticDdcInput {\n readonly schemaVersion: string;\n readonly importer: string;\n readonly codec: string;\n readonly settings: unknown;\n readonly sourceBytes: readonly Uint8Array[];\n readonly declaredGuids: readonly string[];\n readonly targetProfile: string;\n readonly producer: string;\n}\n\nexport function canonicalDdcJson(value: unknown): string {\n const sorted = sortValue(value);\n return JSON.stringify(sorted) ?? 'null';\n}\n\nfunction sortValue(value: unknown): unknown {\n if (value instanceof Uint8Array) {\n return { encoding: 'base64', bytes: Buffer.from(value).toString('base64') };\n }\n if (Array.isArray(value)) return value.map(sortValue);\n if (value !== null && typeof value === 'object') {\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(value as Record<string, unknown>).sort()) {\n result[key] = sortValue((value as Record<string, unknown>)[key]);\n }\n return result;\n }\n return value;\n}\n\nexport function semanticDdcKey(input: SemanticDdcInput): string {\n const semantic = {\n schemaVersion: input.schemaVersion,\n importer: input.importer,\n codec: input.codec,\n settings: input.settings,\n sourceBytes: input.sourceBytes,\n declaredGuids: [...input.declaredGuids].sort(),\n targetProfile: input.targetProfile,\n producer: input.producer,\n };\n return createHash('sha256').update(canonicalDdcJson(semantic)).digest('hex');\n}\n","import { createHash, randomUUID } from 'node:crypto';\nimport { mkdir, readdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { DdcStoreError } from './errors.js';\nimport { canonicalDdcJson } from './key.js';\n\nexport { DdcStoreError } from './errors.js';\n\nexport interface DdcArtifact {\n readonly mediaType: string;\n readonly bytes: Uint8Array;\n}\n\nexport interface DdcReceipt {\n readonly guid: string;\n readonly key: string;\n readonly producer: string;\n readonly inputFingerprint: string;\n readonly outputDigest: string;\n}\n\nexport interface DdcEntry {\n readonly key: string;\n readonly guid: string;\n readonly payload: unknown;\n readonly refs: readonly string[];\n readonly artifacts: Readonly<Record<string, DdcArtifact>>;\n readonly receipt: DdcReceipt;\n}\n\nexport interface StagedDdcEntry {\n readonly key: string;\n readonly path: string;\n}\n\nexport interface PublishDdcEntryResult {\n readonly result: 'published' | 'existing' | 'conflict';\n readonly key: string;\n}\n\ninterface ArtifactIntegrity {\n readonly mediaType: string;\n readonly byteLength: number;\n readonly digest: string;\n}\n\ninterface EntryIntegrity {\n readonly payload: string;\n readonly refs: string;\n readonly receipt: string;\n readonly artifacts: Readonly<Record<string, ArtifactIntegrity>>;\n readonly entry: string;\n}\n\nfunction digest(value: string | Uint8Array): string {\n return createHash('sha256').update(value).digest('hex');\n}\n\nfunction artifactFile(key: string): string {\n if (/^[A-Za-z0-9._-]+$/.test(key)) return key;\n return Buffer.from(key, 'utf8').toString('base64url');\n}\n\nfunction validateKey(key: string): void {\n if (!/^[a-f0-9]{64}$/.test(key)) {\n throw new DdcStoreError('ddc-entry-invalid', `invalid semantic key: ${key}`);\n }\n}\n\nfunction errorCode(error: unknown): string | undefined {\n if (error !== null && typeof error === 'object' && 'code' in error) {\n const code = (error as { readonly code?: unknown }).code;\n return typeof code === 'string' ? code : undefined;\n }\n return undefined;\n}\n\nfunction sortedArtifacts(entry: DdcEntry): readonly [string, DdcArtifact][] {\n return Object.entries(entry.artifacts).sort(([left], [right]) => left.localeCompare(right));\n}\n\n/** The one output identity shared by receipt validation and DDC publication. */\nexport function ddcOutputDigest(\n entry: Pick<DdcEntry, 'guid' | 'payload' | 'refs' | 'artifacts'>,\n): string {\n return `sha256:${digest(\n canonicalDdcJson({\n guid: entry.guid,\n payload: entry.payload,\n refs: entry.refs,\n artifacts: Object.fromEntries(\n sortedArtifacts(entry as DdcEntry).map(([key, artifact]) => [\n key,\n {\n mediaType: artifact.mediaType,\n bytes: artifact.bytes,\n },\n ]),\n ),\n }),\n )}`;\n}\n\nfunction existingResult(\n existing: DdcEntry,\n candidate: DdcEntry,\n key: string,\n): PublishDdcEntryResult {\n return canonicalDdcJson(existing) === canonicalDdcJson(candidate)\n ? { result: 'existing', key }\n : { result: 'conflict', key };\n}\n\nfunction entryShape(entry: DdcEntry): Record<string, unknown> {\n return {\n key: entry.key,\n guid: entry.guid,\n payload: entry.payload,\n refs: entry.refs,\n receipt: entry.receipt,\n artifacts: Object.fromEntries(\n sortedArtifacts(entry).map(([key, artifact]) => [\n key,\n { mediaType: artifact.mediaType, byteLength: artifact.bytes.byteLength },\n ]),\n ),\n };\n}\n\nfunction integrityFor(entry: DdcEntry): EntryIntegrity {\n const artifacts = Object.fromEntries(\n sortedArtifacts(entry).map(([key, artifact]) => [\n key,\n {\n mediaType: artifact.mediaType,\n byteLength: artifact.bytes.byteLength,\n digest: digest(artifact.bytes),\n },\n ]),\n );\n return {\n payload: digest(canonicalDdcJson(entry.payload)),\n refs: digest(canonicalDdcJson(entry.refs)),\n receipt: digest(canonicalDdcJson(entry.receipt)),\n artifacts,\n entry: digest(canonicalDdcJson(entryShape(entry))),\n };\n}\n\nasync function readJson<T>(path: string): Promise<T> {\n return JSON.parse(await readFile(path, 'utf8')) as T;\n}\n\nexport class DdcEntryStore {\n private readonly entries: string;\n private readonly staging: string;\n\n public constructor(root: string) {\n this.entries = join(root, 'entries');\n this.staging = join(root, 'staging');\n }\n\n public async stage(entry: DdcEntry): Promise<StagedDdcEntry> {\n validateKey(entry.key);\n if (entry.receipt.key !== entry.key || entry.receipt.guid !== entry.guid) {\n throw new DdcStoreError('ddc-entry-invalid', 'receipt identity does not match entry');\n }\n if (entry.receipt.outputDigest !== ddcOutputDigest(entry)) {\n throw new DdcStoreError('ddc-entry-invalid', 'receipt output digest does not match entry');\n }\n const path = join(this.staging, `${entry.key}-${randomUUID()}`);\n await mkdir(join(path, 'artifacts'), { recursive: true });\n await writeFile(join(path, 'payload.json'), canonicalDdcJson(entry.payload));\n await writeFile(join(path, 'refs.json'), canonicalDdcJson(entry.refs));\n await writeFile(join(path, 'receipt.json'), canonicalDdcJson(entry.receipt));\n const artifacts: Record<string, { mediaType: string; file: string }> = {};\n for (const [key, artifact] of sortedArtifacts(entry)) {\n const file = artifactFile(key);\n artifacts[key] = { mediaType: artifact.mediaType, file };\n await writeFile(join(path, 'artifacts', `${file}.bin`), artifact.bytes);\n }\n await writeFile(join(path, 'artifacts.json'), canonicalDdcJson(artifacts));\n await writeFile(join(path, 'integrity.json'), canonicalDdcJson(integrityFor(entry)));\n return { key: entry.key, path };\n }\n\n public async publish(staged: StagedDdcEntry): Promise<PublishDdcEntryResult> {\n validateKey(staged.key);\n const candidate = await this.readDirectory(staged.path, true);\n const target = join(this.entries, staged.key);\n await mkdir(this.entries, { recursive: true });\n try {\n await stat(target);\n const existing = await this.readDirectory(target, false);\n await rm(staged.path, { recursive: true, force: true });\n return existingResult(existing, candidate, staged.key);\n } catch (error) {\n if (error instanceof DdcStoreError) throw error;\n if (errorCode(error) !== 'ENOENT') throw error;\n }\n try {\n await rename(staged.path, target);\n return { result: 'published', key: staged.key };\n } catch (error) {\n if (!['EEXIST', 'ENOTEMPTY', 'EISDIR'].includes(errorCode(error) ?? '')) throw error;\n const existing = await this.readDirectory(target, false);\n await rm(staged.path, { recursive: true, force: true });\n return existingResult(existing, candidate, staged.key);\n }\n }\n\n public async write(entry: DdcEntry): Promise<PublishDdcEntryResult> {\n const staged = await this.stage(entry);\n return this.publish(staged);\n }\n\n public async read(key: string): Promise<DdcEntry | null> {\n try {\n validateKey(key);\n return await this.readDirectory(join(this.entries, key), false);\n } catch {\n return null;\n }\n }\n\n public async listKeys(): Promise<readonly string[]> {\n const entries = await readdir(this.entries, { withFileTypes: true }).catch(() => []);\n return entries\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name)\n .sort();\n }\n\n public async remove(key: string): Promise<void> {\n validateKey(key);\n await rm(join(this.entries, key), { recursive: true, force: false });\n }\n\n /** Read with a machine-readable corruption result for recovery tooling. */\n public async readChecked(\n key: string,\n ): Promise<\n | { readonly ok: true; readonly value: DdcEntry | null }\n | { readonly ok: false; readonly error: DdcStoreError }\n > {\n try {\n validateKey(key);\n try {\n await stat(join(this.entries, key));\n } catch (error) {\n if (errorCode(error) === 'ENOENT') return { ok: true, value: null };\n throw error;\n }\n try {\n return { ok: true, value: await this.readDirectory(join(this.entries, key), false) };\n } catch (error) {\n if (error instanceof DdcStoreError && error.code === 'ddc-entry-incomplete') {\n return { ok: true, value: null };\n }\n throw error;\n }\n } catch (error) {\n const normalized =\n error instanceof DdcStoreError\n ? error\n : new DdcStoreError('ddc-entry-invalid', 'entry is unreadable');\n return { ok: false, error: normalized };\n }\n }\n\n private async readDirectory(path: string, incompleteIsError: boolean): Promise<DdcEntry> {\n try {\n const payload = await readJson<unknown>(join(path, 'payload.json'));\n const refs = await readJson<readonly string[]>(join(path, 'refs.json'));\n const receipt = await readJson<DdcReceipt>(join(path, 'receipt.json'));\n const manifest = await readJson<\n Readonly<Record<string, { mediaType: string; file: string }>>\n >(join(path, 'artifacts.json'));\n const integrity = await readJson<EntryIntegrity>(join(path, 'integrity.json'));\n const artifacts: Record<string, DdcArtifact> = {};\n for (const [key, descriptor] of Object.entries(manifest).sort(([left], [right]) =>\n left.localeCompare(right),\n )) {\n const bytes = new Uint8Array(\n await readFile(join(path, 'artifacts', `${descriptor.file}.bin`)),\n );\n artifacts[key] = { mediaType: descriptor.mediaType, bytes };\n }\n const entry = { key: receipt.key, guid: receipt.guid, payload, refs, artifacts, receipt };\n if (entry.receipt.outputDigest !== ddcOutputDigest(entry)) {\n throw new DdcStoreError('ddc-entry-invalid', 'receipt output digest does not match entry');\n }\n const actual = integrityFor(entry);\n if (canonicalDdcJson(actual) !== canonicalDdcJson(integrity)) {\n throw new DdcStoreError('ddc-entry-invalid', 'entry integrity mismatch');\n }\n return entry;\n } catch (error) {\n if (incompleteIsError) {\n if (error instanceof DdcStoreError) throw error;\n throw new DdcStoreError('ddc-entry-incomplete', 'staged entry is incomplete');\n }\n if (error instanceof DdcStoreError) throw error;\n throw new DdcStoreError('ddc-entry-invalid', 'entry is unreadable');\n }\n }\n}\n"]}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export declare const DDC_ERROR_CODES: readonly ["ddc-project-root-required", "ddc-object-conflict", "ddc-head-conflict", "ddc-generation-conflict", "ddc-scope-mismatch", "ddc-lease-expired"];
|
|
2
|
+
export type DdcContractErrorCode = (typeof DDC_ERROR_CODES)[number];
|
|
3
|
+
export type DdcEntryErrorCode = 'ddc-entry-incomplete' | 'ddc-entry-invalid' | 'ddc-entry-conflict';
|
|
4
|
+
export type DdcErrorCode = DdcContractErrorCode | DdcEntryErrorCode;
|
|
5
|
+
export type DdcErrorRootKind = 'build-cache' | 'project-ddc' | 'runtime';
|
|
6
|
+
export interface DdcRecoveryAction {
|
|
7
|
+
readonly kind: 'inspect' | 'allocate-generation' | 'retry' | 'cold-rebuild' | 'prune' | 'release-lease';
|
|
8
|
+
readonly executable: boolean;
|
|
9
|
+
readonly exactTarget?: string;
|
|
10
|
+
readonly reason?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface DdcStoreErrorInit {
|
|
13
|
+
readonly code: DdcErrorCode;
|
|
14
|
+
readonly detail: string;
|
|
15
|
+
readonly hint?: string;
|
|
16
|
+
readonly expected?: unknown;
|
|
17
|
+
readonly actual?: unknown;
|
|
18
|
+
readonly owner?: string;
|
|
19
|
+
readonly rootKind?: DdcErrorRootKind;
|
|
20
|
+
readonly scope?: string;
|
|
21
|
+
readonly generation?: number;
|
|
22
|
+
readonly lease?: string;
|
|
23
|
+
readonly revision?: number;
|
|
24
|
+
readonly recoveryActions?: readonly DdcRecoveryAction[];
|
|
25
|
+
readonly hostPath?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface DdcBrowserError {
|
|
28
|
+
readonly code: DdcErrorCode;
|
|
29
|
+
readonly hint: string;
|
|
30
|
+
readonly expected: unknown;
|
|
31
|
+
readonly actual?: unknown;
|
|
32
|
+
readonly owner?: string;
|
|
33
|
+
readonly rootKind?: DdcErrorRootKind;
|
|
34
|
+
readonly scope?: string;
|
|
35
|
+
readonly generation?: number;
|
|
36
|
+
readonly lease?: string;
|
|
37
|
+
readonly revision?: number;
|
|
38
|
+
readonly recoveryActions: readonly Omit<DdcRecoveryAction, 'exactTarget'>[];
|
|
39
|
+
}
|
|
40
|
+
export declare class DdcStoreError extends Error {
|
|
41
|
+
readonly code: DdcErrorCode;
|
|
42
|
+
readonly detail: string;
|
|
43
|
+
readonly hint: string;
|
|
44
|
+
readonly expected: unknown;
|
|
45
|
+
readonly actual: unknown;
|
|
46
|
+
readonly owner: string | undefined;
|
|
47
|
+
readonly rootKind: DdcErrorRootKind | undefined;
|
|
48
|
+
readonly scope: string | undefined;
|
|
49
|
+
readonly generation: number | undefined;
|
|
50
|
+
readonly lease: string | undefined;
|
|
51
|
+
readonly revision: number | undefined;
|
|
52
|
+
readonly recoveryActions: readonly DdcRecoveryAction[];
|
|
53
|
+
readonly hostPath: string | undefined;
|
|
54
|
+
constructor(code: DdcErrorCode, detail: string);
|
|
55
|
+
constructor(input: DdcStoreErrorInit);
|
|
56
|
+
toBrowserProjection(): DdcBrowserError;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.mjs
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var DDC_ERROR_CODES = [
|
|
3
|
+
"ddc-project-root-required",
|
|
4
|
+
"ddc-object-conflict",
|
|
5
|
+
"ddc-head-conflict",
|
|
6
|
+
"ddc-generation-conflict",
|
|
7
|
+
"ddc-scope-mismatch",
|
|
8
|
+
"ddc-lease-expired"
|
|
9
|
+
];
|
|
10
|
+
var defaultHints = {
|
|
11
|
+
"ddc-project-root-required": "inject the canonical projectDdcRoot before serving or publishing",
|
|
12
|
+
"ddc-object-conflict": "keep the verified object and reject the competing publication",
|
|
13
|
+
"ddc-head-conflict": "inspect the current head and retry with a fresh revision",
|
|
14
|
+
"ddc-generation-conflict": "allocate a new persistent generation; do not edit the counter",
|
|
15
|
+
"ddc-scope-mismatch": "reinspect the canonical root and allocate a new scope",
|
|
16
|
+
"ddc-lease-expired": "discard the stale commit and acquire a new lease",
|
|
17
|
+
"ddc-entry-incomplete": "discard the partial entry and cold-cook from author authority",
|
|
18
|
+
"ddc-entry-invalid": "preserve last-known-good, inspect the entry, then cold-cook",
|
|
19
|
+
"ddc-entry-conflict": "keep the verified entry and reject the competing publication"
|
|
20
|
+
};
|
|
21
|
+
function defaultExpected(code) {
|
|
22
|
+
if (code === "ddc-project-root-required") return "projectDdcRoot";
|
|
23
|
+
if (code === "ddc-entry-incomplete" || code === "ddc-entry-invalid") {
|
|
24
|
+
return "a complete DDC entry whose receipt and integrity digests validate";
|
|
25
|
+
}
|
|
26
|
+
return "a valid DDC owner, scope, generation, revision, and lease";
|
|
27
|
+
}
|
|
28
|
+
function defaultActions(code) {
|
|
29
|
+
switch (code) {
|
|
30
|
+
case "ddc-project-root-required":
|
|
31
|
+
case "ddc-scope-mismatch":
|
|
32
|
+
return [
|
|
33
|
+
{ kind: "inspect", executable: true },
|
|
34
|
+
{ kind: "retry", executable: true }
|
|
35
|
+
];
|
|
36
|
+
case "ddc-generation-conflict":
|
|
37
|
+
return [{ kind: "allocate-generation", executable: true }];
|
|
38
|
+
case "ddc-lease-expired":
|
|
39
|
+
return [
|
|
40
|
+
{ kind: "release-lease", executable: true },
|
|
41
|
+
{ kind: "retry", executable: true }
|
|
42
|
+
];
|
|
43
|
+
case "ddc-object-conflict":
|
|
44
|
+
case "ddc-head-conflict":
|
|
45
|
+
return [
|
|
46
|
+
{ kind: "inspect", executable: true },
|
|
47
|
+
{ kind: "retry", executable: true }
|
|
48
|
+
];
|
|
49
|
+
case "ddc-entry-incomplete":
|
|
50
|
+
case "ddc-entry-invalid":
|
|
51
|
+
return [{ kind: "cold-rebuild", executable: true }];
|
|
52
|
+
case "ddc-entry-conflict":
|
|
53
|
+
return [{ kind: "inspect", executable: true }];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
var DdcStoreError = class extends Error {
|
|
57
|
+
code;
|
|
58
|
+
detail;
|
|
59
|
+
hint;
|
|
60
|
+
expected;
|
|
61
|
+
actual;
|
|
62
|
+
owner;
|
|
63
|
+
rootKind;
|
|
64
|
+
scope;
|
|
65
|
+
generation;
|
|
66
|
+
lease;
|
|
67
|
+
revision;
|
|
68
|
+
recoveryActions;
|
|
69
|
+
hostPath;
|
|
70
|
+
constructor(inputOrCode, legacyDetail) {
|
|
71
|
+
const input = typeof inputOrCode === "string" ? { code: inputOrCode, detail: legacyDetail ?? inputOrCode } : inputOrCode;
|
|
72
|
+
super(input.detail);
|
|
73
|
+
this.name = "DdcStoreError";
|
|
74
|
+
this.code = input.code;
|
|
75
|
+
this.detail = input.detail;
|
|
76
|
+
this.hint = input.hint ?? defaultHints[input.code];
|
|
77
|
+
this.expected = input.expected ?? defaultExpected(input.code);
|
|
78
|
+
this.actual = input.actual;
|
|
79
|
+
this.owner = input.owner;
|
|
80
|
+
this.rootKind = input.rootKind;
|
|
81
|
+
this.scope = input.scope;
|
|
82
|
+
this.generation = input.generation;
|
|
83
|
+
this.lease = input.lease;
|
|
84
|
+
this.revision = input.revision;
|
|
85
|
+
this.recoveryActions = input.recoveryActions ?? defaultActions(input.code);
|
|
86
|
+
this.hostPath = input.hostPath;
|
|
87
|
+
}
|
|
88
|
+
toBrowserProjection() {
|
|
89
|
+
return {
|
|
90
|
+
code: this.code,
|
|
91
|
+
hint: this.hint,
|
|
92
|
+
expected: this.expected,
|
|
93
|
+
...this.actual === void 0 ? {} : { actual: this.actual },
|
|
94
|
+
...this.owner === void 0 ? {} : { owner: this.owner },
|
|
95
|
+
...this.rootKind === void 0 ? {} : { rootKind: this.rootKind },
|
|
96
|
+
...this.scope === void 0 ? {} : { scope: this.scope },
|
|
97
|
+
...this.generation === void 0 ? {} : { generation: this.generation },
|
|
98
|
+
...this.lease === void 0 ? {} : { lease: this.lease },
|
|
99
|
+
...this.revision === void 0 ? {} : { revision: this.revision },
|
|
100
|
+
recoveryActions: this.recoveryActions.map(
|
|
101
|
+
({ exactTarget: _exactTarget, ...action }) => action
|
|
102
|
+
)
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export { DDC_ERROR_CODES, DdcStoreError };
|
|
108
|
+
//# sourceMappingURL=errors.mjs.map
|
|
109
|
+
//# sourceMappingURL=errors.mjs.map
|