codex-orchestrator 2.0.1 → 2.0.3
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/CHANGELOG.md +22 -0
- package/README.md +12 -9
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/v2/acceptance-proof.d.ts +3 -0
- package/dist/src/v2/acceptance-proof.d.ts.map +1 -1
- package/dist/src/v2/acceptance-proof.js +2 -8
- package/dist/src/v2/acceptance-proof.js.map +1 -1
- package/dist/src/v2/adapters/gh-issue-adapter.d.ts +5 -3
- package/dist/src/v2/adapters/gh-issue-adapter.d.ts.map +1 -1
- package/dist/src/v2/adapters/gh-issue-adapter.js +63 -7
- package/dist/src/v2/adapters/gh-issue-adapter.js.map +1 -1
- package/dist/src/v2/adapters/issues.d.ts +16 -2
- package/dist/src/v2/adapters/issues.d.ts.map +1 -1
- package/dist/src/v2/adapters/issues.js +15 -5
- package/dist/src/v2/adapters/issues.js.map +1 -1
- package/dist/src/v2/adapters/mission-coordinator-lock.d.ts +1 -0
- package/dist/src/v2/adapters/mission-coordinator-lock.d.ts.map +1 -1
- package/dist/src/v2/adapters/mission-coordinator-lock.js +5 -1
- package/dist/src/v2/adapters/mission-coordinator-lock.js.map +1 -1
- package/dist/src/v2/candidate-cli.d.ts +4 -0
- package/dist/src/v2/candidate-cli.d.ts.map +1 -1
- package/dist/src/v2/candidate-cli.js +26 -11
- package/dist/src/v2/candidate-cli.js.map +1 -1
- package/dist/src/v2/cli-contract.d.ts +1 -1
- package/dist/src/v2/cli-contract.d.ts.map +1 -1
- package/dist/src/v2/cli-contract.js +10 -0
- package/dist/src/v2/cli-contract.js.map +1 -1
- package/dist/src/v2/code-review-report.d.ts +66 -0
- package/dist/src/v2/code-review-report.d.ts.map +1 -0
- package/dist/src/v2/code-review-report.js +259 -0
- package/dist/src/v2/code-review-report.js.map +1 -0
- package/dist/src/v2/codex-process.d.ts +8 -1
- package/dist/src/v2/codex-process.d.ts.map +1 -1
- package/dist/src/v2/codex-process.js +11 -0
- package/dist/src/v2/codex-process.js.map +1 -1
- package/dist/src/v2/config.d.ts +2 -1
- package/dist/src/v2/config.d.ts.map +1 -1
- package/dist/src/v2/config.js +8 -3
- package/dist/src/v2/config.js.map +1 -1
- package/dist/src/v2/contained-report-operation.d.ts +100 -0
- package/dist/src/v2/contained-report-operation.d.ts.map +1 -0
- package/dist/src/v2/contained-report-operation.js +200 -0
- package/dist/src/v2/contained-report-operation.js.map +1 -0
- package/dist/src/v2/containment.d.ts +6 -0
- package/dist/src/v2/containment.d.ts.map +1 -1
- package/dist/src/v2/containment.js +40 -1
- package/dist/src/v2/containment.js.map +1 -1
- package/dist/src/v2/direct-delivery.d.ts +101 -0
- package/dist/src/v2/direct-delivery.d.ts.map +1 -0
- package/dist/src/v2/direct-delivery.js +547 -0
- package/dist/src/v2/direct-delivery.js.map +1 -0
- package/dist/src/v2/immutable-workflow-publisher.d.ts +40 -0
- package/dist/src/v2/immutable-workflow-publisher.d.ts.map +1 -0
- package/dist/src/v2/immutable-workflow-publisher.js +218 -0
- package/dist/src/v2/immutable-workflow-publisher.js.map +1 -0
- package/dist/src/v2/implementation-reviewer.d.ts +81 -0
- package/dist/src/v2/implementation-reviewer.d.ts.map +1 -0
- package/dist/src/v2/implementation-reviewer.js +157 -0
- package/dist/src/v2/implementation-reviewer.js.map +1 -0
- package/dist/src/v2/owner-control-lock.d.ts +41 -0
- package/dist/src/v2/owner-control-lock.d.ts.map +1 -0
- package/dist/src/v2/owner-control-lock.js +174 -0
- package/dist/src/v2/owner-control-lock.js.map +1 -0
- package/dist/src/v2/route-continuations.d.ts +32 -0
- package/dist/src/v2/route-continuations.d.ts.map +1 -0
- package/dist/src/v2/route-continuations.js +2 -0
- package/dist/src/v2/route-continuations.js.map +1 -0
- package/dist/src/v2/route-coordinator.d.ts +77 -0
- package/dist/src/v2/route-coordinator.d.ts.map +1 -0
- package/dist/src/v2/route-coordinator.js +370 -0
- package/dist/src/v2/route-coordinator.js.map +1 -0
- package/dist/src/v2/route-decision.d.ts +129 -0
- package/dist/src/v2/route-decision.d.ts.map +1 -0
- package/dist/src/v2/route-decision.js +400 -0
- package/dist/src/v2/route-decision.js.map +1 -0
- package/dist/src/v2/run-issue.d.ts +63 -2
- package/dist/src/v2/run-issue.d.ts.map +1 -1
- package/dist/src/v2/run-issue.js +906 -91
- package/dist/src/v2/run-issue.js.map +1 -1
- package/dist/src/v2/run-store.d.ts +25 -1
- package/dist/src/v2/run-store.d.ts.map +1 -1
- package/dist/src/v2/run-store.js +143 -3
- package/dist/src/v2/run-store.js.map +1 -1
- package/dist/src/v2/runtime-assets.d.ts +15 -13
- package/dist/src/v2/runtime-assets.d.ts.map +1 -1
- package/dist/src/v2/runtime-assets.js +263 -416
- package/dist/src/v2/runtime-assets.js.map +1 -1
- package/dist/src/v2/runtime.d.ts +14 -6
- package/dist/src/v2/runtime.d.ts.map +1 -1
- package/dist/src/v2/runtime.js +478 -56
- package/dist/src/v2/runtime.js.map +1 -1
- package/dist/src/v2/setup-cli.d.ts.map +1 -1
- package/dist/src/v2/setup-cli.js +1 -0
- package/dist/src/v2/setup-cli.js.map +1 -1
- package/dist/src/v2/setup-runtime.d.ts.map +1 -1
- package/dist/src/v2/setup-runtime.js +20 -72
- package/dist/src/v2/setup-runtime.js.map +1 -1
- package/dist/src/v2/setup.d.ts +4 -1
- package/dist/src/v2/setup.d.ts.map +1 -1
- package/dist/src/v2/setup.js +104 -1
- package/dist/src/v2/setup.js.map +1 -1
- package/dist/src/v2/spec-coordinator.d.ts +85 -0
- package/dist/src/v2/spec-coordinator.d.ts.map +1 -0
- package/dist/src/v2/spec-coordinator.js +88 -0
- package/dist/src/v2/spec-coordinator.js.map +1 -0
- package/dist/src/v2/spec-delivery.d.ts +143 -0
- package/dist/src/v2/spec-delivery.d.ts.map +1 -0
- package/dist/src/v2/spec-delivery.js +401 -0
- package/dist/src/v2/spec-delivery.js.map +1 -0
- package/dist/src/v2/triage-route.d.ts +68 -0
- package/dist/src/v2/triage-route.d.ts.map +1 -0
- package/dist/src/v2/triage-route.js +223 -0
- package/dist/src/v2/triage-route.js.map +1 -0
- package/dist/src/v2/waiting-human-coordinator.d.ts +49 -0
- package/dist/src/v2/waiting-human-coordinator.d.ts.map +1 -0
- package/dist/src/v2/waiting-human-coordinator.js +509 -0
- package/dist/src/v2/waiting-human-coordinator.js.map +1 -0
- package/dist/src/v2/waiting-human.d.ts +143 -0
- package/dist/src/v2/waiting-human.d.ts.map +1 -0
- package/dist/src/v2/waiting-human.js +408 -0
- package/dist/src/v2/waiting-human.js.map +1 -0
- package/dist/src/v2/workflow-assets.d.ts +90 -0
- package/dist/src/v2/workflow-assets.d.ts.map +1 -0
- package/dist/src/v2/workflow-assets.js +554 -0
- package/dist/src/v2/workflow-assets.js.map +1 -0
- package/docs/deep-dive.md +15 -8
- package/internal-workflow/docs/agents/artifact-review-loop.md +267 -0
- package/internal-workflow/docs/agents/bug-workflow-routing.md +24 -0
- package/internal-workflow/docs/agents/coding-skill-routing.md +203 -0
- package/internal-workflow/docs/agents/confidence-rubric.md +65 -0
- package/internal-workflow/docs/agents/contract-test-ledger.md +60 -0
- package/internal-workflow/docs/agents/implementation-review-loop.md +302 -0
- package/internal-workflow/docs/agents/review-gates.md +49 -0
- package/internal-workflow/docs/agents/review-protocol.md +170 -0
- package/internal-workflow/docs/agents/tool-usage.md +88 -0
- package/internal-workflow/manifest.json +1 -0
- package/internal-workflow/operations/acceptance-proof/SKILL.md +3 -0
- package/internal-workflow/operations/ambiguity-review/SKILL.md +3 -0
- package/internal-workflow/operations/cleanup-review/SKILL.md +3 -0
- package/internal-workflow/operations/code-review/SKILL.md +3 -0
- package/internal-workflow/operations/implementation/SKILL.md +3 -0
- package/internal-workflow/operations/spec-author/SKILL.md +3 -0
- package/internal-workflow/operations/spec-implementation/SKILL.md +3 -0
- package/internal-workflow/operations/spec-review/SKILL.md +3 -0
- package/internal-workflow/operations/triage/SKILL.md +3 -0
- package/internal-workflow/profiles/analyst_deep.toml +9 -0
- package/internal-workflow/profiles/implementer_deep.toml +9 -0
- package/internal-workflow/profiles/implementer_standard.toml +9 -0
- package/internal-workflow/profiles/proof_agent.toml +8 -0
- package/internal-workflow/profiles/researcher_standard.toml +9 -0
- package/internal-workflow/profiles/reviewer_deep.toml +9 -0
- package/internal-workflow/profiles/reviewer_fast.toml +9 -0
- package/internal-workflow/profiles/reviewer_standard.toml +9 -0
- package/internal-workflow/schemas/ambiguity-review-v1.json +1 -0
- package/internal-workflow/schemas/code-review-v1.json +1 -0
- package/internal-workflow/schemas/implementation-report-v1.json +1 -0
- package/internal-workflow/schemas/proof-report-v1.json +1 -0
- package/internal-workflow/schemas/spec-author-v1.json +1 -0
- package/internal-workflow/schemas/spec-review-v1.json +30 -0
- package/internal-workflow/schemas/triage-route-v1.json +1 -0
- package/internal-workflow/skills/acceptance-proof/agents/openai.yaml +6 -0
- package/internal-workflow/skills/agent-auto/agents/openai.yaml +6 -0
- package/internal-workflow/skills/cleanup-review/SKILL.md +84 -0
- package/internal-workflow/skills/cleanup-review/agents/openai.yaml +6 -0
- package/internal-workflow/skills/code-review/SKILL.md +257 -0
- package/internal-workflow/skills/code-review/agents/openai.yaml +4 -0
- package/internal-workflow/skills/code-review/references/bug-classes.md +56 -0
- package/internal-workflow/skills/code-review/references/framework-lenses.md +34 -0
- package/internal-workflow/skills/code-review/references/targeted-recipes.md +49 -0
- package/internal-workflow/skills/codebase-design/DEEPENING.md +35 -0
- package/internal-workflow/skills/codebase-design/DESIGN-IT-TWICE.md +50 -0
- package/internal-workflow/skills/codebase-design/SKILL.md +82 -0
- package/internal-workflow/skills/codebase-design/agents/openai.yaml +6 -0
- package/internal-workflow/skills/diagnosing-bugs/SKILL.md +138 -0
- package/internal-workflow/skills/diagnosing-bugs/agents/openai.yaml +6 -0
- package/internal-workflow/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
- package/internal-workflow/skills/implementation-spec-maker/SKILL.md +93 -0
- package/internal-workflow/skills/implementation-spec-maker/agents/openai.yaml +6 -0
- package/internal-workflow/skills/implementation-spec-maker/references/source-modes.md +31 -0
- package/internal-workflow/skills/implementation-spec-maker/references/spec-template.md +146 -0
- package/internal-workflow/skills/implementation-spec-review/SKILL.md +211 -0
- package/internal-workflow/skills/implementation-spec-review/agents/openai.yaml +6 -0
- package/internal-workflow/skills/research/SKILL.md +107 -0
- package/internal-workflow/skills/research/agents/openai.yaml +6 -0
- package/internal-workflow/skills/small-task-implementer/SKILL.md +97 -0
- package/internal-workflow/skills/small-task-implementer/agents/openai.yaml +6 -0
- package/internal-workflow/skills/spec-implementer/SKILL.md +197 -0
- package/internal-workflow/skills/spec-implementer/agents/openai.yaml +6 -0
- package/internal-workflow/skills/tdd/SKILL.md +59 -0
- package/internal-workflow/skills/tdd/agents/openai.yaml +6 -0
- package/internal-workflow/skills/tdd/interface-design.md +31 -0
- package/internal-workflow/skills/tdd/mocking.md +59 -0
- package/internal-workflow/skills/tdd/refactoring.md +10 -0
- package/internal-workflow/skills/tdd/tests.md +77 -0
- package/internal-workflow/skills/triage/AGENT-BRIEF.md +192 -0
- package/internal-workflow/skills/triage/OUT-OF-SCOPE.md +101 -0
- package/internal-workflow/skills/triage/SKILL.md +134 -0
- package/internal-workflow/skills/triage/agents/openai.yaml +6 -0
- package/internal-workflow/skills/ui-evidence-proof/SKILL.md +123 -0
- package/internal-workflow/skills/ui-evidence-proof/agents/openai.yaml +6 -0
- package/package.json +6 -3
- /package/{internal-skills → internal-workflow/skills}/acceptance-proof/SKILL.md +0 -0
- /package/{internal-skills → internal-workflow/skills}/acceptance-proof/references/android.md +0 -0
- /package/{internal-skills → internal-workflow/skills}/acceptance-proof/references/browser.md +0 -0
- /package/{internal-skills → internal-workflow/skills}/acceptance-proof/references/ios.md +0 -0
- /package/{internal-skills → internal-workflow/skills}/acceptance-proof/tools/android-lease.mjs +0 -0
- /package/{internal-skills → internal-workflow/skills}/acceptance-proof/tools/ios-lease.mjs +0 -0
- /package/{internal-skills → internal-workflow/skills}/agent-auto/SKILL.md +0 -0
|
@@ -1,412 +1,243 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
const
|
|
8
|
-
const SNAPSHOT_FILE_MODE = 0o400;
|
|
9
|
-
const LOCK_WAIT_MS = 5_000;
|
|
10
|
-
const LOCK_POLL_MS = 25;
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { O_NOFOLLOW, O_RDONLY } from 'node:constants';
|
|
3
|
+
import { chmod, lstat, mkdir, open, readdir, realpath, stat, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep } from 'node:path';
|
|
5
|
+
import { resolveWorkflowOperation, sealedWorkflowContentSha256, sealedWorkflowMode, verifyWorkflowGeneration, } from './workflow-assets.js';
|
|
6
|
+
import { publishImmutableWorkflow } from './immutable-workflow-publisher.js';
|
|
7
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
|
|
11
8
|
export async function publishRuntimeAssetSnapshot(input) {
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
assertContained(
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
await
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
await handle.writeFile(bytes);
|
|
50
|
-
await handle.sync();
|
|
9
|
+
await verifyWorkflowGeneration(input.workflowGeneration);
|
|
10
|
+
const operation = await resolveWorkflowOperation(input.workflowGeneration, input.operation);
|
|
11
|
+
const requestedRuntimeRoot = resolve(input.runtimeRoot);
|
|
12
|
+
const requested = validateRelativePath(input.snapshotRelativePath);
|
|
13
|
+
const requestedLogicalRoot = resolve(requestedRuntimeRoot, ...requested.split('/'));
|
|
14
|
+
assertContained(requestedRuntimeRoot, requestedLogicalRoot);
|
|
15
|
+
await ensureManagedPath(requestedRuntimeRoot, dirname(requestedLogicalRoot));
|
|
16
|
+
const runtimeRoot = await realpath(requestedRuntimeRoot);
|
|
17
|
+
const logicalRoot = resolve(runtimeRoot, ...requested.split('/'));
|
|
18
|
+
const parent = dirname(logicalRoot);
|
|
19
|
+
const stem = basename(logicalRoot);
|
|
20
|
+
return publishImmutableWorkflow({
|
|
21
|
+
parent,
|
|
22
|
+
identity: stem,
|
|
23
|
+
bootId: input.bootId,
|
|
24
|
+
createOwner: (parentToken, processStartIdentity) => newOwner(input.bootId, parentToken, processStartIdentity),
|
|
25
|
+
parseOwner,
|
|
26
|
+
createReady: (owner, contentSha256) => ({
|
|
27
|
+
version: 1,
|
|
28
|
+
status: 'ready',
|
|
29
|
+
token: owner.token,
|
|
30
|
+
generationHash: input.workflowGeneration.generationHash,
|
|
31
|
+
operation: input.operation,
|
|
32
|
+
contentSha256,
|
|
33
|
+
}),
|
|
34
|
+
parseReady,
|
|
35
|
+
serializeRecord: (value) => Buffer.from(`${canonicalJson(value)}\n`),
|
|
36
|
+
readControl: readRegular,
|
|
37
|
+
writeContent: async (contentRoot, onStep) => {
|
|
38
|
+
for (const [index, file] of operation.files.entries()) {
|
|
39
|
+
const source = join(input.workflowGeneration.generationRoot, ...file.path.split('/'));
|
|
40
|
+
const target = join(contentRoot, ...file.path.split('/'));
|
|
41
|
+
await mkdir(dirname(target), { recursive: true, mode: 0o700 });
|
|
42
|
+
await writeSynced(target, await readRegular(source), sealedWorkflowMode(file.mode));
|
|
43
|
+
if (index === 0)
|
|
44
|
+
await onStep('after-first-content-file');
|
|
51
45
|
}
|
|
52
|
-
|
|
53
|
-
await
|
|
46
|
+
for (const directory of (await listDirectories(contentRoot)).sort((left, right) => right.length - left.length)) {
|
|
47
|
+
await chmod(directory, 0o555);
|
|
48
|
+
await syncDirectory(directory);
|
|
54
49
|
}
|
|
55
|
-
await
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
snapshotRoot: tempRoot,
|
|
63
|
-
expectedUid,
|
|
64
|
-
reused: false,
|
|
65
|
-
});
|
|
66
|
-
await verifyRuntimeAssetSnapshot(tempEvidence);
|
|
67
|
-
await input.onStep?.('before-temp-directory-sync');
|
|
68
|
-
await syncDirectory(tempRoot);
|
|
69
|
-
await input.onStep?.('after-temp-directory-sync');
|
|
70
|
-
await assertSourceUnchanged(source);
|
|
71
|
-
await input.onStep?.('before-rename');
|
|
72
|
-
if (await pathExists(snapshotRoot)) {
|
|
73
|
-
const raced = expectedSnapshot({ source, runtimeRoot, snapshotRoot, expectedUid, reused: true });
|
|
74
|
-
await verifyRuntimeAssetSnapshot(raced);
|
|
75
|
-
await assertSourceUnchanged(source);
|
|
76
|
-
return raced;
|
|
77
|
-
}
|
|
78
|
-
await rename(tempRoot, snapshotRoot);
|
|
79
|
-
published = true;
|
|
80
|
-
await input.onStep?.('after-rename');
|
|
81
|
-
await syncDirectory(attemptRoot);
|
|
82
|
-
await input.onStep?.('after-parent-sync');
|
|
83
|
-
const snapshot = expectedSnapshot({ source, runtimeRoot, snapshotRoot, expectedUid, reused: false });
|
|
84
|
-
await verifyRuntimeAssetSnapshot(snapshot);
|
|
85
|
-
await assertSourceUnchanged(source);
|
|
86
|
-
return snapshot;
|
|
87
|
-
}
|
|
88
|
-
finally {
|
|
89
|
-
if (tempRoot && !published)
|
|
90
|
-
await rm(tempRoot, { recursive: true, force: true });
|
|
91
|
-
await releasePublishLock(lockPath, lock.token);
|
|
92
|
-
}
|
|
50
|
+
return contentDigest(await evidence(contentRoot, operation.files));
|
|
51
|
+
},
|
|
52
|
+
resultFromReady: ({ ready, contentRoot, reused }) => snapshotFromReady(input, operation, runtimeRoot, ready, contentRoot, reused),
|
|
53
|
+
activePublisherError: 'runtime asset publisher is still active',
|
|
54
|
+
recoveryChainError: 'runtime asset recovery chain is invalid',
|
|
55
|
+
onStep: input.onStep,
|
|
56
|
+
});
|
|
93
57
|
}
|
|
94
58
|
export async function verifyRuntimeAssetSnapshot(snapshot) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
assertOwner(rootStat.uid, expectedUid, snapshotRoot);
|
|
107
|
-
assertMode(rootStat.mode, SNAPSHOT_ROOT_MODE, snapshotRoot);
|
|
108
|
-
const expectedNames = snapshot.skill === 'acceptance-proof'
|
|
109
|
-
? [
|
|
110
|
-
'SKILL.md', 'output-schema.json', 'references/android.md', 'references/browser.md', 'references/ios.md',
|
|
111
|
-
'tools/android-lease.mjs', 'tools/ios-lease.mjs',
|
|
112
|
-
]
|
|
113
|
-
: ['SKILL.md', 'output-schema.json'];
|
|
114
|
-
const actualNames = await snapshotFilePaths(snapshotRoot, expectedUid);
|
|
115
|
-
if (actualNames.length !== expectedNames.length || actualNames.some((name, index) => name !== expectedNames[index])) {
|
|
116
|
-
throw new Error('snapshot has missing or extra entries');
|
|
117
|
-
}
|
|
118
|
-
if (snapshot.files.length !== expectedNames.length)
|
|
119
|
-
throw new Error('snapshot evidence file list is invalid');
|
|
120
|
-
const evidenceNames = snapshot.files.map((file) => file.relativePath);
|
|
121
|
-
if (evidenceNames.some((name, index) => name !== expectedNames[index]))
|
|
122
|
-
throw new Error('snapshot evidence file order is invalid');
|
|
123
|
-
for (const file of snapshot.files) {
|
|
124
|
-
if (file.ownerUid !== expectedUid)
|
|
125
|
-
throw new Error(`snapshot evidence owner drift: ${file.relativePath}`);
|
|
126
|
-
if (file.mode !== SNAPSHOT_FILE_MODE)
|
|
127
|
-
throw new Error(`snapshot evidence mode drift: ${file.relativePath}`);
|
|
128
|
-
const path = join(snapshotRoot, file.relativePath);
|
|
129
|
-
const info = await lstat(path);
|
|
130
|
-
if (info.isSymbolicLink())
|
|
131
|
-
throw new Error(`snapshot contains a symbolic link: ${file.relativePath}`);
|
|
132
|
-
if (!info.isFile())
|
|
133
|
-
throw new Error(`snapshot entry is not a regular file: ${file.relativePath}`);
|
|
134
|
-
assertOwner(info.uid, expectedUid, path);
|
|
135
|
-
assertMode(info.mode, SNAPSHOT_FILE_MODE, path);
|
|
136
|
-
const bytes = await readFile(path);
|
|
137
|
-
if (bytes.length !== file.size)
|
|
138
|
-
throw new Error(`snapshot size mismatch: ${file.relativePath}`);
|
|
139
|
-
if (sha256(bytes) !== file.sha256)
|
|
140
|
-
throw new Error(`snapshot hash mismatch: ${file.relativePath}`);
|
|
141
|
-
}
|
|
142
|
-
const skillPath = join(snapshotRoot, 'SKILL.md');
|
|
143
|
-
const schemaPath = join(snapshotRoot, 'output-schema.json');
|
|
144
|
-
if (resolve(snapshot.skillPath) !== skillPath || resolve(snapshot.schemaPath) !== schemaPath) {
|
|
145
|
-
throw new Error('snapshot evidence paths are invalid');
|
|
59
|
+
if (snapshot.ownerUid !== runnerUid())
|
|
60
|
+
throw new Error('runtime asset owner drift');
|
|
61
|
+
const root = resolve(snapshot.snapshotRoot);
|
|
62
|
+
assertContained(resolve(snapshot.runtimeRoot), root);
|
|
63
|
+
if (await realpath(root) !== root)
|
|
64
|
+
throw new Error('runtime asset snapshot contains a symbolic link');
|
|
65
|
+
for (const directory of await listDirectories(root)) {
|
|
66
|
+
const info = await lstat(directory);
|
|
67
|
+
if (!info.isDirectory() || info.isSymbolicLink() || info.uid !== runnerUid() || (info.mode & 0o777) !== 0o555) {
|
|
68
|
+
throw new Error('runtime asset directory mode or owner drift');
|
|
69
|
+
}
|
|
146
70
|
}
|
|
147
|
-
|
|
148
|
-
|
|
71
|
+
const actual = await listFiles(root);
|
|
72
|
+
const expected = snapshot.files.map((file) => file.path);
|
|
73
|
+
if (!same(actual, expected))
|
|
74
|
+
throw new Error('runtime asset file closure drift');
|
|
75
|
+
const current = await evidence(root, snapshot.files);
|
|
76
|
+
if (canonicalJson(current) !== canonicalJson(snapshot.files))
|
|
77
|
+
throw new Error('runtime asset evidence drift');
|
|
78
|
+
if (contentDigest(current) !== snapshot.contentSha256)
|
|
79
|
+
throw new Error('runtime asset content digest drift');
|
|
80
|
+
for (const [path, expectedPath] of [
|
|
81
|
+
[snapshot.operationPath, `operations/${snapshot.operation}/SKILL.md`],
|
|
82
|
+
[snapshot.schemaPath, snapshot.files.find((file) => resolve(root, ...file.path.split('/')) === resolve(snapshot.schemaPath))?.path],
|
|
83
|
+
[snapshot.profilePath, snapshot.files.find((file) => resolve(root, ...file.path.split('/')) === resolve(snapshot.profilePath))?.path],
|
|
84
|
+
]) {
|
|
85
|
+
if (typeof path !== 'string' || typeof expectedPath !== 'string'
|
|
86
|
+
|| resolve(path) !== resolve(root, ...expectedPath.split('/')))
|
|
87
|
+
throw new Error('runtime asset authority path drift');
|
|
149
88
|
}
|
|
150
89
|
}
|
|
151
|
-
async function
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const supplementalPaths = skill === 'acceptance-proof'
|
|
155
|
-
? [
|
|
156
|
-
{ relativePath: 'references/android.md', sourcePath: join(packageRoot, 'internal-skills', skill, 'references', 'android.md') },
|
|
157
|
-
{ relativePath: 'references/browser.md', sourcePath: join(packageRoot, 'internal-skills', skill, 'references', 'browser.md') },
|
|
158
|
-
{ relativePath: 'references/ios.md', sourcePath: join(packageRoot, 'internal-skills', skill, 'references', 'ios.md') },
|
|
159
|
-
{ relativePath: 'tools/android-lease.mjs', sourcePath: join(packageRoot, 'internal-skills', skill, 'tools', 'android-lease.mjs') },
|
|
160
|
-
{ relativePath: 'tools/ios-lease.mjs', sourcePath: join(packageRoot, 'internal-skills', skill, 'tools', 'ios-lease.mjs') },
|
|
161
|
-
]
|
|
162
|
-
: [];
|
|
163
|
-
await assertSourcePath(packageRoot, packageJsonPath, true);
|
|
164
|
-
await assertSourcePath(packageRoot, skillSourcePath, true);
|
|
165
|
-
await Promise.all(supplementalPaths.map((asset) => assertSourcePath(packageRoot, asset.sourcePath, true)));
|
|
166
|
-
const [packageJsonBytes, skillBytes, supplementalBytes] = await Promise.all([
|
|
167
|
-
readFile(packageJsonPath),
|
|
168
|
-
readFile(skillSourcePath),
|
|
169
|
-
Promise.all(supplementalPaths.map((asset) => readFile(asset.sourcePath))),
|
|
170
|
-
]);
|
|
171
|
-
const packageJson = JSON.parse(packageJsonBytes.toString('utf8'));
|
|
172
|
-
if (packageJson.name !== 'codex-orchestrator' || typeof packageJson.version !== 'string' || packageJson.version.length === 0) {
|
|
173
|
-
throw new Error('package.json does not identify a versioned codex-orchestrator package');
|
|
174
|
-
}
|
|
175
|
-
const schema = skill === 'agent-auto' ? implementationReportOutputSchema() : proofReportOutputSchema();
|
|
176
|
-
return {
|
|
177
|
-
packageRoot,
|
|
178
|
-
packageJsonPath,
|
|
179
|
-
packageJsonBytes,
|
|
180
|
-
packageVersion: packageJson.version,
|
|
181
|
-
skill,
|
|
182
|
-
skillSourcePath,
|
|
183
|
-
skillBytes,
|
|
184
|
-
schemaBytes: Buffer.from(`${canonicalJson(schema)}\n`, 'utf8'),
|
|
185
|
-
supplementalAssets: supplementalPaths.map((asset, index) => ({ ...asset, bytes: supplementalBytes[index] })),
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
async function assertSourceUnchanged(source) {
|
|
189
|
-
await assertSourcePath(source.packageRoot, source.packageJsonPath, true);
|
|
190
|
-
await assertSourcePath(source.packageRoot, source.skillSourcePath, true);
|
|
191
|
-
await Promise.all(source.supplementalAssets.map((asset) => assertSourcePath(source.packageRoot, asset.sourcePath, true)));
|
|
192
|
-
const [packageJsonBytes, skillBytes, supplementalBytes] = await Promise.all([
|
|
193
|
-
readFile(source.packageJsonPath),
|
|
194
|
-
readFile(source.skillSourcePath),
|
|
195
|
-
Promise.all(source.supplementalAssets.map((asset) => readFile(asset.sourcePath))),
|
|
196
|
-
]);
|
|
197
|
-
if (!packageJsonBytes.equals(source.packageJsonBytes) || !skillBytes.equals(source.skillBytes)) {
|
|
198
|
-
throw new Error('package assets changed during resolution');
|
|
90
|
+
async function snapshotFromReady(input, operation, runtimeRoot, ready, contentRoot, reused) {
|
|
91
|
+
if (ready.generationHash !== input.workflowGeneration.generationHash || ready.operation !== input.operation) {
|
|
92
|
+
throw new Error('runtime asset ready identity mismatch');
|
|
199
93
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
ownerUid:
|
|
94
|
+
const snapshotRoot = await realpath(contentRoot);
|
|
95
|
+
const files = await evidence(snapshotRoot, operation.files);
|
|
96
|
+
const remap = (path) => join(snapshotRoot, ...relative(operation.workflowRoot, path).split(sep));
|
|
97
|
+
const snapshot = {
|
|
98
|
+
packageVersion: input.workflowGeneration.packageVersion,
|
|
99
|
+
generationHash: input.workflowGeneration.generationHash,
|
|
100
|
+
operation: input.operation,
|
|
101
|
+
runtimeRoot,
|
|
102
|
+
snapshotRoot,
|
|
103
|
+
operationPath: remap(operation.entryPath),
|
|
104
|
+
sourceSkillPath: operation.sourceSkillPath ? remap(operation.sourceSkillPath) : undefined,
|
|
105
|
+
schemaPath: remap(operation.schemaPath),
|
|
106
|
+
profilePath: remap(operation.profilePath),
|
|
107
|
+
policy: structuredClone(operation.policy),
|
|
108
|
+
ownerUid: runnerUid(),
|
|
215
109
|
files,
|
|
216
|
-
|
|
217
|
-
reused
|
|
110
|
+
contentSha256: ready.contentSha256,
|
|
111
|
+
reused,
|
|
218
112
|
};
|
|
113
|
+
await verifyRuntimeAssetSnapshot(snapshot);
|
|
114
|
+
return snapshot;
|
|
219
115
|
}
|
|
220
|
-
function
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
size: source.schemaBytes.length,
|
|
233
|
-
mode: SNAPSHOT_FILE_MODE,
|
|
234
|
-
ownerUid,
|
|
235
|
-
},
|
|
236
|
-
];
|
|
237
|
-
for (const asset of source.supplementalAssets) {
|
|
238
|
-
files.push({
|
|
239
|
-
relativePath: asset.relativePath,
|
|
240
|
-
sha256: sha256(asset.bytes),
|
|
241
|
-
size: asset.bytes.length,
|
|
242
|
-
mode: SNAPSHOT_FILE_MODE,
|
|
243
|
-
ownerUid,
|
|
244
|
-
});
|
|
116
|
+
async function evidence(root, records) {
|
|
117
|
+
const output = [];
|
|
118
|
+
for (const record of records) {
|
|
119
|
+
const path = join(root, ...record.path.split('/'));
|
|
120
|
+
const { bytes, mode, uid } = await readRegularEvidence(path);
|
|
121
|
+
const sealedMode = sealedWorkflowMode(record.mode);
|
|
122
|
+
if ((mode & 0o777) !== sealedMode || uid !== runnerUid()) {
|
|
123
|
+
throw new Error(`runtime asset mode or owner drift: ${record.path}`);
|
|
124
|
+
}
|
|
125
|
+
if (bytes.length !== record.size || sha(bytes) !== record.sha256)
|
|
126
|
+
throw new Error(`runtime asset hash drift: ${record.path}`);
|
|
127
|
+
output.push({ ...record, sealedMode, ownerUid: uid });
|
|
245
128
|
}
|
|
246
|
-
return
|
|
129
|
+
return output;
|
|
247
130
|
}
|
|
248
|
-
function
|
|
249
|
-
|
|
250
|
-
return source.skillBytes;
|
|
251
|
-
if (relativePath === 'output-schema.json')
|
|
252
|
-
return source.schemaBytes;
|
|
253
|
-
const supplemental = source.supplementalAssets.find((asset) => asset.relativePath === relativePath);
|
|
254
|
-
if (supplemental)
|
|
255
|
-
return supplemental.bytes;
|
|
256
|
-
throw new Error(`unknown runtime asset: ${relativePath}`);
|
|
131
|
+
function contentDigest(files) {
|
|
132
|
+
return sealedWorkflowContentSha256(files.map(({ path, sealedMode, size, sha256 }) => ({ path, sealedMode, size, sha256 })));
|
|
257
133
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const relativePath = relativeDirectory ? `${relativeDirectory}/${entry.name}` : entry.name;
|
|
263
|
-
const path = join(directory, entry.name);
|
|
264
|
-
const info = await lstat(path);
|
|
265
|
-
if (entry.isSymbolicLink() || info.isSymbolicLink())
|
|
266
|
-
throw new Error(`snapshot contains a symbolic link: ${relativePath}`);
|
|
267
|
-
assertOwner(info.uid, expectedUid, path);
|
|
268
|
-
if (entry.isDirectory()) {
|
|
269
|
-
assertMode(info.mode, SNAPSHOT_ROOT_MODE, path);
|
|
270
|
-
await visit(path, relativePath);
|
|
271
|
-
}
|
|
272
|
-
else if (entry.isFile()) {
|
|
273
|
-
paths.push(relativePath);
|
|
274
|
-
}
|
|
275
|
-
else {
|
|
276
|
-
throw new Error(`snapshot contains a special entry: ${relativePath}`);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
};
|
|
280
|
-
await visit(snapshotRoot, '');
|
|
281
|
-
return paths.sort((left, right) => expectedAssetOrder(left) - expectedAssetOrder(right) || left.localeCompare(right));
|
|
134
|
+
function newOwner(bootId, parentToken, processStartIdentity) {
|
|
135
|
+
if (!bootId)
|
|
136
|
+
throw new Error('runtime asset boot identity is invalid');
|
|
137
|
+
return { version: 1, status: 'building', bootId, pid: process.pid, token: randomUUID(), parentToken, processStartIdentity };
|
|
282
138
|
}
|
|
283
|
-
function
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
139
|
+
function parseOwner(value) {
|
|
140
|
+
exact(value, ['version', 'status', 'bootId', 'pid', 'token', 'parentToken', 'processStartIdentity']);
|
|
141
|
+
if (value.version !== 1 || value.status !== 'building' || typeof value.bootId !== 'string' || !value.bootId
|
|
142
|
+
|| !Number.isSafeInteger(value.pid) || Number(value.pid) <= 0 || typeof value.token !== 'string' || !UUID.test(value.token)
|
|
143
|
+
|| !(value.parentToken === null || (typeof value.parentToken === 'string' && UUID.test(value.parentToken))))
|
|
144
|
+
throw new Error('runtime asset owner is invalid');
|
|
145
|
+
if (typeof value.processStartIdentity !== 'string' || !value.processStartIdentity)
|
|
146
|
+
throw new Error('runtime asset owner is invalid');
|
|
147
|
+
return value;
|
|
289
148
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
149
|
+
function parseReady(value) {
|
|
150
|
+
exact(value, ['version', 'status', 'token', 'generationHash', 'operation', 'contentSha256']);
|
|
151
|
+
if (value.version !== 1 || value.status !== 'ready' || typeof value.token !== 'string' || !UUID.test(value.token)
|
|
152
|
+
|| typeof value.generationHash !== 'string' || !/^[a-f0-9]{64}$/u.test(value.generationHash)
|
|
153
|
+
|| typeof value.operation !== 'string' || !value.operation || typeof value.contentSha256 !== 'string'
|
|
154
|
+
|| !/^[a-f0-9]{64}$/u.test(value.contentSha256))
|
|
155
|
+
throw new Error('runtime asset ready record is invalid');
|
|
156
|
+
return value;
|
|
157
|
+
}
|
|
158
|
+
async function ensureManagedPath(root, target) {
|
|
159
|
+
await createManagedRoot(root);
|
|
160
|
+
const rootInfo = await lstat(root);
|
|
161
|
+
if (!rootInfo.isDirectory() || rootInfo.isSymbolicLink() || rootInfo.uid !== runnerUid() || (rootInfo.mode & 0o777) !== 0o700) {
|
|
162
|
+
throw new Error('runtime asset root is unsafe');
|
|
163
|
+
}
|
|
164
|
+
let current = root;
|
|
165
|
+
for (const segment of relative(root, target).split(sep).filter(Boolean)) {
|
|
296
166
|
current = join(current, segment);
|
|
167
|
+
await mkdir(current, { mode: 0o700 }).catch((error) => { if (!isCode(error, 'EEXIST'))
|
|
168
|
+
throw error; });
|
|
169
|
+
const info = await lstat(current);
|
|
170
|
+
if (!info.isDirectory() || info.isSymbolicLink() || info.uid !== runnerUid() || (info.mode & 0o777) !== 0o700)
|
|
171
|
+
throw new Error('runtime asset managed path is unsafe');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async function createManagedRoot(path) {
|
|
175
|
+
const chain = [];
|
|
176
|
+
let current = resolve(path);
|
|
177
|
+
while (dirname(current) !== current) {
|
|
178
|
+
chain.push(current);
|
|
179
|
+
current = dirname(current);
|
|
180
|
+
}
|
|
181
|
+
for (const segment of chain.reverse()) {
|
|
182
|
+
let info;
|
|
297
183
|
try {
|
|
298
|
-
|
|
299
|
-
if (info.isSymbolicLink())
|
|
300
|
-
throw new Error(`managed runtime path contains a symbolic link: ${current}`);
|
|
301
|
-
assertDirectory(info, current);
|
|
302
|
-
assertOwner(info.uid, expectedUid, current);
|
|
184
|
+
info = await lstat(segment);
|
|
303
185
|
}
|
|
304
186
|
catch (error) {
|
|
305
|
-
if (error
|
|
187
|
+
if (!isCode(error, 'ENOENT'))
|
|
306
188
|
throw error;
|
|
307
189
|
try {
|
|
308
|
-
await mkdir(
|
|
190
|
+
await mkdir(segment, { mode: 0o700 });
|
|
309
191
|
}
|
|
310
192
|
catch (mkdirError) {
|
|
311
|
-
if (mkdirError
|
|
193
|
+
if (!isCode(mkdirError, 'EEXIST'))
|
|
312
194
|
throw mkdirError;
|
|
313
195
|
}
|
|
314
|
-
|
|
315
|
-
if (created.isSymbolicLink())
|
|
316
|
-
throw new Error(`managed runtime path contains a symbolic link: ${current}`);
|
|
317
|
-
assertDirectory(created, current);
|
|
318
|
-
assertOwner(created.uid, expectedUid, current);
|
|
319
|
-
assertMode(created.mode, SNAPSHOT_ROOT_MODE, current);
|
|
196
|
+
info = await lstat(segment);
|
|
320
197
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
try {
|
|
325
|
-
await mkdir(runtimeRoot, { mode: SNAPSHOT_ROOT_MODE });
|
|
326
|
-
}
|
|
327
|
-
catch (error) {
|
|
328
|
-
if (error.code !== 'EEXIST')
|
|
329
|
-
throw error;
|
|
330
|
-
}
|
|
331
|
-
const info = await lstat(runtimeRoot);
|
|
332
|
-
if (info.isSymbolicLink())
|
|
333
|
-
throw new Error(`managed runtime path contains a symbolic link: ${runtimeRoot}`);
|
|
334
|
-
assertDirectory(info, runtimeRoot);
|
|
335
|
-
assertOwner(info.uid, expectedUid, runtimeRoot);
|
|
336
|
-
assertMode(info.mode, SNAPSHOT_ROOT_MODE, runtimeRoot);
|
|
337
|
-
}
|
|
338
|
-
async function assertExistingManagedPath(runtimeRoot, target, expectedUid) {
|
|
339
|
-
assertContained(runtimeRoot, target, 'managed snapshot');
|
|
340
|
-
const paths = [runtimeRoot];
|
|
341
|
-
let current = runtimeRoot;
|
|
342
|
-
for (const segment of relative(runtimeRoot, target).split(sep).filter(Boolean)) {
|
|
343
|
-
current = join(current, segment);
|
|
344
|
-
paths.push(current);
|
|
345
|
-
}
|
|
346
|
-
for (const path of paths) {
|
|
347
|
-
const info = await lstat(path);
|
|
348
|
-
if (info.isSymbolicLink())
|
|
349
|
-
throw new Error(`managed runtime path contains a symbolic link: ${path}`);
|
|
350
|
-
if (path !== target)
|
|
351
|
-
assertDirectory(info, path);
|
|
352
|
-
assertOwner(info.uid, expectedUid, path);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
async function assertSourcePath(root, target, finalMustBeFile) {
|
|
356
|
-
assertContained(root, target, 'package asset');
|
|
357
|
-
const paths = [root];
|
|
358
|
-
let current = root;
|
|
359
|
-
for (const segment of relative(root, target).split(sep).filter(Boolean)) {
|
|
360
|
-
current = join(current, segment);
|
|
361
|
-
paths.push(current);
|
|
362
|
-
}
|
|
363
|
-
for (const [index, path] of paths.entries()) {
|
|
364
|
-
const info = await lstat(path);
|
|
365
|
-
if (info.isSymbolicLink())
|
|
366
|
-
throw new Error(`package asset path contains a symbolic link: ${path}`);
|
|
367
|
-
const isFinal = index === paths.length - 1;
|
|
368
|
-
if (isFinal && finalMustBeFile) {
|
|
369
|
-
if (!info.isFile())
|
|
370
|
-
throw new Error(`package asset is not a regular file: ${path}`);
|
|
198
|
+
if (info.isSymbolicLink()) {
|
|
199
|
+
if (info.uid === runnerUid() || !(await stat(segment)).isDirectory())
|
|
200
|
+
throw new Error('runtime asset root is unsafe');
|
|
371
201
|
}
|
|
372
|
-
else {
|
|
373
|
-
|
|
202
|
+
else if (!info.isDirectory()) {
|
|
203
|
+
throw new Error('runtime asset root is unsafe');
|
|
374
204
|
}
|
|
375
205
|
}
|
|
376
206
|
}
|
|
377
|
-
async function
|
|
378
|
-
const
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
await
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
return { token };
|
|
391
|
-
}
|
|
392
|
-
catch (error) {
|
|
393
|
-
if (error.code !== 'EEXIST')
|
|
394
|
-
throw error;
|
|
395
|
-
if (Date.now() >= deadline)
|
|
396
|
-
throw new Error('timed out waiting for runtime asset snapshot publisher');
|
|
397
|
-
await new Promise((resolveWait) => setTimeout(resolveWait, LOCK_POLL_MS));
|
|
207
|
+
async function listFiles(root) {
|
|
208
|
+
const output = [];
|
|
209
|
+
const visit = async (directory) => {
|
|
210
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
211
|
+
const path = join(directory, entry.name);
|
|
212
|
+
if (entry.isSymbolicLink())
|
|
213
|
+
throw new Error('runtime asset symlink rejected');
|
|
214
|
+
if (entry.isDirectory())
|
|
215
|
+
await visit(path);
|
|
216
|
+
else if (entry.isFile())
|
|
217
|
+
output.push(relative(root, path).split(sep).join('/'));
|
|
218
|
+
else
|
|
219
|
+
throw new Error('runtime asset special entry rejected');
|
|
398
220
|
}
|
|
399
|
-
}
|
|
221
|
+
};
|
|
222
|
+
await visit(root);
|
|
223
|
+
return output.sort(compareUtf8);
|
|
400
224
|
}
|
|
401
|
-
async function
|
|
225
|
+
async function listDirectories(root) {
|
|
226
|
+
const output = [root];
|
|
227
|
+
for (const entry of await readdir(root, { withFileTypes: true }))
|
|
228
|
+
if (entry.isDirectory())
|
|
229
|
+
output.push(...await listDirectories(join(root, entry.name)));
|
|
230
|
+
return output;
|
|
231
|
+
}
|
|
232
|
+
async function writeSynced(path, bytes, mode) {
|
|
233
|
+
await writeFile(path, bytes, { mode });
|
|
234
|
+
await chmod(path, mode);
|
|
235
|
+
const handle = await open(path, 'r');
|
|
402
236
|
try {
|
|
403
|
-
|
|
404
|
-
if (current === `${token}\n`)
|
|
405
|
-
await rm(path);
|
|
237
|
+
await handle.sync();
|
|
406
238
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
throw error;
|
|
239
|
+
finally {
|
|
240
|
+
await handle.close();
|
|
410
241
|
}
|
|
411
242
|
}
|
|
412
243
|
async function syncDirectory(path) {
|
|
@@ -418,56 +249,72 @@ async function syncDirectory(path) {
|
|
|
418
249
|
await handle.close();
|
|
419
250
|
}
|
|
420
251
|
}
|
|
421
|
-
function
|
|
422
|
-
|
|
423
|
-
|
|
252
|
+
async function readRegular(path) {
|
|
253
|
+
return (await readRegularEvidence(path)).bytes;
|
|
254
|
+
}
|
|
255
|
+
async function readRegularEvidence(path) {
|
|
256
|
+
let handle;
|
|
257
|
+
try {
|
|
258
|
+
handle = await open(path, O_RDONLY | O_NOFOLLOW);
|
|
424
259
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
throw new Error('snapshotRelativePath contains an unsafe segment');
|
|
260
|
+
catch (error) {
|
|
261
|
+
throw new Error('runtime asset control path is invalid', { cause: error });
|
|
428
262
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
263
|
+
try {
|
|
264
|
+
const before = await handle.stat({ bigint: true });
|
|
265
|
+
if (!before.isFile())
|
|
266
|
+
throw new Error('runtime asset control path is invalid');
|
|
267
|
+
const bytes = await handle.readFile();
|
|
268
|
+
const after = await handle.stat({ bigint: true });
|
|
269
|
+
if (!after.isFile() || before.dev !== after.dev || before.ino !== after.ino
|
|
270
|
+
|| before.size !== after.size || before.mtimeNs !== after.mtimeNs
|
|
271
|
+
|| BigInt(bytes.length) !== after.size)
|
|
272
|
+
throw new Error('runtime asset control path changed while reading');
|
|
273
|
+
return { bytes, mode: Number(after.mode), uid: Number(after.uid) };
|
|
274
|
+
}
|
|
275
|
+
finally {
|
|
276
|
+
await handle.close();
|
|
439
277
|
}
|
|
440
278
|
}
|
|
441
|
-
function
|
|
442
|
-
if (
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
279
|
+
function validateRelativePath(value) {
|
|
280
|
+
if (!value || isAbsolute(value) || value.includes('\\') || posix.normalize(value) !== value
|
|
281
|
+
|| value.split('/').some((part) => !part || part === '.' || part === '..') || basename(value) !== 'snapshot') {
|
|
282
|
+
throw new Error('snapshotRelativePath is invalid');
|
|
283
|
+
}
|
|
284
|
+
return value;
|
|
446
285
|
}
|
|
447
|
-
function
|
|
448
|
-
|
|
449
|
-
|
|
286
|
+
function assertContained(root, target) {
|
|
287
|
+
const rel = relative(resolve(root), resolve(target));
|
|
288
|
+
if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel))
|
|
289
|
+
throw new Error('runtime asset path escapes root');
|
|
450
290
|
}
|
|
451
|
-
function
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
throw new Error(`path mode drift at ${path}: expected ${expectedMode.toString(8)}, got ${mode.toString(8)}`);
|
|
291
|
+
function exact(value, keys) {
|
|
292
|
+
if (!isRecord(value) || !same(Object.keys(value).sort(compareUtf8), [...keys].sort(compareUtf8)))
|
|
293
|
+
throw new Error('runtime asset record shape is invalid');
|
|
455
294
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
throw error;
|
|
465
|
-
}
|
|
295
|
+
function canonicalJson(value) {
|
|
296
|
+
if (value === null || ['boolean', 'string', 'number'].includes(typeof value))
|
|
297
|
+
return JSON.stringify(value);
|
|
298
|
+
if (Array.isArray(value))
|
|
299
|
+
return `[${value.map(canonicalJson).join(',')}]`;
|
|
300
|
+
if (isRecord(value))
|
|
301
|
+
return `{${Object.keys(value).sort(compareUtf8).map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}`;
|
|
302
|
+
throw new Error('runtime asset value is not canonicalizable');
|
|
466
303
|
}
|
|
467
|
-
function runnerUid() {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
304
|
+
function runnerUid() { const uid = process.getuid?.(); if (uid === undefined)
|
|
305
|
+
throw new Error('POSIX ownership is required'); return uid; }
|
|
306
|
+
function sha(bytes) { return createHash('sha256').update(bytes).digest('hex'); }
|
|
307
|
+
function compareUtf8(left, right) { return Buffer.compare(Buffer.from(left), Buffer.from(right)); }
|
|
308
|
+
function same(left, right) { return left.length === right.length && left.every((value, index) => value === right[index]); }
|
|
309
|
+
function isRecord(value) { return typeof value === 'object' && value !== null && !Array.isArray(value); }
|
|
310
|
+
function isCode(error, code) { return typeof error === 'object' && error !== null && error.code === code; }
|
|
311
|
+
async function exists(path) { try {
|
|
312
|
+
await lstat(path);
|
|
313
|
+
return true;
|
|
472
314
|
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
if (isCode(error, 'ENOENT'))
|
|
317
|
+
return false;
|
|
318
|
+
throw error;
|
|
319
|
+
} }
|
|
473
320
|
//# sourceMappingURL=runtime-assets.js.map
|