@nowcrew/daemon 0.6.19 → 0.6.21

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.
Files changed (40) hide show
  1. package/dist/atomic-no-replace-rename.js +91 -0
  2. package/dist/completion-retransmitter-logging.js +16 -0
  3. package/dist/completion-retransmitter.js +39 -4
  4. package/dist/control-plane-url.js +4 -2
  5. package/dist/directory-projection-publication.js +105 -0
  6. package/dist/directory-projection.js +20 -4
  7. package/dist/execution-journal.js +40 -4
  8. package/dist/execution-posix-stop-proof.js +82 -0
  9. package/dist/execution-runner.js +68 -8
  10. package/dist/local-executor.js +67 -52
  11. package/dist/machine-info.js +8 -5
  12. package/dist/project-skills/capability.js +109 -0
  13. package/dist/project-skills/controller-convergence.js +57 -0
  14. package/dist/project-skills/controller.js +80 -24
  15. package/dist/project-skills/initialized-reconciler.js +4 -4
  16. package/dist/project-skills/projection-state-domain.js +19 -2
  17. package/dist/project-skills/projection-state-store.js +3 -2
  18. package/dist/project-skills/projection-state-transaction.js +5 -1
  19. package/dist/project-skills/projection-state.js +1 -1
  20. package/dist/project-skills/reconciler.js +275 -102
  21. package/dist/project-skills/runtime-launch.js +102 -0
  22. package/dist/project-skills/runtime-root-bootstrap.js +47 -0
  23. package/dist/project-skills/runtime-root-domain.js +268 -0
  24. package/dist/project-skills/runtime-root-gc.js +293 -0
  25. package/dist/project-skills/runtime-root-lease-artifact.js +46 -0
  26. package/dist/project-skills/runtime-root-leases.js +487 -0
  27. package/dist/project-skills/runtime-root-source-identity.js +60 -0
  28. package/dist/project-skills/runtime-root-startup.js +49 -0
  29. package/dist/project-skills/runtime-root-state-artifact-domain.js +143 -0
  30. package/dist/project-skills/runtime-root-state-index.js +356 -0
  31. package/dist/project-skills/runtime-root-store.js +722 -0
  32. package/dist/project-skills/serve-capability.js +28 -0
  33. package/dist/project-skills/serve-startup.js +22 -0
  34. package/dist/project-skills/types.js +1 -0
  35. package/dist/runtimes/codex-home-migration-cli.js +26 -0
  36. package/dist/runtimes/codex-home-migration.js +112 -0
  37. package/dist/runtimes/codex-home.js +200 -17
  38. package/dist/serve.js +60 -79
  39. package/dist/supervised-runtime.js +1 -5
  40. package/package.json +2 -1
@@ -0,0 +1,143 @@
1
+ import { createHash } from "node:crypto";
2
+ import { lstat, open, opendir } from "node:fs/promises";
3
+ import { ProjectSkillRuntimeStoreError, parseProjectSkillRuntimeStoreState, } from "./runtime-root-domain.js";
4
+ export const RUNTIME_ROOT_STATE_MAX_BYTES = 4 * 1024 * 1024;
5
+ const MAX_CONSISTENT_READ_ATTEMPTS = 6;
6
+ const fail = (code) => { throw new ProjectSkillRuntimeStoreError(code); };
7
+ const isMissing = (error) => error.code === "ENOENT";
8
+ const ownedByCurrentUser = (uid) => process.getuid === undefined || uid === BigInt(process.getuid());
9
+ const fileIdentity = (info) => Object.freeze({
10
+ dev: info.dev,
11
+ ino: info.ino,
12
+ uid: info.uid,
13
+ size: info.size,
14
+ mtimeNs: info.mtimeNs,
15
+ ctimeNs: info.ctimeNs,
16
+ birthtimeNs: info.birthtimeNs,
17
+ });
18
+ const sameFileIdentity = (left, right, allowCtimeChange = false) => left.dev === right.dev
19
+ && left.ino === right.ino
20
+ && left.uid === right.uid
21
+ && left.size === right.size
22
+ && left.mtimeNs === right.mtimeNs
23
+ && left.birthtimeNs === right.birthtimeNs
24
+ && (allowCtimeChange || left.ctimeNs === right.ctimeNs);
25
+ export const digestRuntimeRootStateRaw = (raw) => `sha256:${createHash("sha256").update(raw, "utf8").digest("hex")}`;
26
+ export const serializeRuntimeRootStateFileIdentity = (identity) => ({
27
+ dev: identity.dev.toString(),
28
+ ino: identity.ino.toString(),
29
+ uid: identity.uid.toString(),
30
+ size: identity.size.toString(),
31
+ mtimeNs: identity.mtimeNs.toString(),
32
+ ctimeNs: identity.ctimeNs.toString(),
33
+ birthtimeNs: identity.birthtimeNs.toString(),
34
+ });
35
+ export const snapshotMatchesRuntimeRootStateBinding = (snapshot, binding, allowCtimeChange) => snapshot.raw === binding.content
36
+ && digestRuntimeRootStateRaw(snapshot.raw) === binding.digest
37
+ && snapshot.identity.dev.toString() === binding.identity.dev
38
+ && snapshot.identity.ino.toString() === binding.identity.ino
39
+ && snapshot.identity.uid.toString() === binding.identity.uid
40
+ && snapshot.identity.size.toString() === binding.identity.size
41
+ && snapshot.identity.mtimeNs.toString() === binding.identity.mtimeNs
42
+ && snapshot.identity.birthtimeNs.toString() === binding.identity.birthtimeNs
43
+ && (allowCtimeChange || snapshot.identity.ctimeNs.toString() === binding.identity.ctimeNs);
44
+ export const readRuntimeRootStateStableFile = async (filePath, maximumBytes) => {
45
+ for (let attempt = 0; attempt < MAX_CONSISTENT_READ_ATTEMPTS; attempt += 1) {
46
+ let before;
47
+ try {
48
+ before = await lstat(filePath, { bigint: true });
49
+ }
50
+ catch (error) {
51
+ if (isMissing(error))
52
+ return null;
53
+ return fail("skill_projection_runtime_root_unmanaged");
54
+ }
55
+ if (!before.isFile() || before.isSymbolicLink() || !ownedByCurrentUser(before.uid)
56
+ || before.size > BigInt(maximumBytes)) {
57
+ return fail("skill_projection_runtime_root_unmanaged");
58
+ }
59
+ let handle = null;
60
+ try {
61
+ handle = await open(filePath, "r");
62
+ const opened = await handle.stat({ bigint: true });
63
+ if (!opened.isFile() || opened.isSymbolicLink() || !ownedByCurrentUser(opened.uid)
64
+ || opened.size > BigInt(maximumBytes)
65
+ || !sameFileIdentity(fileIdentity(before), fileIdentity(opened)))
66
+ continue;
67
+ const raw = await handle.readFile("utf8");
68
+ const after = await handle.stat({ bigint: true });
69
+ let pathAfter;
70
+ try {
71
+ pathAfter = await lstat(filePath, { bigint: true });
72
+ }
73
+ catch (error) {
74
+ if (isMissing(error))
75
+ continue;
76
+ return fail("skill_projection_runtime_root_unmanaged");
77
+ }
78
+ if (!pathAfter.isFile() || pathAfter.isSymbolicLink()
79
+ || Buffer.byteLength(raw, "utf8") !== Number(after.size)
80
+ || !sameFileIdentity(fileIdentity(opened), fileIdentity(after))
81
+ || !sameFileIdentity(fileIdentity(after), fileIdentity(pathAfter)))
82
+ continue;
83
+ return Object.freeze({ identity: fileIdentity(after), raw });
84
+ }
85
+ catch (error) {
86
+ if (error instanceof ProjectSkillRuntimeStoreError)
87
+ throw error;
88
+ if (isMissing(error))
89
+ continue;
90
+ return fail("skill_projection_runtime_root_unmanaged");
91
+ }
92
+ finally {
93
+ await handle?.close().catch(() => undefined);
94
+ }
95
+ }
96
+ return fail("skill_projection_runtime_root_unmanaged");
97
+ };
98
+ const parseStateRaw = (raw) => {
99
+ let candidate;
100
+ try {
101
+ candidate = JSON.parse(raw);
102
+ }
103
+ catch {
104
+ return fail("skill_projection_snapshot_corrupt");
105
+ }
106
+ return parseProjectSkillRuntimeStoreState(candidate);
107
+ };
108
+ export const loadRuntimeRootState = async (statePath) => {
109
+ const snapshot = await readRuntimeRootStateStableFile(statePath, RUNTIME_ROOT_STATE_MAX_BYTES);
110
+ if (snapshot === null)
111
+ return null;
112
+ return Object.freeze({ state: parseStateRaw(snapshot.raw), snapshot });
113
+ };
114
+ export const boundedRuntimeRootStateNames = async (directoryPath, maximumEntries) => {
115
+ let directory;
116
+ try {
117
+ directory = await opendir(directoryPath);
118
+ }
119
+ catch {
120
+ return fail("skill_projection_runtime_root_unmanaged");
121
+ }
122
+ const names = [];
123
+ try {
124
+ for (;;) {
125
+ const entry = await directory.read();
126
+ if (entry === null)
127
+ break;
128
+ names.push(entry.name);
129
+ if (names.length > maximumEntries) {
130
+ return fail("skill_projection_runtime_store_limit_exceeded");
131
+ }
132
+ }
133
+ }
134
+ catch (error) {
135
+ if (error instanceof ProjectSkillRuntimeStoreError)
136
+ throw error;
137
+ return fail("skill_projection_runtime_root_unmanaged");
138
+ }
139
+ finally {
140
+ await directory.close().catch(() => undefined);
141
+ }
142
+ return Object.freeze(names.sort());
143
+ };
@@ -0,0 +1,356 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { open, unlink } from "node:fs/promises";
3
+ import { z } from "zod";
4
+ import { ProjectSkillRuntimeStoreError, createProjectSkillRuntimeStoreState, isCanonicalProjectSkillRuntimeRootId, parseProjectSkillRuntimeStoreState, } from "./runtime-root-domain.js";
5
+ import { RUNTIME_ROOT_STATE_MAX_BYTES, boundedRuntimeRootStateNames, digestRuntimeRootStateRaw, loadRuntimeRootState, readRuntimeRootStateStableFile, serializeRuntimeRootStateFileIdentity, snapshotMatchesRuntimeRootStateBinding, } from "./runtime-root-state-artifact-domain.js";
6
+ const MANAGED_BY = "nowcrew-project-skill-runtime-state-index";
7
+ const MAX_INDEX_ENTRIES = 64;
8
+ const MAX_INDEX_RECORD_BYTES = (RUNTIME_ROOT_STATE_MAX_BYTES * 2) + (128 * 1024);
9
+ const PRIVATE_NAME = /^\.state-index-private-([0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})\.tmp$/u;
10
+ const FINAL_NAME = /^(legacy-import|high-water)-([0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})\.json$/u;
11
+ const DECIMAL = /^(?:0|[1-9][0-9]*)$/u;
12
+ const DIGEST = /^sha256:[a-f0-9]{64}$/u;
13
+ // The store's single-daemon-writer invariant is enforced by its coordinator. This process-local
14
+ // queue also closes the capacity-check/publication gap for callers that share this owner directly.
15
+ // A separate process remains outside that invariant; any resulting over-cap inventory fails closed.
16
+ const indexCreationTails = new Map();
17
+ const fail = (code) => {
18
+ throw new ProjectSkillRuntimeStoreError(code);
19
+ };
20
+ const ownedByCurrentUser = (uid) => process.getuid === undefined || uid === BigInt(process.getuid());
21
+ const SerializedIdentitySchema = z.object({
22
+ dev: z.string().regex(DECIMAL),
23
+ ino: z.string().regex(DECIMAL),
24
+ uid: z.string().regex(DECIMAL),
25
+ birthtimeNs: z.string().regex(DECIMAL),
26
+ }).strict();
27
+ const LegacyFileIdentitySchema = z.object({
28
+ dev: z.string().regex(DECIMAL),
29
+ ino: z.string().regex(DECIMAL),
30
+ uid: z.string().regex(DECIMAL),
31
+ size: z.string().regex(DECIMAL),
32
+ mtimeNs: z.string().regex(DECIMAL),
33
+ ctimeNs: z.string().regex(DECIMAL),
34
+ birthtimeNs: z.string().regex(DECIMAL),
35
+ }).strict();
36
+ const BaseRecordSchema = z.object({
37
+ managedBy: z.literal(MANAGED_BY),
38
+ version: z.literal(1),
39
+ nonce: z.string().refine(isCanonicalProjectSkillRuntimeRootId),
40
+ nextSequence: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
41
+ artifactIdentity: SerializedIdentitySchema,
42
+ });
43
+ const LegacyImportRecordSchema = BaseRecordSchema.extend({
44
+ kind: z.literal("legacy-import"),
45
+ state: z.object({
46
+ content: z.string(),
47
+ digest: z.string().regex(DIGEST),
48
+ identity: LegacyFileIdentitySchema,
49
+ }).strict(),
50
+ }).strict();
51
+ const HighWaterRecordSchema = BaseRecordSchema.extend({
52
+ kind: z.literal("high-water"),
53
+ }).strict();
54
+ const IndexRecordSchema = z.discriminatedUnion("kind", [
55
+ LegacyImportRecordSchema,
56
+ HighWaterRecordSchema,
57
+ ]);
58
+ const sameRecord = (left, right) => JSON.stringify(left) === JSON.stringify(right);
59
+ const serializeIndexIdentity = (identity) => Object.freeze({
60
+ dev: identity.dev.toString(),
61
+ ino: identity.ino.toString(),
62
+ uid: identity.uid.toString(),
63
+ birthtimeNs: identity.birthtimeNs.toString(),
64
+ });
65
+ const identityMatches = (actual, expected) => actual.dev.toString() === expected.dev
66
+ && actual.ino.toString() === expected.ino
67
+ && actual.uid.toString() === expected.uid
68
+ && actual.birthtimeNs.toString() === expected.birthtimeNs;
69
+ const parseIndexRecord = async (filePath, expectedNonce, expectedKind) => {
70
+ const snapshot = await readRuntimeRootStateStableFile(filePath, MAX_INDEX_RECORD_BYTES);
71
+ if (snapshot === null)
72
+ return fail("skill_projection_runtime_root_unmanaged");
73
+ let candidate;
74
+ try {
75
+ candidate = JSON.parse(snapshot.raw);
76
+ }
77
+ catch {
78
+ return fail("skill_projection_runtime_root_unmanaged");
79
+ }
80
+ const parsed = IndexRecordSchema.safeParse(candidate);
81
+ if (!parsed.success
82
+ || parsed.data.nonce !== expectedNonce
83
+ || (expectedKind !== undefined && parsed.data.kind !== expectedKind)) {
84
+ return fail("skill_projection_runtime_root_unmanaged");
85
+ }
86
+ if (!identityMatches(snapshot.identity, parsed.data.artifactIdentity)) {
87
+ return fail("skill_projection_runtime_root_unmanaged");
88
+ }
89
+ return Object.freeze(parsed.data);
90
+ };
91
+ const finalName = (record) => `${record.kind}-${record.nonce}.json`;
92
+ const withIndexCreationLock = async (key, operation) => {
93
+ const previous = indexCreationTails.get(key) ?? Promise.resolve();
94
+ let release;
95
+ const turn = new Promise((resolve) => { release = resolve; });
96
+ const tail = previous.then(() => turn);
97
+ indexCreationTails.set(key, tail);
98
+ await previous;
99
+ try {
100
+ return await operation();
101
+ }
102
+ finally {
103
+ release?.();
104
+ if (indexCreationTails.get(key) === tail)
105
+ indexCreationTails.delete(key);
106
+ }
107
+ };
108
+ const activateStagedRecord = async (context, stagingPath, nonce, invokeHook) => {
109
+ const staged = await parseIndexRecord(stagingPath, nonce);
110
+ const destination = context.path.join(context.indexDirectory, finalName(staged));
111
+ const hookContext = Object.freeze({
112
+ stagingPath,
113
+ finalPath: destination,
114
+ kind: staged.kind,
115
+ nonce,
116
+ });
117
+ if (invokeHook)
118
+ await context.hooks?.afterStagingDurableBeforeRename?.(hookContext);
119
+ const result = await context.atomicRename(stagingPath, destination);
120
+ if (result !== "published")
121
+ return fail("skill_projection_runtime_root_unmanaged");
122
+ if (invokeHook)
123
+ await context.hooks?.afterRenamedBeforeParentSync?.(hookContext);
124
+ await context.syncDirectory(context.indexDirectory);
125
+ if (invokeHook)
126
+ await context.hooks?.afterParentSyncedBeforeRevalidate?.(hookContext);
127
+ return parseIndexRecord(destination, nonce, staged.kind);
128
+ };
129
+ const writeIndexRecord = async (context, input) => {
130
+ const nonce = (context.randomId ?? randomUUID)();
131
+ if (!isCanonicalProjectSkillRuntimeRootId(nonce)) {
132
+ return fail("skill_projection_snapshot_corrupt");
133
+ }
134
+ const stagingPath = context.path.join(context.indexDirectory, `.state-index-private-${nonce}.tmp`);
135
+ let handle = null;
136
+ try {
137
+ handle = await open(stagingPath, "wx", 0o600);
138
+ const info = await handle.stat({ bigint: true });
139
+ if (!info.isFile() || info.isSymbolicLink() || !ownedByCurrentUser(info.uid)) {
140
+ return fail("skill_projection_runtime_root_unmanaged");
141
+ }
142
+ const record = Object.freeze({
143
+ managedBy: MANAGED_BY,
144
+ version: 1,
145
+ ...input,
146
+ nonce,
147
+ artifactIdentity: serializeIndexIdentity({
148
+ dev: info.dev,
149
+ ino: info.ino,
150
+ uid: info.uid,
151
+ birthtimeNs: info.birthtimeNs,
152
+ }),
153
+ });
154
+ const raw = `${JSON.stringify(record)}\n`;
155
+ if (Buffer.byteLength(raw, "utf8") > MAX_INDEX_RECORD_BYTES) {
156
+ return fail("skill_projection_runtime_store_limit_exceeded");
157
+ }
158
+ await handle.writeFile(raw, "utf8");
159
+ await handle.sync();
160
+ await handle.close();
161
+ handle = null;
162
+ await context.syncDirectory(context.indexDirectory);
163
+ await parseIndexRecord(stagingPath, nonce, input.kind);
164
+ return activateStagedRecord(context, stagingPath, nonce, true);
165
+ }
166
+ finally {
167
+ await handle?.close().catch(() => undefined);
168
+ }
169
+ };
170
+ const scanIndex = async (context) => {
171
+ const initialNames = await boundedRuntimeRootStateNames(context.indexDirectory, MAX_INDEX_ENTRIES);
172
+ for (const name of initialNames) {
173
+ const privateMatch = PRIVATE_NAME.exec(name);
174
+ if (privateMatch === null)
175
+ continue;
176
+ await activateStagedRecord(context, context.path.join(context.indexDirectory, name), privateMatch[1], false);
177
+ }
178
+ const names = await boundedRuntimeRootStateNames(context.indexDirectory, MAX_INDEX_ENTRIES);
179
+ const records = [];
180
+ for (const name of names) {
181
+ const match = FINAL_NAME.exec(name);
182
+ if (match === null)
183
+ return fail("skill_projection_runtime_root_unmanaged");
184
+ records.push(await parseIndexRecord(context.path.join(context.indexDirectory, name), match[2], match[1]));
185
+ }
186
+ return Object.freeze(records);
187
+ };
188
+ const createIndexRecord = async (context, input) => withIndexCreationLock(context.path.resolve(context.indexDirectory), async () => {
189
+ const inventory = await scanIndex(context);
190
+ if (inventory.length >= MAX_INDEX_ENTRIES) {
191
+ return fail("skill_projection_runtime_store_limit_exceeded");
192
+ }
193
+ return writeIndexRecord(context, input);
194
+ });
195
+ const exactLegacyBinding = (snapshot) => Object.freeze({
196
+ content: snapshot.raw,
197
+ digest: digestRuntimeRootStateRaw(snapshot.raw),
198
+ identity: Object.freeze(serializeRuntimeRootStateFileIdentity(snapshot.identity)),
199
+ });
200
+ const parseEmbeddedLegacyState = (receipt) => {
201
+ if (Buffer.byteLength(receipt.state.content, "utf8") > RUNTIME_ROOT_STATE_MAX_BYTES
202
+ || receipt.state.identity.size !== String(Buffer.byteLength(receipt.state.content, "utf8"))
203
+ || digestRuntimeRootStateRaw(receipt.state.content) !== receipt.state.digest) {
204
+ return fail("skill_projection_runtime_root_unmanaged");
205
+ }
206
+ let candidate;
207
+ try {
208
+ candidate = JSON.parse(receipt.state.content);
209
+ }
210
+ catch {
211
+ return fail("skill_projection_runtime_root_unmanaged");
212
+ }
213
+ let state;
214
+ try {
215
+ state = parseProjectSkillRuntimeStoreState(candidate);
216
+ }
217
+ catch {
218
+ return fail("skill_projection_runtime_root_unmanaged");
219
+ }
220
+ if (state.nextSequence !== receipt.nextSequence) {
221
+ return fail("skill_projection_runtime_root_unmanaged");
222
+ }
223
+ return state;
224
+ };
225
+ const validateLegacyRoots = (legacy, roots) => {
226
+ for (const record of legacy.roots) {
227
+ const actual = roots.find(({ rootId }) => rootId === record.rootId);
228
+ if (actual === undefined || !sameRecord(actual, record)) {
229
+ return fail("skill_projection_runtime_root_unmanaged");
230
+ }
231
+ }
232
+ };
233
+ const validateImportedRootSemantics = (imported, roots) => {
234
+ for (const root of roots) {
235
+ const importedById = imported.roots.find(({ rootId }) => rootId === root.rootId);
236
+ const importedBySequence = imported.roots.find(({ publishedSequence }) => publishedSequence === root.publishedSequence);
237
+ if ((importedById !== undefined && !sameRecord(importedById, root))
238
+ || (importedBySequence !== undefined && !sameRecord(importedBySequence, root))
239
+ || (importedById === undefined
240
+ && importedBySequence === undefined
241
+ && root.publishedSequence < imported.nextSequence)) {
242
+ return fail("skill_projection_runtime_root_unmanaged");
243
+ }
244
+ }
245
+ };
246
+ const validateLegacyEvidence = async (context, receipt) => {
247
+ const current = await readRuntimeRootStateStableFile(context.statePath, RUNTIME_ROOT_STATE_MAX_BYTES);
248
+ if (current === null || !snapshotMatchesRuntimeRootStateBinding(current, receipt.state, false)) {
249
+ return fail("skill_projection_runtime_root_unmanaged");
250
+ }
251
+ };
252
+ export async function reconstructRuntimeRootState(context, roots) {
253
+ let records = await scanIndex(context);
254
+ let legacyReceipts = records.filter((record) => record.kind === "legacy-import");
255
+ if (legacyReceipts.length > 1)
256
+ return fail("skill_projection_runtime_root_unmanaged");
257
+ const existingReceipt = legacyReceipts[0];
258
+ let importedState;
259
+ if (existingReceipt !== undefined) {
260
+ importedState = parseEmbeddedLegacyState(existingReceipt);
261
+ validateImportedRootSemantics(importedState, roots);
262
+ }
263
+ const legacyFile = await loadRuntimeRootState(context.statePath);
264
+ if (legacyReceipts.length === 0 && legacyFile !== null) {
265
+ validateLegacyRoots(legacyFile.state, roots);
266
+ await context.hooks?.afterLegacyStateValidatedBeforeImport?.(Object.freeze({ statePath: context.statePath }));
267
+ const created = await createIndexRecord(context, {
268
+ kind: "legacy-import",
269
+ nextSequence: legacyFile.state.nextSequence,
270
+ state: exactLegacyBinding(legacyFile.snapshot),
271
+ });
272
+ if (created.kind !== "legacy-import")
273
+ return fail("skill_projection_runtime_root_unmanaged");
274
+ records = Object.freeze([...records, created]);
275
+ legacyReceipts = [created];
276
+ }
277
+ const receipt = legacyReceipts[0];
278
+ if (receipt !== undefined) {
279
+ if (importedState === undefined) {
280
+ importedState = parseEmbeddedLegacyState(receipt);
281
+ validateImportedRootSemantics(importedState, roots);
282
+ }
283
+ await validateLegacyEvidence(context, receipt);
284
+ }
285
+ const rootIds = new Set();
286
+ const sequences = new Set();
287
+ let nextSequence = receipt?.nextSequence ?? 0;
288
+ for (const root of roots) {
289
+ if (rootIds.has(root.rootId) || sequences.has(root.publishedSequence)) {
290
+ return fail("skill_projection_runtime_root_unmanaged");
291
+ }
292
+ rootIds.add(root.rootId);
293
+ sequences.add(root.publishedSequence);
294
+ nextSequence = Math.max(nextSequence, root.publishedSequence + 1);
295
+ }
296
+ const highWaterSequences = new Set();
297
+ for (const record of records) {
298
+ if (record.kind !== "high-water")
299
+ continue;
300
+ if (highWaterSequences.has(record.nextSequence)) {
301
+ return fail("skill_projection_runtime_root_unmanaged");
302
+ }
303
+ highWaterSequences.add(record.nextSequence);
304
+ nextSequence = Math.max(nextSequence, record.nextSequence);
305
+ }
306
+ if (!Number.isSafeInteger(nextSequence)) {
307
+ return fail("skill_projection_runtime_store_limit_exceeded");
308
+ }
309
+ return createProjectSkillRuntimeStoreState({ nextSequence, roots });
310
+ }
311
+ export async function checkpointRuntimeRootNextSequence(context, state, options = {}) {
312
+ const records = await scanIndex(context);
313
+ const durable = records
314
+ .filter((record) => record.kind === "high-water")
315
+ .some((record) => record.nextSequence >= state.nextSequence);
316
+ if (!durable) {
317
+ try {
318
+ await createIndexRecord(context, { kind: "high-water", nextSequence: state.nextSequence });
319
+ }
320
+ catch (error) {
321
+ if (!(options.allowCapacityCompaction === true
322
+ && error instanceof ProjectSkillRuntimeStoreError
323
+ && error.code === "skill_projection_runtime_store_limit_exceeded"))
324
+ throw error;
325
+ const records = await scanIndex(context);
326
+ const maximum = Math.max(...records.map((record) => record.nextSequence));
327
+ const dominated = records
328
+ .filter((record) => record.kind === "high-water" && record.nextSequence < maximum)
329
+ .sort((left, right) => left.nextSequence - right.nextSequence)[0];
330
+ if (dominated === undefined)
331
+ throw error;
332
+ const source = context.path.join(context.indexDirectory, finalName(dominated));
333
+ // Reuse the recoverable private-artifact grammar so a crash after claim restores the
334
+ // dominated record on the next scan instead of poisoning the state-index inventory.
335
+ const claim = context.path.join(context.indexDirectory, `.state-index-private-${dominated.nonce}.tmp`);
336
+ const claimed = await context.atomicRename(source, claim);
337
+ if (claimed !== "published")
338
+ throw error;
339
+ try {
340
+ await parseIndexRecord(claim, dominated.nonce, "high-water");
341
+ await unlink(claim);
342
+ await context.syncDirectory(context.indexDirectory);
343
+ }
344
+ catch (claimError) {
345
+ throw claimError;
346
+ }
347
+ await context.syncDirectory(context.indexDirectory);
348
+ await createIndexRecord(context, { kind: "high-water", nextSequence: state.nextSequence });
349
+ }
350
+ }
351
+ const reconstructed = await reconstructRuntimeRootState(context, state.roots);
352
+ if (reconstructed.nextSequence < state.nextSequence) {
353
+ return fail("skill_projection_runtime_root_unmanaged");
354
+ }
355
+ return reconstructed.nextSequence;
356
+ }