@fitlab-ai/agent-infra 0.9.6 → 0.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/internal-cli.js +10 -0
- package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +47 -5
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +245 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +398 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +23 -6
- package/dist/lib/decide.js +68 -14
- package/dist/lib/defaults.json +10 -2
- package/dist/lib/git/workflow.js +30 -3
- package/dist/lib/init.js +1 -1
- package/dist/lib/internal/codex-lifecycle.js +69 -1
- package/dist/lib/internal/codex-sandbox-controller.js +106 -0
- package/dist/lib/internal/release-workflow.js +1 -3
- package/dist/lib/internal/task-activity.js +57 -7
- package/dist/lib/internal/task-event.js +57 -4
- package/dist/lib/internal/task-ledger.js +59 -5
- package/dist/lib/internal/task-lifecycle.js +59 -8
- package/dist/lib/internal/task-orchestration.js +48 -8
- package/dist/lib/internal/task-override.js +131 -0
- package/dist/lib/internal/task-review.js +64 -6
- package/dist/lib/internal/task-verify.js +65 -3
- package/dist/lib/internal/task-warning.js +57 -4
- package/dist/lib/platform/context.js +6 -4
- package/dist/lib/platform/github-client.js +55 -2
- package/dist/lib/platform/pr-summary.js +5 -3
- package/dist/lib/process-data/checkpoint.js +319 -0
- package/dist/lib/process-data/index.js +128 -60
- package/dist/lib/process-data/materialize.js +84 -0
- package/dist/lib/process-data/normalize.js +34 -1
- package/dist/lib/process-data/reconcile.js +55 -1
- package/dist/lib/process-data/sources.js +422 -1
- package/dist/lib/process-data/store.js +341 -11
- package/dist/lib/runtime/agent-runtime.js +56 -0
- package/dist/lib/sandbox/commands/create.js +394 -264
- package/dist/lib/sandbox/commands/rm.js +64 -15
- package/dist/lib/sandbox/commands/start.js +4 -3
- package/dist/lib/sandbox/control/container-identity.js +64 -0
- package/dist/lib/sandbox/control/executor.js +23 -9
- package/dist/lib/sandbox/control/lifecycle.js +590 -61
- package/dist/lib/sandbox/control/protocol.js +7 -0
- package/dist/lib/sandbox/control/server.js +202 -26
- package/dist/lib/sandbox/control/state.js +51 -25
- package/dist/lib/sandbox/control/timing.js +8 -0
- package/dist/lib/sandbox/engine.js +14 -7
- package/dist/lib/sandbox/mounts.js +7 -0
- package/dist/lib/sandbox/recovery.js +135 -50
- package/dist/lib/sandbox/workspace-view.js +59 -20
- package/dist/lib/server/daemon.js +1 -1
- package/dist/lib/server/process-control.js +11 -1
- package/dist/lib/server/process-state.js +107 -20
- package/dist/lib/task/activity-intent.js +4 -1
- package/dist/lib/task/artifact-lifecycle.js +82 -11
- package/dist/lib/task/artifact-receipts.js +128 -0
- package/dist/lib/task/codex-orchestration.js +81 -6
- package/dist/lib/task/commands/validate.js +1 -1
- package/dist/lib/task/create.js +10 -2
- package/dist/lib/task/decision-intents.js +4 -2
- package/dist/lib/task/delegation-receipts.js +120 -17
- package/dist/lib/task/events.js +148 -7
- package/dist/lib/task/guard-override.js +31 -0
- package/dist/lib/task/human-override.js +1240 -0
- package/dist/lib/task/ledger-intents.js +6 -3
- package/dist/lib/task/ledger.js +13 -1
- package/dist/lib/task/lifecycle.js +69 -14
- package/dist/lib/task/orchestration.js +293 -18
- package/dist/lib/task/review-finalization.js +3 -2
- package/dist/lib/task/sections.js +5 -1
- package/dist/lib/task/task-execution-lock.js +5 -4
- package/dist/lib/task/verification-engine.js +80 -22
- package/dist/lib/task/verification.js +13 -1
- package/dist/lib/task/workflow-warning-intents.js +4 -2
- package/dist/lib/task/write.js +2 -1
- package/dist/lifecycle-build-manifest.json +6 -0
- package/lib/agent-clients/adapters/claude-code.ts +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +62 -4
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +322 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +480 -0
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +35 -5
- package/lib/decide.ts +59 -14
- package/lib/defaults.json +10 -2
- package/lib/git/workflow.ts +25 -2
- package/lib/init.ts +1 -1
- package/lib/internal/codex-lifecycle.ts +68 -1
- package/lib/internal/codex-sandbox-controller.ts +109 -0
- package/lib/internal/release-workflow.ts +1 -3
- package/lib/internal/task-activity.ts +46 -6
- package/lib/internal/task-event.ts +53 -4
- package/lib/internal/task-ledger.ts +48 -4
- package/lib/internal/task-lifecycle.ts +56 -12
- package/lib/internal/task-orchestration.ts +43 -8
- package/lib/internal/task-override.ts +138 -0
- package/lib/internal/task-review.ts +51 -6
- package/lib/internal/task-verify.ts +52 -3
- package/lib/internal/task-warning.ts +44 -3
- package/lib/platform/context.ts +8 -5
- package/lib/platform/github-client.ts +61 -3
- package/lib/platform/pr-summary.ts +10 -3
- package/lib/process-data/checkpoint.ts +335 -0
- package/lib/process-data/index.ts +112 -60
- package/lib/process-data/materialize.ts +83 -0
- package/lib/process-data/normalize.ts +29 -1
- package/lib/process-data/reconcile.ts +64 -2
- package/lib/process-data/sources.ts +449 -2
- package/lib/process-data/store.ts +304 -11
- package/lib/process-data/types.ts +124 -2
- package/lib/runtime/agent-runtime.ts +76 -0
- package/lib/sandbox/commands/create.ts +139 -6
- package/lib/sandbox/commands/rm.ts +64 -14
- package/lib/sandbox/commands/start.ts +4 -3
- package/lib/sandbox/config.ts +2 -1
- package/lib/sandbox/control/container-identity.ts +93 -0
- package/lib/sandbox/control/executor.ts +24 -12
- package/lib/sandbox/control/lifecycle.ts +685 -67
- package/lib/sandbox/control/protocol.ts +35 -6
- package/lib/sandbox/control/server.ts +184 -24
- package/lib/sandbox/control/state.ts +67 -33
- package/lib/sandbox/control/timing.ts +12 -0
- package/lib/sandbox/engine.ts +20 -9
- package/lib/sandbox/mounts.ts +8 -0
- package/lib/sandbox/recovery.ts +159 -49
- package/lib/sandbox/workspace-view.ts +73 -22
- package/lib/server/daemon.ts +1 -1
- package/lib/server/process-control.ts +13 -3
- package/lib/server/process-state.ts +129 -27
- package/lib/task/activity-intent.ts +4 -2
- package/lib/task/artifact-lifecycle.ts +73 -10
- package/lib/task/artifact-receipts.ts +161 -0
- package/lib/task/codex-orchestration.ts +91 -5
- package/lib/task/commands/validate.ts +1 -1
- package/lib/task/create.ts +11 -2
- package/lib/task/decision-intents.ts +4 -2
- package/lib/task/delegation-receipts.ts +205 -25
- package/lib/task/events.ts +132 -6
- package/lib/task/guard-override.ts +50 -0
- package/lib/task/human-override.ts +1503 -0
- package/lib/task/ledger-intents.ts +6 -3
- package/lib/task/ledger.ts +16 -1
- package/lib/task/lifecycle.ts +71 -14
- package/lib/task/orchestration.ts +443 -22
- package/lib/task/review-finalization.ts +6 -2
- package/lib/task/sections.ts +6 -2
- package/lib/task/task-execution-lock.ts +11 -9
- package/lib/task/verification-engine.ts +93 -32
- package/lib/task/verification.ts +15 -1
- package/lib/task/workflow-warning-intents.ts +4 -2
- package/lib/task/write.ts +4 -1
- package/package.json +1 -1
- package/templates/.agents/README.en.md +7 -0
- package/templates/.agents/README.zh-CN.md +5 -0
- package/templates/.agents/hooks/lifecycle-delegation.js +33 -4
- package/templates/.agents/rules/lifecycle-orchestration.en.md +10 -3
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +10 -3
- package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
- package/templates/.agents/rules/review-handshake.en.md +3 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +6 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -3
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +25 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +25 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +5 -1
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +5 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
- package/templates/.agents/workspace/README.en.md +0 -26
- package/templates/.agents/workspace/README.zh-CN.md +0 -26
|
@@ -9,6 +9,9 @@ import type {
|
|
|
9
9
|
RepairAction,
|
|
10
10
|
RestCollectionEvidence,
|
|
11
11
|
SnapshotManifest,
|
|
12
|
+
SnapshotManifestV1,
|
|
13
|
+
SnapshotManifestV2,
|
|
14
|
+
SnapshotOperations,
|
|
12
15
|
StoredObjectEvidence
|
|
13
16
|
} from './types.ts';
|
|
14
17
|
|
|
@@ -28,6 +31,18 @@ type PublishInput = {
|
|
|
28
31
|
repairs: RepairAction[];
|
|
29
32
|
};
|
|
30
33
|
|
|
34
|
+
type PublishV2Input = PublishInput & {
|
|
35
|
+
snapshotKind: 'base' | 'delta';
|
|
36
|
+
parentSnapshotId: string | null;
|
|
37
|
+
checkpointBefore: string | null;
|
|
38
|
+
watermark: string;
|
|
39
|
+
window: SnapshotManifestV2['window'];
|
|
40
|
+
observation: SnapshotManifestV2['observation'];
|
|
41
|
+
coverage: SnapshotManifestV2['coverage'];
|
|
42
|
+
reconciliation: 'incremental' | 'full';
|
|
43
|
+
operations: SnapshotOperations;
|
|
44
|
+
};
|
|
45
|
+
|
|
31
46
|
type VerificationResult = {
|
|
32
47
|
ok: boolean;
|
|
33
48
|
snapshotId: string;
|
|
@@ -82,12 +97,32 @@ function canonicalJsonBytes(value: JsonValue | unknown): Buffer {
|
|
|
82
97
|
}
|
|
83
98
|
|
|
84
99
|
function ensurePrivateDirectory(directory: string): void {
|
|
85
|
-
|
|
86
|
-
|
|
100
|
+
const missing: string[] = [];
|
|
101
|
+
let current = path.resolve(directory);
|
|
102
|
+
while (!fs.existsSync(current)) {
|
|
103
|
+
missing.push(current);
|
|
104
|
+
const parent = path.dirname(current);
|
|
105
|
+
if (parent === current) throw new Error(`cannot create directory tree: ${directory}`);
|
|
106
|
+
current = parent;
|
|
107
|
+
}
|
|
108
|
+
for (const target of missing.reverse()) {
|
|
109
|
+
fs.mkdirSync(target, { mode: 0o700 });
|
|
110
|
+
if (process.platform !== 'win32') fs.chmodSync(target, 0o700);
|
|
111
|
+
fsyncDirectory(path.dirname(target));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function fsyncDirectory(directory: string): void {
|
|
116
|
+
if (process.platform === 'win32') return;
|
|
117
|
+
const fd = fs.openSync(directory, 'r');
|
|
118
|
+
try { fs.fsyncSync(fd); } finally { fs.closeSync(fd); }
|
|
87
119
|
}
|
|
88
120
|
|
|
89
121
|
function writeExclusive(filePath: string, bytes: Buffer): boolean {
|
|
90
|
-
|
|
122
|
+
const directory = path.dirname(filePath);
|
|
123
|
+
const directoryExisted = fs.existsSync(directory);
|
|
124
|
+
ensurePrivateDirectory(directory);
|
|
125
|
+
if (!directoryExisted) fsyncDirectory(path.dirname(directory));
|
|
91
126
|
try {
|
|
92
127
|
const fd = fs.openSync(filePath, 'wx', 0o600);
|
|
93
128
|
try {
|
|
@@ -96,6 +131,7 @@ function writeExclusive(filePath: string, bytes: Buffer): boolean {
|
|
|
96
131
|
} finally {
|
|
97
132
|
fs.closeSync(fd);
|
|
98
133
|
}
|
|
134
|
+
fsyncDirectory(directory);
|
|
99
135
|
return true;
|
|
100
136
|
} catch (error) {
|
|
101
137
|
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
|
|
@@ -133,11 +169,17 @@ function jsonLine(value: JsonValue | unknown): string {
|
|
|
133
169
|
}
|
|
134
170
|
|
|
135
171
|
function writePrivateFile(filePath: string, content: string | Buffer): void {
|
|
136
|
-
fs.
|
|
172
|
+
const fd = fs.openSync(filePath, 'wx', 0o600);
|
|
173
|
+
try {
|
|
174
|
+
fs.writeFileSync(fd, content);
|
|
175
|
+
fs.fsyncSync(fd);
|
|
176
|
+
} finally {
|
|
177
|
+
fs.closeSync(fd);
|
|
178
|
+
}
|
|
137
179
|
if (process.platform !== 'win32') fs.chmodSync(filePath, 0o600);
|
|
138
180
|
}
|
|
139
181
|
|
|
140
|
-
function manifestDigest(manifest:
|
|
182
|
+
function manifestDigest(manifest: Record<string, unknown>): string {
|
|
141
183
|
return sha256(canonicalJsonBytes(manifest));
|
|
142
184
|
}
|
|
143
185
|
|
|
@@ -163,7 +205,7 @@ function publishSnapshot(root: string, input: PublishInput): { snapshotId: strin
|
|
|
163
205
|
const identityDigest = sha256(canonicalJsonBytes(identity));
|
|
164
206
|
const stamp = input.observedTo.replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z');
|
|
165
207
|
const snapshotId = `${stamp}-${identityDigest.slice(0, 12)}`;
|
|
166
|
-
const partialManifest: Omit<
|
|
208
|
+
const partialManifest: Omit<SnapshotManifestV1, 'manifestSha256'> = {
|
|
167
209
|
schema: 'raw-manifest/v1',
|
|
168
210
|
snapshotId,
|
|
169
211
|
...identity,
|
|
@@ -200,9 +242,79 @@ function publishSnapshot(root: string, input: PublishInput): { snapshotId: strin
|
|
|
200
242
|
const existing = fs.readFileSync(path.join(destination, 'manifest.json'), 'utf8');
|
|
201
243
|
if (existing !== jsonLine(manifest)) throw new Error(`snapshot collision: ${snapshotId}`);
|
|
202
244
|
fs.rmSync(staging, { recursive: true, force: true });
|
|
245
|
+
fsyncDirectory(stagingRoot);
|
|
203
246
|
return { snapshotId, path: destination, created: false };
|
|
204
247
|
}
|
|
248
|
+
fsyncDirectory(staging);
|
|
205
249
|
fs.renameSync(staging, destination);
|
|
250
|
+
fsyncDirectory(stagingRoot);
|
|
251
|
+
fsyncDirectory(path.dirname(destination));
|
|
252
|
+
return { snapshotId, path: destination, created: true };
|
|
253
|
+
} catch (error) {
|
|
254
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function publishSnapshotV2(root: string, input: PublishV2Input): { snapshotId: string; path: string; created: boolean } {
|
|
260
|
+
ensurePrivateDirectory(root);
|
|
261
|
+
ensurePrivateDirectory(path.join(root, 'repairs'));
|
|
262
|
+
const date = new Date(input.observedTo);
|
|
263
|
+
if (!Number.isFinite(date.getTime())) throw new Error('observedTo must be an ISO timestamp');
|
|
264
|
+
const identity = {
|
|
265
|
+
scope: 'github' as const,
|
|
266
|
+
repository: input.repository,
|
|
267
|
+
observedFrom: input.observedFrom,
|
|
268
|
+
observedTo: input.observedTo,
|
|
269
|
+
objects: input.objects,
|
|
270
|
+
endpoints: input.endpoints,
|
|
271
|
+
excerptsEnabled: false,
|
|
272
|
+
dispositionCounts: dispositionCounts(input.objects),
|
|
273
|
+
recordCount: input.records.length,
|
|
274
|
+
findingCount: input.quality.length,
|
|
275
|
+
repairCount: input.repairs.length,
|
|
276
|
+
snapshotKind: input.snapshotKind,
|
|
277
|
+
parentSnapshotId: input.parentSnapshotId,
|
|
278
|
+
checkpointBefore: input.checkpointBefore,
|
|
279
|
+
watermark: input.watermark,
|
|
280
|
+
window: input.window,
|
|
281
|
+
observation: input.observation,
|
|
282
|
+
coverage: input.coverage,
|
|
283
|
+
reconciliation: input.reconciliation,
|
|
284
|
+
operations: input.operations
|
|
285
|
+
};
|
|
286
|
+
const snapshotId = `${input.watermark.replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z')}-${sha256(canonicalJsonBytes(identity)).slice(0, 12)}`;
|
|
287
|
+
const partialManifest = {
|
|
288
|
+
schema: 'raw-manifest/v2' as const,
|
|
289
|
+
snapshotId,
|
|
290
|
+
...identity,
|
|
291
|
+
observedFrom: input.observedFrom,
|
|
292
|
+
observedTo: input.observedTo,
|
|
293
|
+
privacyPolicyVersion: 'process-data-privacy/v1' as const
|
|
294
|
+
};
|
|
295
|
+
const manifest: SnapshotManifestV2 = { ...partialManifest, manifestSha256: manifestDigest(partialManifest) };
|
|
296
|
+
const stagingRoot = path.join(root, '.staging');
|
|
297
|
+
ensurePrivateDirectory(stagingRoot);
|
|
298
|
+
const staging = fs.mkdtempSync(path.join(stagingRoot, `${snapshotId}-`));
|
|
299
|
+
if (process.platform !== 'win32') fs.chmodSync(staging, 0o700);
|
|
300
|
+
try {
|
|
301
|
+
writePrivateFile(path.join(staging, 'manifest.json'), jsonLine(manifest));
|
|
302
|
+
writePrivateFile(path.join(staging, 'normalized.jsonl'), input.records.map((record) => jsonLine({ schema: 'normalized/v2', ...record })).join(''));
|
|
303
|
+
writePrivateFile(path.join(staging, 'quality.json'), jsonLine({ schema: 'quality/v1', findings: input.quality }));
|
|
304
|
+
writePrivateFile(path.join(staging, 'repair-manifest.jsonl'), input.repairs.map((repair) => jsonLine({ schema: 'repair/v1', ...repair })).join(''));
|
|
305
|
+
const destination = path.join(root, 'snapshots', String(date.getUTCFullYear()), String(date.getUTCMonth() + 1).padStart(2, '0'), String(date.getUTCDate()).padStart(2, '0'), snapshotId);
|
|
306
|
+
ensurePrivateDirectory(path.dirname(destination));
|
|
307
|
+
if (fs.existsSync(destination)) {
|
|
308
|
+
const existing = fs.readFileSync(path.join(destination, 'manifest.json'), 'utf8');
|
|
309
|
+
if (existing !== jsonLine(manifest)) throw new Error(`snapshot collision: ${snapshotId}`);
|
|
310
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
311
|
+
fsyncDirectory(stagingRoot);
|
|
312
|
+
return { snapshotId, path: destination, created: false };
|
|
313
|
+
}
|
|
314
|
+
fsyncDirectory(staging);
|
|
315
|
+
fs.renameSync(staging, destination);
|
|
316
|
+
fsyncDirectory(stagingRoot);
|
|
317
|
+
fsyncDirectory(path.dirname(destination));
|
|
206
318
|
return { snapshotId, path: destination, created: true };
|
|
207
319
|
} catch (error) {
|
|
208
320
|
fs.rmSync(staging, { recursive: true, force: true });
|
|
@@ -226,7 +338,181 @@ function findSnapshot(root: string, snapshotId: string): string | null {
|
|
|
226
338
|
return null;
|
|
227
339
|
}
|
|
228
340
|
|
|
229
|
-
function
|
|
341
|
+
function verifyV2Snapshot(root: string, snapshotId: string, snapshotPath: string, manifest: SnapshotManifestV2, seen: Set<string>): VerificationResult {
|
|
342
|
+
const errors: string[] = [];
|
|
343
|
+
if (seen.has(snapshotId)) return { ok: false, snapshotId, snapshotPath, errors: ['snapshot lineage cycle detected'], manifest };
|
|
344
|
+
seen.add(snapshotId);
|
|
345
|
+
if (manifest.snapshotId !== snapshotId) errors.push('snapshot identity mismatch');
|
|
346
|
+
if (manifest.scope !== 'github') errors.push('v2 snapshot scope must be github');
|
|
347
|
+
if (manifest.snapshotKind === 'base' && manifest.parentSnapshotId !== null) errors.push('base snapshot must not have a parent');
|
|
348
|
+
if (manifest.snapshotKind === 'delta' && !manifest.parentSnapshotId) errors.push('delta snapshot must have a parent');
|
|
349
|
+
if (manifest.checkpointBefore !== manifest.parentSnapshotId) errors.push('checkpoint and parent lineage mismatch');
|
|
350
|
+
if (manifest.parentSnapshotId) {
|
|
351
|
+
const parentPath = findSnapshot(root, manifest.parentSnapshotId);
|
|
352
|
+
if (!parentPath) errors.push('parent snapshot is missing');
|
|
353
|
+
else {
|
|
354
|
+
const parentVerification = verifySnapshot(root, manifest.parentSnapshotId, seen);
|
|
355
|
+
if (!parentVerification.ok || !parentVerification.manifest) {
|
|
356
|
+
errors.push(`parent snapshot is invalid: ${parentVerification.errors.join('; ')}`);
|
|
357
|
+
} else if (parentVerification.manifest.schema !== 'raw-manifest/v2') {
|
|
358
|
+
errors.push('v2 snapshot parent is not v2');
|
|
359
|
+
} else {
|
|
360
|
+
const parent = parentVerification.manifest;
|
|
361
|
+
if (parent.repository !== manifest.repository) errors.push('parent repository mismatch');
|
|
362
|
+
if (new Date(parent.watermark).getTime() >= new Date(manifest.watermark).getTime()) errors.push('snapshot watermark is not monotonic');
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (manifest.watermark !== manifest.window.toExclusive) errors.push('watermark and window cutoff mismatch');
|
|
367
|
+
const watermark = new Date(manifest.watermark);
|
|
368
|
+
const from = manifest.window.fromInclusive ? new Date(manifest.window.fromInclusive) : null;
|
|
369
|
+
const queryAfter = manifest.window.queryAfter ? new Date(manifest.window.queryAfter) : null;
|
|
370
|
+
if (!Number.isFinite(watermark.getTime()) || (from && !Number.isFinite(from.getTime())) || (queryAfter && !Number.isFinite(queryAfter.getTime()))) {
|
|
371
|
+
errors.push('v2 observation window contains an invalid timestamp');
|
|
372
|
+
}
|
|
373
|
+
if (from && queryAfter && queryAfter.getTime() !== from.getTime() - 1000) errors.push('queryAfter does not equal watermark minus one second');
|
|
374
|
+
if (!from && queryAfter) errors.push('base/full window cannot have queryAfter without fromInclusive');
|
|
375
|
+
const strictSinceEndpoints = manifest.endpoints.filter((endpoint) => endpoint.queryMode === 'strict-since');
|
|
376
|
+
const expectedQueryAfter = from && Number.isFinite(from.getTime()) ? new Date(from.getTime() - 1000).toISOString() : null;
|
|
377
|
+
if (manifest.snapshotKind === 'base' && from) errors.push('base snapshot must not have fromInclusive');
|
|
378
|
+
if (queryAfter && strictSinceEndpoints.length === 0) errors.push('queryAfter requires a strict-since endpoint');
|
|
379
|
+
if (strictSinceEndpoints.length > 0 && (manifest.snapshotKind !== 'delta' || manifest.reconciliation !== 'incremental')) {
|
|
380
|
+
errors.push('strict-since is only valid for incremental delta snapshots');
|
|
381
|
+
}
|
|
382
|
+
if (strictSinceEndpoints.length > 0 && manifest.window.queryAfter !== expectedQueryAfter) {
|
|
383
|
+
errors.push('incremental strict-since queryAfter proof is invalid');
|
|
384
|
+
}
|
|
385
|
+
if (manifest.coverage.mode !== 'boundary-reread-with-full-reconcile' || manifest.coverage.absoluteCompleteness !== false) errors.push('coverage declaration is invalid');
|
|
386
|
+
const preflight = new Date(manifest.observation.preflightDate);
|
|
387
|
+
if (!Number.isFinite(preflight.getTime())) errors.push('preflight Date is invalid');
|
|
388
|
+
for (const responseDate of manifest.observation.responseDates) {
|
|
389
|
+
const parsed = new Date(responseDate);
|
|
390
|
+
if (!Number.isFinite(parsed.getTime()) || parsed.getTime() < preflight.getTime()) errors.push('response Date evidence is invalid');
|
|
391
|
+
}
|
|
392
|
+
for (const endpoint of manifest.endpoints) {
|
|
393
|
+
if (!endpoint.queryMode) errors.push(`endpoint query mode missing: ${endpoint.endpoint}`);
|
|
394
|
+
if (endpoint.queryMode !== 'strict-since' && endpoint.queryMode !== 'full-enumeration') {
|
|
395
|
+
errors.push(`endpoint query mode is invalid: ${endpoint.endpoint}`);
|
|
396
|
+
} else if (endpoint.queryMode === 'strict-since') {
|
|
397
|
+
if (endpoint.requestedSince !== expectedQueryAfter) errors.push(`endpoint since proof mismatch: ${endpoint.endpoint}`);
|
|
398
|
+
} else if (endpoint.requestedSince !== undefined) {
|
|
399
|
+
errors.push(`full-enumeration endpoint has requestedSince: ${endpoint.endpoint}`);
|
|
400
|
+
}
|
|
401
|
+
for (const page of endpoint.pages) {
|
|
402
|
+
if (page.queryMode !== 'strict-since' && page.queryMode !== 'full-enumeration') {
|
|
403
|
+
errors.push(`page query mode is invalid: ${endpoint.endpoint}#${page.index}`);
|
|
404
|
+
}
|
|
405
|
+
if (page.queryMode !== endpoint.queryMode) errors.push(`page query mode mismatch: ${endpoint.endpoint}#${page.index}`);
|
|
406
|
+
if (endpoint.queryMode === 'strict-since') {
|
|
407
|
+
if (page.requestedSince !== expectedQueryAfter) errors.push(`page since proof mismatch: ${endpoint.endpoint}#${page.index}`);
|
|
408
|
+
} else if (page.requestedSince !== undefined) {
|
|
409
|
+
errors.push(`full-enumeration page has requestedSince: ${endpoint.endpoint}#${page.index}`);
|
|
410
|
+
}
|
|
411
|
+
if (page.responseDate && new Date(page.responseDate).getTime() < preflight.getTime()) errors.push(`page Date precedes preflight: ${endpoint.endpoint}#${page.index}`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
for (const endpoint of manifest.endpoints) {
|
|
415
|
+
for (const page of endpoint.pages) {
|
|
416
|
+
const matches = manifest.objects.filter((object) => object.role === 'page-evidence'
|
|
417
|
+
&& object.endpoint === endpoint.endpoint
|
|
418
|
+
&& object.page === page.index
|
|
419
|
+
&& object.sha256 === page.canonicalSha256);
|
|
420
|
+
if (matches.length !== 1) {
|
|
421
|
+
errors.push(`page evidence CAS reference is missing or ambiguous: ${endpoint.endpoint}#${page.index}`);
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
const object = matches[0]!;
|
|
425
|
+
if (object.queryMode !== 'strict-since' && object.queryMode !== 'full-enumeration') {
|
|
426
|
+
errors.push(`page evidence query mode is invalid: ${endpoint.endpoint}#${page.index}`);
|
|
427
|
+
}
|
|
428
|
+
if (object.queryMode !== page.queryMode
|
|
429
|
+
|| object.requestedSince !== page.requestedSince
|
|
430
|
+
|| object.responseDate !== page.responseDate) {
|
|
431
|
+
errors.push(`page evidence metadata mismatch: ${endpoint.endpoint}#${page.index}`);
|
|
432
|
+
}
|
|
433
|
+
if (endpoint.queryMode === 'strict-since' && object.requestedSince !== expectedQueryAfter) {
|
|
434
|
+
errors.push(`page evidence since proof mismatch: ${endpoint.endpoint}#${page.index}`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
const { manifestSha256, ...partial } = manifest;
|
|
439
|
+
if (manifestDigest(partial) !== manifestSha256) errors.push('manifest hash mismatch');
|
|
440
|
+
const checkMode = (target: string, expected: number, label: string) => {
|
|
441
|
+
if (process.platform === 'win32' || !fs.existsSync(target)) return;
|
|
442
|
+
const actual = fs.statSync(target).mode & 0o777;
|
|
443
|
+
if (actual !== expected) errors.push(`${label} permission mismatch: expected ${expected.toString(8)}, got ${actual.toString(8)}`);
|
|
444
|
+
};
|
|
445
|
+
checkMode(snapshotPath, 0o700, 'snapshot directory');
|
|
446
|
+
for (const file of ['manifest.json', 'normalized.jsonl', 'quality.json', 'repair-manifest.jsonl']) checkMode(path.join(snapshotPath, file), 0o600, file);
|
|
447
|
+
const store = createObjectStore(root);
|
|
448
|
+
const normalizedEvidence: Array<{ resourceIdentity: string; resourceSha256: string; pageSha256: string }> = [];
|
|
449
|
+
try {
|
|
450
|
+
const lines = fs.readFileSync(path.join(snapshotPath, 'normalized.jsonl'), 'utf8').split('\n').filter(Boolean);
|
|
451
|
+
const ids = new Set<string>();
|
|
452
|
+
const operationCounts: SnapshotOperations = { upsert: 0, supersede: 0, tombstone: 0, unavailable: 0 };
|
|
453
|
+
for (const line of lines) {
|
|
454
|
+
const record = JSON.parse(line) as {
|
|
455
|
+
schema?: string;
|
|
456
|
+
recordId?: string;
|
|
457
|
+
resourceIdentity?: string;
|
|
458
|
+
sourceSha256?: string;
|
|
459
|
+
operation?: keyof SnapshotOperations;
|
|
460
|
+
evidence?: Array<{ resourceIdentity?: string; resourceSha256?: string; pageSha256?: string }>;
|
|
461
|
+
};
|
|
462
|
+
if (record.schema !== 'normalized/v2' || typeof record.recordId !== 'string') errors.push('normalized v2 record schema is invalid');
|
|
463
|
+
if (typeof record.recordId === 'string' && ids.has(record.recordId)) errors.push(`duplicate normalized record: ${record.recordId}`);
|
|
464
|
+
if (typeof record.recordId === 'string') ids.add(record.recordId);
|
|
465
|
+
if (!record.operation || !(record.operation in operationCounts)) errors.push('normalized v2 operation is invalid');
|
|
466
|
+
else operationCounts[record.operation] += 1;
|
|
467
|
+
if ((record.operation === 'upsert' || record.operation === 'supersede')
|
|
468
|
+
&& (!Array.isArray(record.evidence) || record.evidence.length !== 1)) {
|
|
469
|
+
errors.push(`resource evidence is required for ${record.operation}: ${record.recordId ?? 'unknown'}`);
|
|
470
|
+
}
|
|
471
|
+
for (const evidence of record.evidence ?? []) {
|
|
472
|
+
if (typeof evidence.resourceIdentity === 'string' && typeof evidence.resourceSha256 === 'string' && typeof evidence.pageSha256 === 'string') {
|
|
473
|
+
if (record.resourceIdentity !== evidence.resourceIdentity || record.sourceSha256 !== evidence.resourceSha256) {
|
|
474
|
+
errors.push(`normalized v2 evidence does not match record source: ${record.recordId ?? 'unknown'}`);
|
|
475
|
+
}
|
|
476
|
+
normalizedEvidence.push({ resourceIdentity: evidence.resourceIdentity, resourceSha256: evidence.resourceSha256, pageSha256: evidence.pageSha256 });
|
|
477
|
+
} else {
|
|
478
|
+
errors.push('normalized v2 evidence reference is invalid');
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
if (lines.length !== manifest.recordCount) errors.push('normalized record count mismatch');
|
|
483
|
+
if (canonicalJsonBytes(operationCounts).toString('utf8') !== canonicalJsonBytes(manifest.operations).toString('utf8')) errors.push('operation count mismatch');
|
|
484
|
+
const quality = JSON.parse(fs.readFileSync(path.join(snapshotPath, 'quality.json'), 'utf8')) as { schema?: string; findings?: unknown[] };
|
|
485
|
+
if (quality.schema !== 'quality/v1' || !Array.isArray(quality.findings)) errors.push('quality schema is invalid');
|
|
486
|
+
else if (quality.findings.length !== manifest.findingCount) errors.push('quality finding count mismatch');
|
|
487
|
+
const repairLines = fs.readFileSync(path.join(snapshotPath, 'repair-manifest.jsonl'), 'utf8').split('\n').filter(Boolean);
|
|
488
|
+
if (repairLines.length !== manifest.repairCount) errors.push('repair manifest count mismatch');
|
|
489
|
+
for (const line of repairLines) if ((JSON.parse(line) as { schema?: string }).schema !== 'repair/v1') errors.push('repair manifest schema is invalid');
|
|
490
|
+
} catch {
|
|
491
|
+
errors.push('snapshot derived files are missing or invalid');
|
|
492
|
+
}
|
|
493
|
+
for (const object of manifest.objects) {
|
|
494
|
+
if (object.role !== 'page-evidence' && object.role !== 'resource') errors.push(`object role is invalid: ${object.sourceIdentity}`);
|
|
495
|
+
if (object.disposition?.state && object.disposition.state !== 'included') continue;
|
|
496
|
+
try {
|
|
497
|
+
const objectPath = store.pathFor(object.sha256);
|
|
498
|
+
const bytes = fs.readFileSync(objectPath);
|
|
499
|
+
if (sha256(bytes) !== object.sha256 || bytes.length !== object.bytes) errors.push(`object integrity mismatch: ${object.sourceIdentity}`);
|
|
500
|
+
} catch {
|
|
501
|
+
errors.push(`object missing: ${object.sourceIdentity}`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
for (const evidence of normalizedEvidence) {
|
|
505
|
+
const resources = manifest.objects.filter((object) => object.role === 'resource'
|
|
506
|
+
&& object.resourceIdentity === evidence.resourceIdentity
|
|
507
|
+
&& object.sha256 === evidence.resourceSha256);
|
|
508
|
+
const pages = manifest.objects.filter((object) => object.role === 'page-evidence' && object.sha256 === evidence.pageSha256);
|
|
509
|
+
if (resources.length !== 1) errors.push(`resource evidence reference is missing or ambiguous: ${evidence.resourceIdentity}`);
|
|
510
|
+
if (pages.length === 0) errors.push(`resource page evidence reference is missing: ${evidence.resourceIdentity}`);
|
|
511
|
+
}
|
|
512
|
+
return { ok: errors.length === 0, snapshotId, snapshotPath, errors, manifest };
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function verifySnapshot(root: string, snapshotId: string, seen: Set<string> = new Set()): VerificationResult {
|
|
230
516
|
const snapshotPath = findSnapshot(root, snapshotId);
|
|
231
517
|
if (!snapshotPath) return { ok: false, snapshotId, snapshotPath: null, errors: ['snapshot not found'] };
|
|
232
518
|
const errors: string[] = [];
|
|
@@ -239,13 +525,19 @@ function verifySnapshot(root: string, snapshotId: string): VerificationResult {
|
|
|
239
525
|
for (const file of ['manifest.json', 'normalized.jsonl', 'quality.json', 'repair-manifest.jsonl']) {
|
|
240
526
|
checkMode(path.join(snapshotPath, file), 0o600, file);
|
|
241
527
|
}
|
|
242
|
-
let
|
|
528
|
+
let rawManifest: { schema?: unknown };
|
|
243
529
|
try {
|
|
244
|
-
|
|
530
|
+
rawManifest = JSON.parse(fs.readFileSync(path.join(snapshotPath, 'manifest.json'), 'utf8')) as { schema?: unknown };
|
|
245
531
|
} catch {
|
|
246
532
|
return { ok: false, snapshotId, snapshotPath, errors: ['manifest is invalid JSON'] };
|
|
247
533
|
}
|
|
248
|
-
if (
|
|
534
|
+
if (rawManifest.schema === 'raw-manifest/v2') {
|
|
535
|
+
return verifyV2Snapshot(root, snapshotId, snapshotPath, rawManifest as SnapshotManifestV2, seen);
|
|
536
|
+
}
|
|
537
|
+
if (rawManifest.schema !== 'raw-manifest/v1') {
|
|
538
|
+
return { ok: false, snapshotId, snapshotPath, errors: [`unsupported manifest schema: ${String(rawManifest.schema)}`] };
|
|
539
|
+
}
|
|
540
|
+
const manifest = rawManifest as SnapshotManifestV1;
|
|
249
541
|
if (manifest.snapshotId !== snapshotId) errors.push('snapshot identity mismatch');
|
|
250
542
|
if (typeof manifest.excerptsEnabled !== 'boolean') errors.push('manifest excerpts opt-in is invalid');
|
|
251
543
|
if (!manifest.dispositionCounts || canonicalJsonBytes(manifest.dispositionCounts).toString('utf8') !== canonicalJsonBytes(dispositionCounts(manifest.objects)).toString('utf8')) {
|
|
@@ -309,7 +601,8 @@ export {
|
|
|
309
601
|
createObjectStore,
|
|
310
602
|
findSnapshot,
|
|
311
603
|
publishSnapshot,
|
|
604
|
+
publishSnapshotV2,
|
|
312
605
|
sha256,
|
|
313
606
|
verifySnapshot
|
|
314
607
|
};
|
|
315
|
-
export type { ObjectStore, ObjectWrite, PublishInput, VerificationResult };
|
|
608
|
+
export type { ObjectStore, ObjectWrite, PublishInput, PublishV2Input, VerificationResult };
|
|
@@ -7,6 +7,9 @@ type Availability<T> =
|
|
|
7
7
|
|
|
8
8
|
type SourceKind = 'local-file' | 'github-rest' | 'operational-report' | 'structured-telemetry';
|
|
9
9
|
|
|
10
|
+
type CaptureObjectRole = 'page-evidence' | 'resource';
|
|
11
|
+
type EndpointQueryMode = 'strict-since' | 'full-enumeration';
|
|
12
|
+
|
|
10
13
|
type PrivacyDisposition =
|
|
11
14
|
| { state: 'included' }
|
|
12
15
|
| { state: 'excluded-sensitive'; ruleId: string }
|
|
@@ -19,14 +22,37 @@ type CapturedObject = {
|
|
|
19
22
|
bytes: number;
|
|
20
23
|
content?: string;
|
|
21
24
|
disposition?: PrivacyDisposition;
|
|
25
|
+
role?: CaptureObjectRole;
|
|
26
|
+
resourceIdentity?: string;
|
|
27
|
+
routeNumber?: number;
|
|
28
|
+
eventTime?: string;
|
|
29
|
+
endpoint?: string;
|
|
30
|
+
page?: number;
|
|
31
|
+
queryMode?: EndpointQueryMode;
|
|
32
|
+
requestedSince?: string;
|
|
33
|
+
responseDate?: string;
|
|
34
|
+
pageSha256?: string;
|
|
35
|
+
parentIdentity?: string;
|
|
22
36
|
};
|
|
23
37
|
|
|
24
38
|
type StoredObjectEvidence = Omit<CapturedObject, 'content'>;
|
|
25
39
|
|
|
40
|
+
type ResourceEvidenceRef = {
|
|
41
|
+
resourceIdentity: string;
|
|
42
|
+
resourceSha256: string;
|
|
43
|
+
pageSha256: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
26
46
|
type RestPageEvidence = {
|
|
27
47
|
index: number;
|
|
28
48
|
itemCount: number;
|
|
29
49
|
canonicalSha256: string;
|
|
50
|
+
queryMode?: EndpointQueryMode;
|
|
51
|
+
requestedSince?: string;
|
|
52
|
+
responseDate?: string;
|
|
53
|
+
overlapItemCount?: number;
|
|
54
|
+
acceptedItemCount?: number;
|
|
55
|
+
deferredItemCount?: number;
|
|
30
56
|
};
|
|
31
57
|
|
|
32
58
|
type RestCollectionEvidence = {
|
|
@@ -36,6 +62,8 @@ type RestCollectionEvidence = {
|
|
|
36
62
|
itemCount: number;
|
|
37
63
|
termination: 'short-page';
|
|
38
64
|
pages: RestPageEvidence[];
|
|
65
|
+
queryMode?: EndpointQueryMode;
|
|
66
|
+
requestedSince?: string;
|
|
39
67
|
};
|
|
40
68
|
|
|
41
69
|
type NormalizedKind =
|
|
@@ -58,6 +86,10 @@ type NormalizedRecord = {
|
|
|
58
86
|
binding?: string;
|
|
59
87
|
observedAt?: Availability<string>;
|
|
60
88
|
data?: JsonValue;
|
|
89
|
+
resourceIdentity?: string;
|
|
90
|
+
parentIdentity?: string;
|
|
91
|
+
evidence?: ResourceEvidenceRef[];
|
|
92
|
+
operation?: 'upsert' | 'supersede' | 'tombstone' | 'unavailable' | 'unchanged';
|
|
61
93
|
};
|
|
62
94
|
|
|
63
95
|
type QualityCategory =
|
|
@@ -88,10 +120,12 @@ type RepairAction = {
|
|
|
88
120
|
preconditionSha256: string;
|
|
89
121
|
};
|
|
90
122
|
|
|
91
|
-
type
|
|
123
|
+
type SnapshotScope = 'all' | 'local' | 'github';
|
|
124
|
+
|
|
125
|
+
type SnapshotManifestV1 = {
|
|
92
126
|
schema: 'raw-manifest/v1';
|
|
93
127
|
snapshotId: string;
|
|
94
|
-
scope:
|
|
128
|
+
scope: SnapshotScope;
|
|
95
129
|
repository: string;
|
|
96
130
|
observedFrom: string;
|
|
97
131
|
observedTo: string;
|
|
@@ -106,6 +140,80 @@ type SnapshotManifest = {
|
|
|
106
140
|
manifestSha256: string;
|
|
107
141
|
};
|
|
108
142
|
|
|
143
|
+
type SnapshotWindow = {
|
|
144
|
+
fromInclusive: string | null;
|
|
145
|
+
queryAfter: string | null;
|
|
146
|
+
toExclusive: string;
|
|
147
|
+
precision: 'second';
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
type ObservationEvidence = {
|
|
151
|
+
cutoffSource: 'github-response-date';
|
|
152
|
+
preflightDate: string;
|
|
153
|
+
responseDates: string[];
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
type CoverageEvidence = {
|
|
157
|
+
mode: 'boundary-reread-with-full-reconcile';
|
|
158
|
+
absoluteCompleteness: false;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
type SnapshotOperations = {
|
|
162
|
+
upsert: number;
|
|
163
|
+
supersede: number;
|
|
164
|
+
tombstone: number;
|
|
165
|
+
unavailable: number;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
type SnapshotManifestV2 = Omit<SnapshotManifestV1, 'schema' | 'observedFrom' | 'observedTo'> & {
|
|
169
|
+
schema: 'raw-manifest/v2';
|
|
170
|
+
snapshotKind: 'base' | 'delta';
|
|
171
|
+
parentSnapshotId: string | null;
|
|
172
|
+
checkpointBefore: string | null;
|
|
173
|
+
watermark: string;
|
|
174
|
+
window: SnapshotWindow;
|
|
175
|
+
observation: ObservationEvidence;
|
|
176
|
+
coverage: CoverageEvidence;
|
|
177
|
+
reconciliation: 'incremental' | 'full';
|
|
178
|
+
operations: SnapshotOperations;
|
|
179
|
+
observedFrom: string;
|
|
180
|
+
observedTo: string;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
type SnapshotManifest = SnapshotManifestV1 | SnapshotManifestV2;
|
|
184
|
+
|
|
185
|
+
type CheckpointOwner = {
|
|
186
|
+
schema: 'checkpoint-owner/v1';
|
|
187
|
+
ownerId: string;
|
|
188
|
+
pid: number;
|
|
189
|
+
host: string;
|
|
190
|
+
processStartToken: string;
|
|
191
|
+
createdAt: string;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
type GitHubCheckpoint = {
|
|
195
|
+
schema: 'github-checkpoint/v1';
|
|
196
|
+
repository: string;
|
|
197
|
+
snapshotId: string;
|
|
198
|
+
watermark: string;
|
|
199
|
+
manifestSha256: string;
|
|
200
|
+
committedAt: string;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
type DeltaOperation = {
|
|
204
|
+
operation: 'upsert' | 'supersede' | 'tombstone' | 'unavailable';
|
|
205
|
+
resourceIdentity: string;
|
|
206
|
+
previousSha256?: string;
|
|
207
|
+
resourceSha256?: string;
|
|
208
|
+
reason?: string;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
type RecoveryReport = {
|
|
212
|
+
recovered: boolean;
|
|
213
|
+
quarantined: string[];
|
|
214
|
+
reason?: string;
|
|
215
|
+
};
|
|
216
|
+
|
|
109
217
|
type ProcessDataError = {
|
|
110
218
|
code: string;
|
|
111
219
|
message: string;
|
|
@@ -127,9 +235,23 @@ export type {
|
|
|
127
235
|
QualityCategory,
|
|
128
236
|
QualityFinding,
|
|
129
237
|
RepairAction,
|
|
238
|
+
CaptureObjectRole,
|
|
239
|
+
CheckpointOwner,
|
|
240
|
+
CoverageEvidence,
|
|
241
|
+
DeltaOperation,
|
|
242
|
+
EndpointQueryMode,
|
|
243
|
+
GitHubCheckpoint,
|
|
244
|
+
ObservationEvidence,
|
|
245
|
+
RecoveryReport,
|
|
246
|
+
ResourceEvidenceRef,
|
|
130
247
|
RestCollectionEvidence,
|
|
131
248
|
RestPageEvidence,
|
|
132
249
|
SnapshotManifest,
|
|
250
|
+
SnapshotManifestV1,
|
|
251
|
+
SnapshotManifestV2,
|
|
252
|
+
SnapshotOperations,
|
|
253
|
+
SnapshotScope,
|
|
254
|
+
SnapshotWindow,
|
|
133
255
|
SourceKind,
|
|
134
256
|
StoredObjectEvidence
|
|
135
257
|
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
export type AgentRuntimeStore = 'capabilities' | 'lifecycle';
|
|
4
|
+
|
|
5
|
+
type RuntimeResolutionOptions = Readonly<{
|
|
6
|
+
repoRoot?: string;
|
|
7
|
+
env?: NodeJS.ProcessEnv;
|
|
8
|
+
explicitRoot?: string;
|
|
9
|
+
client?: string;
|
|
10
|
+
store?: AgentRuntimeStore;
|
|
11
|
+
}>;
|
|
12
|
+
|
|
13
|
+
const CLIENT_NAMESPACE = /^[a-z][a-z0-9-]{0,31}$/u;
|
|
14
|
+
|
|
15
|
+
function runtimeError(code: string, message: string): Error {
|
|
16
|
+
const error = new Error(`${code}: ${message}`);
|
|
17
|
+
error.name = code;
|
|
18
|
+
return error;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function boundControlContext(env: NodeJS.ProcessEnv): boolean {
|
|
22
|
+
return [
|
|
23
|
+
'AGENT_INFRA_CONTROL_TOKEN',
|
|
24
|
+
'AGENT_INFRA_CONTROL_GENERATION',
|
|
25
|
+
'AGENT_INFRA_EXECUTOR_MANIFEST',
|
|
26
|
+
'AGENT_INFRA_CODEX_CONTROLLER_CONTEXT'
|
|
27
|
+
].some((key) => typeof env[key] === 'string' && env[key]!.length > 0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function clientNamespace(client: string): string {
|
|
31
|
+
if (!CLIENT_NAMESPACE.test(client)) {
|
|
32
|
+
throw runtimeError('AGENT_INFRA_RUNTIME_CLIENT_INVALID', `invalid runtime client namespace '${client}'`);
|
|
33
|
+
}
|
|
34
|
+
return client;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function resolveAgentRuntimeRoot(options: Readonly<{
|
|
38
|
+
repoRoot?: string;
|
|
39
|
+
env?: NodeJS.ProcessEnv;
|
|
40
|
+
}> = {}): string {
|
|
41
|
+
const env = options.env ?? process.env;
|
|
42
|
+
const configured = env.AGENT_INFRA_RUNTIME_DIR;
|
|
43
|
+
if (configured !== undefined && configured.length > 0) {
|
|
44
|
+
if (!path.isAbsolute(configured)) {
|
|
45
|
+
throw runtimeError('AGENT_INFRA_RUNTIME_DIR_INVALID', 'runtime directory must be absolute');
|
|
46
|
+
}
|
|
47
|
+
return path.resolve(configured);
|
|
48
|
+
}
|
|
49
|
+
if (boundControlContext(env)) {
|
|
50
|
+
throw runtimeError(
|
|
51
|
+
'AGENT_INFRA_RUNTIME_DIR_REQUIRED',
|
|
52
|
+
'task-bound control context requires AGENT_INFRA_RUNTIME_DIR'
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return path.join(path.resolve(options.repoRoot ?? process.cwd()), '.agents', 'workspace', '.runtime');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function resolveAgentRuntimeStoreRoot(options: RuntimeResolutionOptions = {}): string {
|
|
59
|
+
const client = clientNamespace(options.client ?? 'codex');
|
|
60
|
+
if (options.explicitRoot) return path.resolve(options.explicitRoot);
|
|
61
|
+
const env = options.env ?? process.env;
|
|
62
|
+
const runtimeRoot = resolveAgentRuntimeRoot({ repoRoot: options.repoRoot, env });
|
|
63
|
+
if (env.AGENT_INFRA_RUNTIME_DIR) {
|
|
64
|
+
if (!options.store) {
|
|
65
|
+
throw runtimeError('AGENT_INFRA_RUNTIME_STORE_INVALID', 'store kind is required for task-bound runtime');
|
|
66
|
+
}
|
|
67
|
+
return path.join(runtimeRoot, 'clients', client, options.store);
|
|
68
|
+
}
|
|
69
|
+
if (client === 'codex' && options.store) {
|
|
70
|
+
return path.join(runtimeRoot, `codex-${options.store}`);
|
|
71
|
+
}
|
|
72
|
+
if (!options.store) {
|
|
73
|
+
throw runtimeError('AGENT_INFRA_RUNTIME_STORE_INVALID', 'store kind is required');
|
|
74
|
+
}
|
|
75
|
+
return path.join(runtimeRoot, 'clients', client, options.store);
|
|
76
|
+
}
|