@fitlab-ai/agent-infra 0.9.5 → 0.9.7
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 +8 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +171 -20
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +244 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +370 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +68 -8
- package/dist/lib/agent-clients/adapters/codex.js +1 -1
- package/dist/lib/defaults.json +10 -2
- package/dist/lib/git/workflow.js +30 -4
- package/dist/lib/internal/codex-lifecycle.js +169 -3
- package/dist/lib/internal/codex-sandbox-controller.js +106 -0
- package/dist/lib/internal/release-workflow.js +108 -11
- package/dist/lib/internal/sandbox-control.js +43 -6
- package/dist/lib/internal/task-create.js +12 -4
- package/dist/lib/internal/task-orchestration.js +40 -7
- package/dist/lib/platform/context.js +6 -4
- package/dist/lib/platform/github-client.js +55 -2
- package/dist/lib/platform/issue-comments.js +2 -1
- 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/release/workflow.js +29 -21
- package/dist/lib/sandbox/commands/create.js +111 -60
- package/dist/lib/sandbox/commands/prune.js +40 -16
- package/dist/lib/sandbox/commands/rm.js +372 -94
- package/dist/lib/sandbox/commands/start.js +4 -3
- package/dist/lib/sandbox/control/client.js +138 -22
- package/dist/lib/sandbox/control/container-identity.js +64 -0
- package/dist/lib/sandbox/control/executor.js +142 -0
- package/dist/lib/sandbox/control/lifecycle.js +557 -0
- package/dist/lib/sandbox/control/protocol.js +69 -26
- package/dist/lib/sandbox/control/server.js +395 -99
- package/dist/lib/sandbox/control/state.js +191 -0
- package/dist/lib/sandbox/control/timing.js +8 -0
- package/dist/lib/sandbox/index.js +2 -2
- package/dist/lib/sandbox/managed-fs.js +14 -2
- package/dist/lib/sandbox/mounts.js +22 -19
- package/dist/lib/sandbox/recovery.js +284 -50
- package/dist/lib/sandbox/workspace-view.js +90 -10
- package/dist/lib/sandbox/worktree-safety.js +196 -0
- package/dist/lib/server/daemon.js +1 -1
- package/dist/lib/server/process-control.js +17 -1
- package/dist/lib/server/process-state.js +57 -20
- package/dist/lib/task/artifact-lifecycle.js +87 -15
- package/dist/lib/task/artifact-receipts.js +128 -0
- package/dist/lib/task/codex-orchestration.js +236 -0
- package/dist/lib/task/commands/status.js +3 -0
- package/dist/lib/task/commands/validate.js +289 -0
- package/dist/lib/task/commit-finalization.js +36 -9
- package/dist/lib/task/create.js +10 -2
- package/dist/lib/task/delegation-receipts.js +109 -2
- package/dist/lib/task/events.js +149 -6
- package/dist/lib/task/finalization-artifacts.js +2 -1
- package/dist/lib/task/implementation-inputs.js +1 -7
- package/dist/lib/task/index.js +9 -0
- package/dist/lib/task/ledger.js +13 -1
- package/dist/lib/task/orchestration.js +365 -15
- 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 +78 -14
- package/dist/lib/task/verification.js +1 -0
- package/dist/lifecycle-build-manifest.json +6 -0
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +202 -18
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +321 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -3
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +453 -0
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +83 -6
- package/lib/agent-clients/adapters/codex.ts +1 -1
- package/lib/defaults.json +10 -2
- package/lib/git/workflow.ts +25 -3
- package/lib/internal/codex-lifecycle.ts +175 -4
- package/lib/internal/codex-sandbox-controller.ts +109 -0
- package/lib/internal/release-workflow.ts +107 -11
- package/lib/internal/sandbox-control.ts +38 -6
- package/lib/internal/task-create.ts +16 -4
- package/lib/internal/task-orchestration.ts +34 -7
- package/lib/platform/context.ts +8 -5
- package/lib/platform/github-client.ts +61 -3
- package/lib/platform/issue-comments.ts +2 -1
- 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/release/workflow.ts +69 -20
- package/lib/sandbox/commands/create.ts +70 -25
- package/lib/sandbox/commands/prune.ts +53 -15
- package/lib/sandbox/commands/rm.ts +423 -108
- package/lib/sandbox/commands/start.ts +4 -3
- package/lib/sandbox/control/client.ts +152 -28
- package/lib/sandbox/control/container-identity.ts +93 -0
- package/lib/sandbox/control/executor.ts +167 -0
- package/lib/sandbox/control/lifecycle.ts +573 -0
- package/lib/sandbox/control/protocol.ts +121 -68
- package/lib/sandbox/control/server.ts +398 -109
- package/lib/sandbox/control/state.ts +213 -0
- package/lib/sandbox/control/timing.ts +12 -0
- package/lib/sandbox/index.ts +2 -2
- package/lib/sandbox/managed-fs.ts +17 -2
- package/lib/sandbox/mounts.ts +16 -8
- package/lib/sandbox/recovery.ts +335 -65
- package/lib/sandbox/workspace-view.ts +102 -11
- package/lib/sandbox/worktree-safety.ts +234 -0
- package/lib/server/daemon.ts +1 -1
- package/lib/server/process-control.ts +24 -3
- package/lib/server/process-state.ts +82 -27
- package/lib/task/artifact-lifecycle.ts +78 -14
- package/lib/task/artifact-receipts.ts +161 -0
- package/lib/task/codex-orchestration.ts +319 -0
- package/lib/task/commands/status.ts +2 -0
- package/lib/task/commands/validate.ts +282 -0
- package/lib/task/commit-finalization.ts +52 -9
- package/lib/task/create.ts +11 -2
- package/lib/task/delegation-receipts.ts +208 -7
- package/lib/task/events.ts +131 -5
- package/lib/task/finalization-artifacts.ts +2 -1
- package/lib/task/implementation-inputs.ts +1 -5
- package/lib/task/index.ts +8 -0
- package/lib/task/ledger.ts +16 -1
- package/lib/task/orchestration.ts +552 -18
- package/lib/task/sections.ts +6 -2
- package/lib/task/task-execution-lock.ts +11 -9
- package/lib/task/verification-engine.ts +93 -24
- package/lib/task/verification.ts +2 -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 +72 -12
- package/templates/.agents/rules/lifecycle-orchestration.en.md +8 -2
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +8 -2
- package/templates/.agents/rules/no-mid-flow-questions.en.md +1 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +1 -0
- 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/post-release/SKILL.en.md +32 -6
- package/templates/.agents/skills/post-release/SKILL.zh-CN.md +32 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +42 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +42 -0
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +25 -0
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +25 -0
- 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 +69 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +69 -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 -3
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +5 -3
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
- package/templates/.claude/commands/run-manual-validation.en.md +9 -0
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +9 -0
- package/templates/.codex/hooks.json +8 -8
- package/templates/.opencode/commands/run-manual-validation.en.md +11 -0
- package/templates/.opencode/commands/run-manual-validation.zh-CN.md +11 -0
- package/templates/.agents/workspace/README.en.md +0 -26
- package/templates/.agents/workspace/README.zh-CN.md +0 -26
|
@@ -47,12 +47,39 @@ function canonicalJsonBytes(value) {
|
|
|
47
47
|
return Buffer.from(canonicalString(value, new Set()), 'utf8');
|
|
48
48
|
}
|
|
49
49
|
function ensurePrivateDirectory(directory) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
const missing = [];
|
|
51
|
+
let current = path.resolve(directory);
|
|
52
|
+
while (!fs.existsSync(current)) {
|
|
53
|
+
missing.push(current);
|
|
54
|
+
const parent = path.dirname(current);
|
|
55
|
+
if (parent === current)
|
|
56
|
+
throw new Error(`cannot create directory tree: ${directory}`);
|
|
57
|
+
current = parent;
|
|
58
|
+
}
|
|
59
|
+
for (const target of missing.reverse()) {
|
|
60
|
+
fs.mkdirSync(target, { mode: 0o700 });
|
|
61
|
+
if (process.platform !== 'win32')
|
|
62
|
+
fs.chmodSync(target, 0o700);
|
|
63
|
+
fsyncDirectory(path.dirname(target));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function fsyncDirectory(directory) {
|
|
67
|
+
if (process.platform === 'win32')
|
|
68
|
+
return;
|
|
69
|
+
const fd = fs.openSync(directory, 'r');
|
|
70
|
+
try {
|
|
71
|
+
fs.fsyncSync(fd);
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
fs.closeSync(fd);
|
|
75
|
+
}
|
|
53
76
|
}
|
|
54
77
|
function writeExclusive(filePath, bytes) {
|
|
55
|
-
|
|
78
|
+
const directory = path.dirname(filePath);
|
|
79
|
+
const directoryExisted = fs.existsSync(directory);
|
|
80
|
+
ensurePrivateDirectory(directory);
|
|
81
|
+
if (!directoryExisted)
|
|
82
|
+
fsyncDirectory(path.dirname(directory));
|
|
56
83
|
try {
|
|
57
84
|
const fd = fs.openSync(filePath, 'wx', 0o600);
|
|
58
85
|
try {
|
|
@@ -62,6 +89,7 @@ function writeExclusive(filePath, bytes) {
|
|
|
62
89
|
finally {
|
|
63
90
|
fs.closeSync(fd);
|
|
64
91
|
}
|
|
92
|
+
fsyncDirectory(directory);
|
|
65
93
|
return true;
|
|
66
94
|
}
|
|
67
95
|
catch (error) {
|
|
@@ -100,7 +128,14 @@ function jsonLine(value) {
|
|
|
100
128
|
return `${canonicalJsonBytes(value).toString('utf8')}\n`;
|
|
101
129
|
}
|
|
102
130
|
function writePrivateFile(filePath, content) {
|
|
103
|
-
fs.
|
|
131
|
+
const fd = fs.openSync(filePath, 'wx', 0o600);
|
|
132
|
+
try {
|
|
133
|
+
fs.writeFileSync(fd, content);
|
|
134
|
+
fs.fsyncSync(fd);
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
fs.closeSync(fd);
|
|
138
|
+
}
|
|
104
139
|
if (process.platform !== 'win32')
|
|
105
140
|
fs.chmodSync(filePath, 0o600);
|
|
106
141
|
}
|
|
@@ -153,9 +188,82 @@ function publishSnapshot(root, input) {
|
|
|
153
188
|
if (existing !== jsonLine(manifest))
|
|
154
189
|
throw new Error(`snapshot collision: ${snapshotId}`);
|
|
155
190
|
fs.rmSync(staging, { recursive: true, force: true });
|
|
191
|
+
fsyncDirectory(stagingRoot);
|
|
192
|
+
return { snapshotId, path: destination, created: false };
|
|
193
|
+
}
|
|
194
|
+
fsyncDirectory(staging);
|
|
195
|
+
fs.renameSync(staging, destination);
|
|
196
|
+
fsyncDirectory(stagingRoot);
|
|
197
|
+
fsyncDirectory(path.dirname(destination));
|
|
198
|
+
return { snapshotId, path: destination, created: true };
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function publishSnapshotV2(root, input) {
|
|
206
|
+
ensurePrivateDirectory(root);
|
|
207
|
+
ensurePrivateDirectory(path.join(root, 'repairs'));
|
|
208
|
+
const date = new Date(input.observedTo);
|
|
209
|
+
if (!Number.isFinite(date.getTime()))
|
|
210
|
+
throw new Error('observedTo must be an ISO timestamp');
|
|
211
|
+
const identity = {
|
|
212
|
+
scope: 'github',
|
|
213
|
+
repository: input.repository,
|
|
214
|
+
observedFrom: input.observedFrom,
|
|
215
|
+
observedTo: input.observedTo,
|
|
216
|
+
objects: input.objects,
|
|
217
|
+
endpoints: input.endpoints,
|
|
218
|
+
excerptsEnabled: false,
|
|
219
|
+
dispositionCounts: dispositionCounts(input.objects),
|
|
220
|
+
recordCount: input.records.length,
|
|
221
|
+
findingCount: input.quality.length,
|
|
222
|
+
repairCount: input.repairs.length,
|
|
223
|
+
snapshotKind: input.snapshotKind,
|
|
224
|
+
parentSnapshotId: input.parentSnapshotId,
|
|
225
|
+
checkpointBefore: input.checkpointBefore,
|
|
226
|
+
watermark: input.watermark,
|
|
227
|
+
window: input.window,
|
|
228
|
+
observation: input.observation,
|
|
229
|
+
coverage: input.coverage,
|
|
230
|
+
reconciliation: input.reconciliation,
|
|
231
|
+
operations: input.operations
|
|
232
|
+
};
|
|
233
|
+
const snapshotId = `${input.watermark.replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z')}-${sha256(canonicalJsonBytes(identity)).slice(0, 12)}`;
|
|
234
|
+
const partialManifest = {
|
|
235
|
+
schema: 'raw-manifest/v2',
|
|
236
|
+
snapshotId,
|
|
237
|
+
...identity,
|
|
238
|
+
observedFrom: input.observedFrom,
|
|
239
|
+
observedTo: input.observedTo,
|
|
240
|
+
privacyPolicyVersion: 'process-data-privacy/v1'
|
|
241
|
+
};
|
|
242
|
+
const manifest = { ...partialManifest, manifestSha256: manifestDigest(partialManifest) };
|
|
243
|
+
const stagingRoot = path.join(root, '.staging');
|
|
244
|
+
ensurePrivateDirectory(stagingRoot);
|
|
245
|
+
const staging = fs.mkdtempSync(path.join(stagingRoot, `${snapshotId}-`));
|
|
246
|
+
if (process.platform !== 'win32')
|
|
247
|
+
fs.chmodSync(staging, 0o700);
|
|
248
|
+
try {
|
|
249
|
+
writePrivateFile(path.join(staging, 'manifest.json'), jsonLine(manifest));
|
|
250
|
+
writePrivateFile(path.join(staging, 'normalized.jsonl'), input.records.map((record) => jsonLine({ schema: 'normalized/v2', ...record })).join(''));
|
|
251
|
+
writePrivateFile(path.join(staging, 'quality.json'), jsonLine({ schema: 'quality/v1', findings: input.quality }));
|
|
252
|
+
writePrivateFile(path.join(staging, 'repair-manifest.jsonl'), input.repairs.map((repair) => jsonLine({ schema: 'repair/v1', ...repair })).join(''));
|
|
253
|
+
const destination = path.join(root, 'snapshots', String(date.getUTCFullYear()), String(date.getUTCMonth() + 1).padStart(2, '0'), String(date.getUTCDate()).padStart(2, '0'), snapshotId);
|
|
254
|
+
ensurePrivateDirectory(path.dirname(destination));
|
|
255
|
+
if (fs.existsSync(destination)) {
|
|
256
|
+
const existing = fs.readFileSync(path.join(destination, 'manifest.json'), 'utf8');
|
|
257
|
+
if (existing !== jsonLine(manifest))
|
|
258
|
+
throw new Error(`snapshot collision: ${snapshotId}`);
|
|
259
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
260
|
+
fsyncDirectory(stagingRoot);
|
|
156
261
|
return { snapshotId, path: destination, created: false };
|
|
157
262
|
}
|
|
263
|
+
fsyncDirectory(staging);
|
|
158
264
|
fs.renameSync(staging, destination);
|
|
265
|
+
fsyncDirectory(stagingRoot);
|
|
266
|
+
fsyncDirectory(path.dirname(destination));
|
|
159
267
|
return { snapshotId, path: destination, created: true };
|
|
160
268
|
}
|
|
161
269
|
catch (error) {
|
|
@@ -181,7 +289,224 @@ function findSnapshot(root, snapshotId) {
|
|
|
181
289
|
}
|
|
182
290
|
return null;
|
|
183
291
|
}
|
|
184
|
-
function
|
|
292
|
+
function verifyV2Snapshot(root, snapshotId, snapshotPath, manifest, seen) {
|
|
293
|
+
const errors = [];
|
|
294
|
+
if (seen.has(snapshotId))
|
|
295
|
+
return { ok: false, snapshotId, snapshotPath, errors: ['snapshot lineage cycle detected'], manifest };
|
|
296
|
+
seen.add(snapshotId);
|
|
297
|
+
if (manifest.snapshotId !== snapshotId)
|
|
298
|
+
errors.push('snapshot identity mismatch');
|
|
299
|
+
if (manifest.scope !== 'github')
|
|
300
|
+
errors.push('v2 snapshot scope must be github');
|
|
301
|
+
if (manifest.snapshotKind === 'base' && manifest.parentSnapshotId !== null)
|
|
302
|
+
errors.push('base snapshot must not have a parent');
|
|
303
|
+
if (manifest.snapshotKind === 'delta' && !manifest.parentSnapshotId)
|
|
304
|
+
errors.push('delta snapshot must have a parent');
|
|
305
|
+
if (manifest.checkpointBefore !== manifest.parentSnapshotId)
|
|
306
|
+
errors.push('checkpoint and parent lineage mismatch');
|
|
307
|
+
if (manifest.parentSnapshotId) {
|
|
308
|
+
const parentPath = findSnapshot(root, manifest.parentSnapshotId);
|
|
309
|
+
if (!parentPath)
|
|
310
|
+
errors.push('parent snapshot is missing');
|
|
311
|
+
else {
|
|
312
|
+
const parentVerification = verifySnapshot(root, manifest.parentSnapshotId, seen);
|
|
313
|
+
if (!parentVerification.ok || !parentVerification.manifest) {
|
|
314
|
+
errors.push(`parent snapshot is invalid: ${parentVerification.errors.join('; ')}`);
|
|
315
|
+
}
|
|
316
|
+
else if (parentVerification.manifest.schema !== 'raw-manifest/v2') {
|
|
317
|
+
errors.push('v2 snapshot parent is not v2');
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
const parent = parentVerification.manifest;
|
|
321
|
+
if (parent.repository !== manifest.repository)
|
|
322
|
+
errors.push('parent repository mismatch');
|
|
323
|
+
if (new Date(parent.watermark).getTime() >= new Date(manifest.watermark).getTime())
|
|
324
|
+
errors.push('snapshot watermark is not monotonic');
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (manifest.watermark !== manifest.window.toExclusive)
|
|
329
|
+
errors.push('watermark and window cutoff mismatch');
|
|
330
|
+
const watermark = new Date(manifest.watermark);
|
|
331
|
+
const from = manifest.window.fromInclusive ? new Date(manifest.window.fromInclusive) : null;
|
|
332
|
+
const queryAfter = manifest.window.queryAfter ? new Date(manifest.window.queryAfter) : null;
|
|
333
|
+
if (!Number.isFinite(watermark.getTime()) || (from && !Number.isFinite(from.getTime())) || (queryAfter && !Number.isFinite(queryAfter.getTime()))) {
|
|
334
|
+
errors.push('v2 observation window contains an invalid timestamp');
|
|
335
|
+
}
|
|
336
|
+
if (from && queryAfter && queryAfter.getTime() !== from.getTime() - 1000)
|
|
337
|
+
errors.push('queryAfter does not equal watermark minus one second');
|
|
338
|
+
if (!from && queryAfter)
|
|
339
|
+
errors.push('base/full window cannot have queryAfter without fromInclusive');
|
|
340
|
+
const strictSinceEndpoints = manifest.endpoints.filter((endpoint) => endpoint.queryMode === 'strict-since');
|
|
341
|
+
const expectedQueryAfter = from && Number.isFinite(from.getTime()) ? new Date(from.getTime() - 1000).toISOString() : null;
|
|
342
|
+
if (manifest.snapshotKind === 'base' && from)
|
|
343
|
+
errors.push('base snapshot must not have fromInclusive');
|
|
344
|
+
if (queryAfter && strictSinceEndpoints.length === 0)
|
|
345
|
+
errors.push('queryAfter requires a strict-since endpoint');
|
|
346
|
+
if (strictSinceEndpoints.length > 0 && (manifest.snapshotKind !== 'delta' || manifest.reconciliation !== 'incremental')) {
|
|
347
|
+
errors.push('strict-since is only valid for incremental delta snapshots');
|
|
348
|
+
}
|
|
349
|
+
if (strictSinceEndpoints.length > 0 && manifest.window.queryAfter !== expectedQueryAfter) {
|
|
350
|
+
errors.push('incremental strict-since queryAfter proof is invalid');
|
|
351
|
+
}
|
|
352
|
+
if (manifest.coverage.mode !== 'boundary-reread-with-full-reconcile' || manifest.coverage.absoluteCompleteness !== false)
|
|
353
|
+
errors.push('coverage declaration is invalid');
|
|
354
|
+
const preflight = new Date(manifest.observation.preflightDate);
|
|
355
|
+
if (!Number.isFinite(preflight.getTime()))
|
|
356
|
+
errors.push('preflight Date is invalid');
|
|
357
|
+
for (const responseDate of manifest.observation.responseDates) {
|
|
358
|
+
const parsed = new Date(responseDate);
|
|
359
|
+
if (!Number.isFinite(parsed.getTime()) || parsed.getTime() < preflight.getTime())
|
|
360
|
+
errors.push('response Date evidence is invalid');
|
|
361
|
+
}
|
|
362
|
+
for (const endpoint of manifest.endpoints) {
|
|
363
|
+
if (!endpoint.queryMode)
|
|
364
|
+
errors.push(`endpoint query mode missing: ${endpoint.endpoint}`);
|
|
365
|
+
if (endpoint.queryMode !== 'strict-since' && endpoint.queryMode !== 'full-enumeration') {
|
|
366
|
+
errors.push(`endpoint query mode is invalid: ${endpoint.endpoint}`);
|
|
367
|
+
}
|
|
368
|
+
else if (endpoint.queryMode === 'strict-since') {
|
|
369
|
+
if (endpoint.requestedSince !== expectedQueryAfter)
|
|
370
|
+
errors.push(`endpoint since proof mismatch: ${endpoint.endpoint}`);
|
|
371
|
+
}
|
|
372
|
+
else if (endpoint.requestedSince !== undefined) {
|
|
373
|
+
errors.push(`full-enumeration endpoint has requestedSince: ${endpoint.endpoint}`);
|
|
374
|
+
}
|
|
375
|
+
for (const page of endpoint.pages) {
|
|
376
|
+
if (page.queryMode !== 'strict-since' && page.queryMode !== 'full-enumeration') {
|
|
377
|
+
errors.push(`page query mode is invalid: ${endpoint.endpoint}#${page.index}`);
|
|
378
|
+
}
|
|
379
|
+
if (page.queryMode !== endpoint.queryMode)
|
|
380
|
+
errors.push(`page query mode mismatch: ${endpoint.endpoint}#${page.index}`);
|
|
381
|
+
if (endpoint.queryMode === 'strict-since') {
|
|
382
|
+
if (page.requestedSince !== expectedQueryAfter)
|
|
383
|
+
errors.push(`page since proof mismatch: ${endpoint.endpoint}#${page.index}`);
|
|
384
|
+
}
|
|
385
|
+
else if (page.requestedSince !== undefined) {
|
|
386
|
+
errors.push(`full-enumeration page has requestedSince: ${endpoint.endpoint}#${page.index}`);
|
|
387
|
+
}
|
|
388
|
+
if (page.responseDate && new Date(page.responseDate).getTime() < preflight.getTime())
|
|
389
|
+
errors.push(`page Date precedes preflight: ${endpoint.endpoint}#${page.index}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
for (const endpoint of manifest.endpoints) {
|
|
393
|
+
for (const page of endpoint.pages) {
|
|
394
|
+
const matches = manifest.objects.filter((object) => object.role === 'page-evidence'
|
|
395
|
+
&& object.endpoint === endpoint.endpoint
|
|
396
|
+
&& object.page === page.index
|
|
397
|
+
&& object.sha256 === page.canonicalSha256);
|
|
398
|
+
if (matches.length !== 1) {
|
|
399
|
+
errors.push(`page evidence CAS reference is missing or ambiguous: ${endpoint.endpoint}#${page.index}`);
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
const object = matches[0];
|
|
403
|
+
if (object.queryMode !== 'strict-since' && object.queryMode !== 'full-enumeration') {
|
|
404
|
+
errors.push(`page evidence query mode is invalid: ${endpoint.endpoint}#${page.index}`);
|
|
405
|
+
}
|
|
406
|
+
if (object.queryMode !== page.queryMode
|
|
407
|
+
|| object.requestedSince !== page.requestedSince
|
|
408
|
+
|| object.responseDate !== page.responseDate) {
|
|
409
|
+
errors.push(`page evidence metadata mismatch: ${endpoint.endpoint}#${page.index}`);
|
|
410
|
+
}
|
|
411
|
+
if (endpoint.queryMode === 'strict-since' && object.requestedSince !== expectedQueryAfter) {
|
|
412
|
+
errors.push(`page evidence since proof mismatch: ${endpoint.endpoint}#${page.index}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
const { manifestSha256, ...partial } = manifest;
|
|
417
|
+
if (manifestDigest(partial) !== manifestSha256)
|
|
418
|
+
errors.push('manifest hash mismatch');
|
|
419
|
+
const checkMode = (target, expected, label) => {
|
|
420
|
+
if (process.platform === 'win32' || !fs.existsSync(target))
|
|
421
|
+
return;
|
|
422
|
+
const actual = fs.statSync(target).mode & 0o777;
|
|
423
|
+
if (actual !== expected)
|
|
424
|
+
errors.push(`${label} permission mismatch: expected ${expected.toString(8)}, got ${actual.toString(8)}`);
|
|
425
|
+
};
|
|
426
|
+
checkMode(snapshotPath, 0o700, 'snapshot directory');
|
|
427
|
+
for (const file of ['manifest.json', 'normalized.jsonl', 'quality.json', 'repair-manifest.jsonl'])
|
|
428
|
+
checkMode(path.join(snapshotPath, file), 0o600, file);
|
|
429
|
+
const store = createObjectStore(root);
|
|
430
|
+
const normalizedEvidence = [];
|
|
431
|
+
try {
|
|
432
|
+
const lines = fs.readFileSync(path.join(snapshotPath, 'normalized.jsonl'), 'utf8').split('\n').filter(Boolean);
|
|
433
|
+
const ids = new Set();
|
|
434
|
+
const operationCounts = { upsert: 0, supersede: 0, tombstone: 0, unavailable: 0 };
|
|
435
|
+
for (const line of lines) {
|
|
436
|
+
const record = JSON.parse(line);
|
|
437
|
+
if (record.schema !== 'normalized/v2' || typeof record.recordId !== 'string')
|
|
438
|
+
errors.push('normalized v2 record schema is invalid');
|
|
439
|
+
if (typeof record.recordId === 'string' && ids.has(record.recordId))
|
|
440
|
+
errors.push(`duplicate normalized record: ${record.recordId}`);
|
|
441
|
+
if (typeof record.recordId === 'string')
|
|
442
|
+
ids.add(record.recordId);
|
|
443
|
+
if (!record.operation || !(record.operation in operationCounts))
|
|
444
|
+
errors.push('normalized v2 operation is invalid');
|
|
445
|
+
else
|
|
446
|
+
operationCounts[record.operation] += 1;
|
|
447
|
+
if ((record.operation === 'upsert' || record.operation === 'supersede')
|
|
448
|
+
&& (!Array.isArray(record.evidence) || record.evidence.length !== 1)) {
|
|
449
|
+
errors.push(`resource evidence is required for ${record.operation}: ${record.recordId ?? 'unknown'}`);
|
|
450
|
+
}
|
|
451
|
+
for (const evidence of record.evidence ?? []) {
|
|
452
|
+
if (typeof evidence.resourceIdentity === 'string' && typeof evidence.resourceSha256 === 'string' && typeof evidence.pageSha256 === 'string') {
|
|
453
|
+
if (record.resourceIdentity !== evidence.resourceIdentity || record.sourceSha256 !== evidence.resourceSha256) {
|
|
454
|
+
errors.push(`normalized v2 evidence does not match record source: ${record.recordId ?? 'unknown'}`);
|
|
455
|
+
}
|
|
456
|
+
normalizedEvidence.push({ resourceIdentity: evidence.resourceIdentity, resourceSha256: evidence.resourceSha256, pageSha256: evidence.pageSha256 });
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
errors.push('normalized v2 evidence reference is invalid');
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (lines.length !== manifest.recordCount)
|
|
464
|
+
errors.push('normalized record count mismatch');
|
|
465
|
+
if (canonicalJsonBytes(operationCounts).toString('utf8') !== canonicalJsonBytes(manifest.operations).toString('utf8'))
|
|
466
|
+
errors.push('operation count mismatch');
|
|
467
|
+
const quality = JSON.parse(fs.readFileSync(path.join(snapshotPath, 'quality.json'), 'utf8'));
|
|
468
|
+
if (quality.schema !== 'quality/v1' || !Array.isArray(quality.findings))
|
|
469
|
+
errors.push('quality schema is invalid');
|
|
470
|
+
else if (quality.findings.length !== manifest.findingCount)
|
|
471
|
+
errors.push('quality finding count mismatch');
|
|
472
|
+
const repairLines = fs.readFileSync(path.join(snapshotPath, 'repair-manifest.jsonl'), 'utf8').split('\n').filter(Boolean);
|
|
473
|
+
if (repairLines.length !== manifest.repairCount)
|
|
474
|
+
errors.push('repair manifest count mismatch');
|
|
475
|
+
for (const line of repairLines)
|
|
476
|
+
if (JSON.parse(line).schema !== 'repair/v1')
|
|
477
|
+
errors.push('repair manifest schema is invalid');
|
|
478
|
+
}
|
|
479
|
+
catch {
|
|
480
|
+
errors.push('snapshot derived files are missing or invalid');
|
|
481
|
+
}
|
|
482
|
+
for (const object of manifest.objects) {
|
|
483
|
+
if (object.role !== 'page-evidence' && object.role !== 'resource')
|
|
484
|
+
errors.push(`object role is invalid: ${object.sourceIdentity}`);
|
|
485
|
+
if (object.disposition?.state && object.disposition.state !== 'included')
|
|
486
|
+
continue;
|
|
487
|
+
try {
|
|
488
|
+
const objectPath = store.pathFor(object.sha256);
|
|
489
|
+
const bytes = fs.readFileSync(objectPath);
|
|
490
|
+
if (sha256(bytes) !== object.sha256 || bytes.length !== object.bytes)
|
|
491
|
+
errors.push(`object integrity mismatch: ${object.sourceIdentity}`);
|
|
492
|
+
}
|
|
493
|
+
catch {
|
|
494
|
+
errors.push(`object missing: ${object.sourceIdentity}`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
for (const evidence of normalizedEvidence) {
|
|
498
|
+
const resources = manifest.objects.filter((object) => object.role === 'resource'
|
|
499
|
+
&& object.resourceIdentity === evidence.resourceIdentity
|
|
500
|
+
&& object.sha256 === evidence.resourceSha256);
|
|
501
|
+
const pages = manifest.objects.filter((object) => object.role === 'page-evidence' && object.sha256 === evidence.pageSha256);
|
|
502
|
+
if (resources.length !== 1)
|
|
503
|
+
errors.push(`resource evidence reference is missing or ambiguous: ${evidence.resourceIdentity}`);
|
|
504
|
+
if (pages.length === 0)
|
|
505
|
+
errors.push(`resource page evidence reference is missing: ${evidence.resourceIdentity}`);
|
|
506
|
+
}
|
|
507
|
+
return { ok: errors.length === 0, snapshotId, snapshotPath, errors, manifest };
|
|
508
|
+
}
|
|
509
|
+
function verifySnapshot(root, snapshotId, seen = new Set()) {
|
|
185
510
|
const snapshotPath = findSnapshot(root, snapshotId);
|
|
186
511
|
if (!snapshotPath)
|
|
187
512
|
return { ok: false, snapshotId, snapshotPath: null, errors: ['snapshot not found'] };
|
|
@@ -197,15 +522,20 @@ function verifySnapshot(root, snapshotId) {
|
|
|
197
522
|
for (const file of ['manifest.json', 'normalized.jsonl', 'quality.json', 'repair-manifest.jsonl']) {
|
|
198
523
|
checkMode(path.join(snapshotPath, file), 0o600, file);
|
|
199
524
|
}
|
|
200
|
-
let
|
|
525
|
+
let rawManifest;
|
|
201
526
|
try {
|
|
202
|
-
|
|
527
|
+
rawManifest = JSON.parse(fs.readFileSync(path.join(snapshotPath, 'manifest.json'), 'utf8'));
|
|
203
528
|
}
|
|
204
529
|
catch {
|
|
205
530
|
return { ok: false, snapshotId, snapshotPath, errors: ['manifest is invalid JSON'] };
|
|
206
531
|
}
|
|
207
|
-
if (
|
|
208
|
-
|
|
532
|
+
if (rawManifest.schema === 'raw-manifest/v2') {
|
|
533
|
+
return verifyV2Snapshot(root, snapshotId, snapshotPath, rawManifest, seen);
|
|
534
|
+
}
|
|
535
|
+
if (rawManifest.schema !== 'raw-manifest/v1') {
|
|
536
|
+
return { ok: false, snapshotId, snapshotPath, errors: [`unsupported manifest schema: ${String(rawManifest.schema)}`] };
|
|
537
|
+
}
|
|
538
|
+
const manifest = rawManifest;
|
|
209
539
|
if (manifest.snapshotId !== snapshotId)
|
|
210
540
|
errors.push('snapshot identity mismatch');
|
|
211
541
|
if (typeof manifest.excerptsEnabled !== 'boolean')
|
|
@@ -278,5 +608,5 @@ function verifySnapshot(root, snapshotId) {
|
|
|
278
608
|
}
|
|
279
609
|
return { ok: errors.length === 0, snapshotId, snapshotPath, errors, manifest };
|
|
280
610
|
}
|
|
281
|
-
export { canonicalJsonBytes, createObjectStore, findSnapshot, publishSnapshot, sha256, verifySnapshot };
|
|
611
|
+
export { canonicalJsonBytes, createObjectStore, findSnapshot, publishSnapshot, publishSnapshotV2, sha256, verifySnapshot };
|
|
282
612
|
//# sourceMappingURL=store.js.map
|
|
@@ -1,34 +1,42 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
function byteSort(values) {
|
|
3
|
+
return [...values].sort((left, right) => Buffer.from(left).compare(Buffer.from(right)));
|
|
4
|
+
}
|
|
1
5
|
function deriveReleasePhase(facts) {
|
|
2
6
|
if (!facts.localTag && !facts.localTagAncestor)
|
|
3
7
|
return 'unprepared';
|
|
8
|
+
if (facts.post.commit)
|
|
9
|
+
return facts.post.published ? 'complete' : 'post-prepared';
|
|
4
10
|
if (!facts.remoteBranch && !facts.remoteTag)
|
|
5
11
|
return 'prepared';
|
|
6
12
|
if (!facts.remoteBranch || !facts.remoteTag)
|
|
7
13
|
return 'partially-published';
|
|
8
14
|
if (facts.githubRelease !== true || facts.npm !== true || facts.homebrew !== true)
|
|
9
15
|
return 'published';
|
|
10
|
-
|
|
11
|
-
return 'post-pending';
|
|
12
|
-
return 'complete';
|
|
16
|
+
return 'post-pending';
|
|
13
17
|
}
|
|
14
|
-
function
|
|
15
|
-
|
|
18
|
+
function createPostConfirmation(version, post) {
|
|
19
|
+
if (!post.commit || !post.isHead || post.worktree.length || post.staged.length)
|
|
20
|
+
return null;
|
|
21
|
+
const canonical = {
|
|
22
|
+
version,
|
|
23
|
+
commit: post.commit,
|
|
24
|
+
branch: post.branch,
|
|
25
|
+
upstream: post.upstream,
|
|
26
|
+
remoteHead: post.remoteHead,
|
|
27
|
+
newVersion: post.newVersion,
|
|
28
|
+
changedPaths: byteSort(post.changedPaths),
|
|
29
|
+
demoInputSha256: post.demoInputSha256,
|
|
30
|
+
worktree: byteSort(post.worktree),
|
|
31
|
+
staged: byteSort(post.staged)
|
|
32
|
+
};
|
|
33
|
+
const sha256 = `sha256:${crypto.createHash('sha256').update(JSON.stringify(canonical), 'utf8').digest('hex')}`;
|
|
34
|
+
return { ...canonical, sha256 };
|
|
16
35
|
}
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (before.facts.localTagConflict)
|
|
22
|
-
return { status: 'failed', changed: false, before, snapshot: before, error: { code: 'GIT_TAG_CONFLICT', message: `Tag ${before.tag} is not reachable from HEAD` } };
|
|
23
|
-
const allowed = input.action === 'prepare' ? before.phase === 'unprepared'
|
|
24
|
-
: input.action === 'publish' ? before.facts.localTag && ['prepared', 'partially-published'].includes(before.phase)
|
|
25
|
-
: ['published', 'post-pending'].includes(before.phase);
|
|
26
|
-
if (!allowed)
|
|
27
|
-
return { status: before.phase === 'complete' ? 'no-op' : 'failed', changed: false, before, snapshot: before, error: before.phase === 'complete' ? null : { code: 'RELEASE_PHASE_INVALID', message: `Cannot ${input.action} from ${before.phase}` } };
|
|
28
|
-
await input[input.action]?.();
|
|
29
|
-
const snapshot = releaseSnapshot(input.version, await input.inspect());
|
|
30
|
-
const progressed = snapshot.phase !== before.phase;
|
|
31
|
-
return { status: progressed ? 'applied' : 'blocked', changed: progressed, before, snapshot, error: progressed ? null : { code: 'RELEASE_PROGRESS_PENDING', message: `${input.action} has not reached the next observable phase` } };
|
|
36
|
+
function releaseSnapshot(version, facts) {
|
|
37
|
+
const snapshot = { version, tag: `v${version.replace(/^v/, '')}`, phase: deriveReleasePhase(facts), facts };
|
|
38
|
+
const postConfirmation = createPostConfirmation(version, facts.post);
|
|
39
|
+
return postConfirmation ? { ...snapshot, postConfirmation } : snapshot;
|
|
32
40
|
}
|
|
33
|
-
export { deriveReleasePhase, releaseSnapshot
|
|
41
|
+
export { createPostConfirmation, deriveReleasePhase, releaseSnapshot };
|
|
34
42
|
//# sourceMappingURL=workflow.js.map
|