@tea-agent/loop-agent 0.16.0 → 0.16.1-beta.1
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/AGENTS.md +1 -1
- package/CHANGELOG.md +7 -51
- package/README.md +1 -1
- package/dist/application/evaluation/candidate.js +6 -23
- package/dist/application/evaluation/types.js +0 -522
- package/dist/cli/command-definitions.js +3 -19
- package/dist/commands/eval.js +13 -1176
- package/dist/commands/init.js +1 -4
- package/dist/executors/dag-pi-executor.js +44 -4
- package/dist/worker/delivery/verification-bundle.js +2 -13
- package/dist/worker/feature/review.js +2 -3
- package/dist/worker/outcomes/adapters.js +1 -4
- package/dist/worker/outcomes/gate.js +11 -10
- package/dist/worker/outcomes/projector.js +1 -10
- package/dist/worker/outcomes/types.js +0 -3
- package/dist/worker/run-task/run-task.js +4 -23
- package/dist/worker/task-graph/ready-planner.js +8 -14
- package/dist/worker/task-graph/task-graph-schema.js +3 -5
- package/dist/workflows/dag/frontend-implementation-contract.js +77 -0
- package/dist/workflows/dag/init-hybrid.js +47 -3
- package/dist/workflows/dag/knowledge-curator.js +0 -3
- package/dist/workflows/dag/node-execution.js +4 -11
- package/dist/workflows/dag/prompt.js +1 -1
- package/dist/workflows/dag/runner.js +16 -43
- package/dist/workflows/dag/skill-snapshot.js +7 -11
- package/dist/workflows/dag/types.js +1 -18
- package/docs/init-surface.manifest.json +0 -3
- package/docs/templates/agent-dag.schema.json +5 -0
- package/docs/templates/product-line/AGENTS.md +0 -1
- package/docs/templates/product-line/README.md +0 -17
- package/docs/templates/product-line/acceptance.yaml +0 -9
- package/docs/templates/product-line/task-graph.yaml +0 -8
- package/docs/templates/product-line/task.yaml +0 -4
- package/package.json +1 -1
- package/skills/frontend-implementation/references/node-contracts.md +3 -3
- package/skills/loop-agent/references/hybrid-dag.md +1 -1
- package/dist/application/evaluation/alias.js +0 -184
- package/dist/application/evaluation/budget.js +0 -192
- package/dist/application/evaluation/campaign-hash.js +0 -47
- package/dist/application/evaluation/campaign-matrix.js +0 -372
- package/dist/application/evaluation/campaign-scorecard.js +0 -135
- package/dist/application/evaluation/campaign.js +0 -370
- package/dist/application/evaluation/corpus-hash.js +0 -38
- package/dist/application/evaluation/corpus.js +0 -56
- package/dist/application/evaluation/experiment.js +0 -294
- package/dist/application/evaluation/ignition.js +0 -198
- package/dist/application/evaluation/integrity-audit.js +0 -162
- package/dist/application/evaluation/outer-loop.js +0 -132
- package/dist/application/evaluation/pi-cell-executor.js +0 -39
- package/dist/application/evaluation/private-verifier.js +0 -46
- package/dist/application/evaluation/promotion-policy.js +0 -151
- package/dist/application/evaluation/proposer.js +0 -98
- package/dist/infrastructure/evaluation/alias-store.js +0 -199
- package/dist/infrastructure/evaluation/campaign-store.js +0 -154
- package/dist/infrastructure/evaluation/corpus-store.js +0 -181
- package/dist/infrastructure/evaluation/experiment-store.js +0 -124
- package/dist/infrastructure/evaluation/ignition-store.js +0 -82
- package/dist/infrastructure/evaluation/private-verifier-store.js +0 -145
- package/dist/infrastructure/evaluation/proposer-store.js +0 -78
- package/dist/worker/outcomes/evidence-tokens.js +0 -29
- package/dist/workflows/dag/budget-enforcement.js +0 -67
- package/dist/workflows/dag/context-policy.js +0 -137
- package/docs/templates/evaluation/campaign-budget-v1.json +0 -12
- package/docs/templates/evaluation/campaign-dogfood-v0.json +0 -24
- package/docs/templates/evaluation/campaign-evidence-v1.json +0 -44
- package/docs/templates/evaluation/context-policy-baseline-v1.json +0 -17
- package/docs/templates/evaluation/context-policy-role-specialized-v1.json +0 -28
- package/docs/templates/evaluation/corpus-dogfood-v0.manifest.json +0 -118
- package/docs/templates/evaluation/matrix-dag-dry-run-v1.json +0 -21
- package/docs/templates/evaluation/matrix-fixture-v1.json +0 -10
- package/docs/templates/evaluation/private-verifier-dogfood-v0.json +0 -16
- package/docs/templates/product-line/feature.yaml +0 -11
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { access, mkdir, mkdtemp, readdir, readFile, rename, rm } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { campaignEvidenceSchema, campaignIdSchema, campaignManifestSchema, campaignPlanSchema, } from "../../application/evaluation/types.js";
|
|
4
|
-
import { sha256Hex } from "../../application/evaluation/candidate-hash.js";
|
|
5
|
-
import { writeJsonAtomic, writeTextAtomic } from "../harness/atomic-write.js";
|
|
6
|
-
import { EVALUATION_ROOT } from "./store.js";
|
|
7
|
-
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
8
|
-
const MANIFEST_INTEGRITY_FILE = "manifest.sha256";
|
|
9
|
-
export function assertCampaignId(value) {
|
|
10
|
-
campaignIdSchema.parse(value);
|
|
11
|
-
}
|
|
12
|
-
export function campaignDir(repoRoot, campaignId) {
|
|
13
|
-
assertCampaignId(campaignId);
|
|
14
|
-
return path.join(repoRoot, EVALUATION_ROOT, "campaigns", campaignId);
|
|
15
|
-
}
|
|
16
|
-
export function campaignManifestPath(repoRoot, campaignId) {
|
|
17
|
-
return path.join(campaignDir(repoRoot, campaignId), "manifest.json");
|
|
18
|
-
}
|
|
19
|
-
export function campaignPlanPath(repoRoot, campaignId) {
|
|
20
|
-
return path.join(campaignDir(repoRoot, campaignId), "plan.json");
|
|
21
|
-
}
|
|
22
|
-
export function campaignScorecardPath(repoRoot, campaignId) {
|
|
23
|
-
return path.join(campaignDir(repoRoot, campaignId), "scorecard.json");
|
|
24
|
-
}
|
|
25
|
-
export function campaignEvidencePath(repoRoot, campaignId) {
|
|
26
|
-
return path.join(campaignDir(repoRoot, campaignId), "evidence.json");
|
|
27
|
-
}
|
|
28
|
-
async function pathExists(filePath) {
|
|
29
|
-
try {
|
|
30
|
-
await access(filePath);
|
|
31
|
-
return true;
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
function serializeManifest(manifest) {
|
|
38
|
-
return `${JSON.stringify(manifest, null, 2)}\n`;
|
|
39
|
-
}
|
|
40
|
-
function manifestsEqual(a, b) {
|
|
41
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
42
|
-
}
|
|
43
|
-
export async function readCampaignManifest(repoRoot, campaignId) {
|
|
44
|
-
assertCampaignId(campaignId);
|
|
45
|
-
const manifestPath = campaignManifestPath(repoRoot, campaignId);
|
|
46
|
-
const rawText = await readFile(manifestPath, "utf-8");
|
|
47
|
-
const expected = (await readFile(path.join(campaignDir(repoRoot, campaignId), MANIFEST_INTEGRITY_FILE), "utf-8")).trim();
|
|
48
|
-
const actual = sha256Hex(rawText);
|
|
49
|
-
if (expected !== actual) {
|
|
50
|
-
throw new Error(`campaign manifest integrity mismatch for ${campaignId}`);
|
|
51
|
-
}
|
|
52
|
-
const stored = campaignManifestSchema.parse(JSON.parse(rawText));
|
|
53
|
-
if (stored.campaignId !== campaignId) {
|
|
54
|
-
throw new Error(`campaign identity mismatch: directory=${campaignId}, manifest=${stored.campaignId}`);
|
|
55
|
-
}
|
|
56
|
-
return stored;
|
|
57
|
-
}
|
|
58
|
-
export async function readCampaignPlan(repoRoot, campaignId) {
|
|
59
|
-
const planPath = campaignPlanPath(repoRoot, campaignId);
|
|
60
|
-
if (!(await pathExists(planPath)))
|
|
61
|
-
return undefined;
|
|
62
|
-
return campaignPlanSchema.parse(JSON.parse(await readFile(planPath, "utf-8")));
|
|
63
|
-
}
|
|
64
|
-
export async function listCampaignIds(repoRoot) {
|
|
65
|
-
const root = path.join(repoRoot, EVALUATION_ROOT, "campaigns");
|
|
66
|
-
try {
|
|
67
|
-
const entries = await readdir(root, { withFileTypes: true });
|
|
68
|
-
return entries
|
|
69
|
-
.filter((entry) => entry.isDirectory() && SAFE_ID.test(entry.name))
|
|
70
|
-
.map((entry) => entry.name)
|
|
71
|
-
.sort();
|
|
72
|
-
}
|
|
73
|
-
catch (error) {
|
|
74
|
-
const code = error.code;
|
|
75
|
-
if (code === "ENOENT")
|
|
76
|
-
return [];
|
|
77
|
-
throw error;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
export async function registerCampaignManifest(input) {
|
|
81
|
-
const { repoRoot, manifest } = input;
|
|
82
|
-
assertCampaignId(manifest.campaignId);
|
|
83
|
-
const manifestPath = campaignManifestPath(repoRoot, manifest.campaignId);
|
|
84
|
-
if (await pathExists(manifestPath)) {
|
|
85
|
-
const existing = await readCampaignManifest(repoRoot, manifest.campaignId);
|
|
86
|
-
if (!manifestsEqual(existing, manifest)) {
|
|
87
|
-
throw new Error(`campaign already exists with different content: ${manifest.campaignId}`);
|
|
88
|
-
}
|
|
89
|
-
return { manifest: existing, idempotent: true, manifestPath };
|
|
90
|
-
}
|
|
91
|
-
const root = path.dirname(campaignDir(repoRoot, manifest.campaignId));
|
|
92
|
-
await mkdir(root, { recursive: true });
|
|
93
|
-
const stagingDir = await mkdtemp(path.join(root, `.${manifest.campaignId}.register-`));
|
|
94
|
-
try {
|
|
95
|
-
await writeJsonAtomic(path.join(stagingDir, "manifest.json"), manifest, {
|
|
96
|
-
repoRoot,
|
|
97
|
-
});
|
|
98
|
-
await writeTextAtomic(path.join(stagingDir, MANIFEST_INTEGRITY_FILE), `${sha256Hex(serializeManifest(manifest))}\n`, { repoRoot });
|
|
99
|
-
try {
|
|
100
|
-
await rename(stagingDir, campaignDir(repoRoot, manifest.campaignId));
|
|
101
|
-
}
|
|
102
|
-
catch (error) {
|
|
103
|
-
const code = error.code;
|
|
104
|
-
const isCreateRace = code === "EEXIST" ||
|
|
105
|
-
code === "ENOTEMPTY" ||
|
|
106
|
-
(code === "EPERM" &&
|
|
107
|
-
(await pathExists(campaignDir(repoRoot, manifest.campaignId))));
|
|
108
|
-
if (!isCreateRace)
|
|
109
|
-
throw error;
|
|
110
|
-
await rm(stagingDir, { recursive: true, force: true });
|
|
111
|
-
const existing = await readCampaignManifest(repoRoot, manifest.campaignId);
|
|
112
|
-
if (!manifestsEqual(existing, manifest)) {
|
|
113
|
-
throw new Error(`campaign already exists with different content: ${manifest.campaignId}`);
|
|
114
|
-
}
|
|
115
|
-
return { manifest: existing, idempotent: true, manifestPath };
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
catch (error) {
|
|
119
|
-
await rm(stagingDir, { recursive: true, force: true });
|
|
120
|
-
throw error;
|
|
121
|
-
}
|
|
122
|
-
return { manifest, idempotent: false, manifestPath };
|
|
123
|
-
}
|
|
124
|
-
export async function writeCampaignPlan(input) {
|
|
125
|
-
const planPath = campaignPlanPath(input.repoRoot, input.plan.campaignId);
|
|
126
|
-
await writeJsonAtomic(planPath, input.plan, { repoRoot: input.repoRoot });
|
|
127
|
-
return planPath;
|
|
128
|
-
}
|
|
129
|
-
export async function writeCampaignScorecard(input) {
|
|
130
|
-
const scorecardPath = campaignScorecardPath(input.repoRoot, input.scorecard.campaignId);
|
|
131
|
-
await writeJsonAtomic(scorecardPath, input.scorecard, {
|
|
132
|
-
repoRoot: input.repoRoot,
|
|
133
|
-
});
|
|
134
|
-
if (input.evidence) {
|
|
135
|
-
await writeJsonAtomic(campaignEvidencePath(input.repoRoot, input.scorecard.campaignId), input.evidence, { repoRoot: input.repoRoot });
|
|
136
|
-
}
|
|
137
|
-
return scorecardPath;
|
|
138
|
-
}
|
|
139
|
-
export async function readCampaignScorecard(repoRoot, campaignId) {
|
|
140
|
-
const scorecardPath = campaignScorecardPath(repoRoot, campaignId);
|
|
141
|
-
if (!(await pathExists(scorecardPath)))
|
|
142
|
-
return undefined;
|
|
143
|
-
const raw = JSON.parse(await readFile(scorecardPath, "utf-8"));
|
|
144
|
-
if (raw.schemaVersion !== 1 || raw.campaignId !== campaignId) {
|
|
145
|
-
throw new Error(`invalid campaign scorecard for ${campaignId}`);
|
|
146
|
-
}
|
|
147
|
-
return raw;
|
|
148
|
-
}
|
|
149
|
-
export async function readCampaignEvidence(repoRoot, campaignId) {
|
|
150
|
-
const evidencePath = campaignEvidencePath(repoRoot, campaignId);
|
|
151
|
-
if (!(await pathExists(evidencePath)))
|
|
152
|
-
return undefined;
|
|
153
|
-
return campaignEvidenceSchema.parse(JSON.parse(await readFile(evidencePath, "utf-8")));
|
|
154
|
-
}
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import { access, mkdir, readdir, readFile, rename, rm } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { mkdtemp } from "node:fs/promises";
|
|
4
|
-
import { computeCorpusHash, } from "../../application/evaluation/corpus-hash.js";
|
|
5
|
-
import { formatContentSha, normalizeContentSha, sha256Hex, } from "../../application/evaluation/candidate-hash.js";
|
|
6
|
-
import { corpusIdSchema, corpusManifestInputSchema, corpusManifestSchema, } from "../../application/evaluation/types.js";
|
|
7
|
-
import { writeJsonAtomic, writeTextAtomic } from "../harness/atomic-write.js";
|
|
8
|
-
import { EVALUATION_ROOT } from "./store.js";
|
|
9
|
-
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
10
|
-
const MANIFEST_INTEGRITY_FILE = "manifest.sha256";
|
|
11
|
-
export function assertCorpusId(value) {
|
|
12
|
-
corpusIdSchema.parse(value);
|
|
13
|
-
}
|
|
14
|
-
export function corpusDir(repoRoot, corpusId) {
|
|
15
|
-
assertCorpusId(corpusId);
|
|
16
|
-
return path.join(repoRoot, EVALUATION_ROOT, "corpora", corpusId);
|
|
17
|
-
}
|
|
18
|
-
export function corpusManifestPath(repoRoot, corpusId) {
|
|
19
|
-
return path.join(corpusDir(repoRoot, corpusId), "manifest.json");
|
|
20
|
-
}
|
|
21
|
-
async function pathExists(filePath) {
|
|
22
|
-
try {
|
|
23
|
-
await access(filePath);
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
function serializeManifest(manifest) {
|
|
31
|
-
return `${JSON.stringify(manifest, null, 2)}\n`;
|
|
32
|
-
}
|
|
33
|
-
function normalizeTasksForStorage(tasks) {
|
|
34
|
-
return [...tasks]
|
|
35
|
-
.map((task) => ({
|
|
36
|
-
...task,
|
|
37
|
-
taskRef: task.taskRef.replace(/\\/g, "/"),
|
|
38
|
-
seeds: [...task.seeds].sort((a, b) => a - b),
|
|
39
|
-
}))
|
|
40
|
-
.sort((a, b) => a.taskRef < b.taskRef ? -1 : a.taskRef > b.taskRef ? 1 : 0);
|
|
41
|
-
}
|
|
42
|
-
export async function materializeCorpusManifest(raw) {
|
|
43
|
-
const input = corpusManifestInputSchema.parse(raw);
|
|
44
|
-
const tasks = normalizeTasksForStorage(input.tasks);
|
|
45
|
-
const withoutHash = {
|
|
46
|
-
schemaVersion: 1,
|
|
47
|
-
corpusId: input.corpusId,
|
|
48
|
-
createdAt: input.createdAt,
|
|
49
|
-
...(input.description !== undefined
|
|
50
|
-
? { description: input.description }
|
|
51
|
-
: {}),
|
|
52
|
-
tasks,
|
|
53
|
-
};
|
|
54
|
-
const corpusHash = computeCorpusHash(withoutHash);
|
|
55
|
-
if (input.corpusHash) {
|
|
56
|
-
const provided = formatContentSha(normalizeContentSha(input.corpusHash));
|
|
57
|
-
if (provided !== corpusHash) {
|
|
58
|
-
throw new Error(`corpusHash mismatch: expected ${corpusHash}, got ${provided}`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return corpusManifestSchema.parse({
|
|
62
|
-
...withoutHash,
|
|
63
|
-
corpusHash,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
async function assertManifestIntegrity(repoRoot, corpusId, rawManifest) {
|
|
67
|
-
const integrityPath = path.join(corpusDir(repoRoot, corpusId), MANIFEST_INTEGRITY_FILE);
|
|
68
|
-
const expected = (await readFile(integrityPath, "utf-8")).trim();
|
|
69
|
-
const actual = sha256Hex(rawManifest);
|
|
70
|
-
if (expected !== actual) {
|
|
71
|
-
throw new Error(`corpus manifest integrity mismatch for ${corpusId}: expected ${expected}, got ${actual}`);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
function manifestsEqual(a, b) {
|
|
75
|
-
return (a.corpusId === b.corpusId &&
|
|
76
|
-
a.corpusHash === b.corpusHash &&
|
|
77
|
-
a.createdAt === b.createdAt &&
|
|
78
|
-
(a.description ?? "") === (b.description ?? "") &&
|
|
79
|
-
JSON.stringify(a.tasks) === JSON.stringify(b.tasks));
|
|
80
|
-
}
|
|
81
|
-
export async function loadCorpusManifestFromPath(repoRoot, manifestPath) {
|
|
82
|
-
const resolved = path.isAbsolute(manifestPath)
|
|
83
|
-
? manifestPath
|
|
84
|
-
: path.resolve(repoRoot, manifestPath);
|
|
85
|
-
const raw = JSON.parse(await readFile(resolved, "utf-8"));
|
|
86
|
-
return corpusManifestInputSchema.parse(raw);
|
|
87
|
-
}
|
|
88
|
-
export async function readCorpusManifest(repoRoot, corpusId) {
|
|
89
|
-
assertCorpusId(corpusId);
|
|
90
|
-
const manifestPath = corpusManifestPath(repoRoot, corpusId);
|
|
91
|
-
const rawText = await readFile(manifestPath, "utf-8");
|
|
92
|
-
await assertManifestIntegrity(repoRoot, corpusId, rawText);
|
|
93
|
-
const stored = corpusManifestSchema.parse(JSON.parse(rawText));
|
|
94
|
-
if (stored.corpusId !== corpusId) {
|
|
95
|
-
throw new Error(`corpus identity mismatch: directory=${corpusId}, manifest=${stored.corpusId}`);
|
|
96
|
-
}
|
|
97
|
-
const recomputed = computeCorpusHash(stored);
|
|
98
|
-
if (recomputed !== stored.corpusHash) {
|
|
99
|
-
throw new Error(`corpusHash mismatch for ${corpusId}: expected ${recomputed}, got ${stored.corpusHash}`);
|
|
100
|
-
}
|
|
101
|
-
return stored;
|
|
102
|
-
}
|
|
103
|
-
export async function listCorpusIds(repoRoot) {
|
|
104
|
-
const root = path.join(repoRoot, EVALUATION_ROOT, "corpora");
|
|
105
|
-
try {
|
|
106
|
-
const entries = await readdir(root, { withFileTypes: true });
|
|
107
|
-
return entries
|
|
108
|
-
.filter((entry) => entry.isDirectory() && SAFE_ID.test(entry.name))
|
|
109
|
-
.map((entry) => entry.name)
|
|
110
|
-
.sort();
|
|
111
|
-
}
|
|
112
|
-
catch (error) {
|
|
113
|
-
const code = error.code;
|
|
114
|
-
if (code === "ENOENT")
|
|
115
|
-
return [];
|
|
116
|
-
throw error;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
export async function registerCorpusManifest(input) {
|
|
120
|
-
const { repoRoot, manifest } = input;
|
|
121
|
-
assertCorpusId(manifest.corpusId);
|
|
122
|
-
const manifestPath = corpusManifestPath(repoRoot, manifest.corpusId);
|
|
123
|
-
if (await pathExists(manifestPath)) {
|
|
124
|
-
const existing = await readCorpusManifest(repoRoot, manifest.corpusId);
|
|
125
|
-
if (!manifestsEqual(existing, manifest)) {
|
|
126
|
-
throw new Error(`corpus already exists with different content: ${manifest.corpusId}`);
|
|
127
|
-
}
|
|
128
|
-
return { manifest: existing, idempotent: true, manifestPath };
|
|
129
|
-
}
|
|
130
|
-
const corporaRoot = path.dirname(corpusDir(repoRoot, manifest.corpusId));
|
|
131
|
-
await mkdir(corporaRoot, { recursive: true });
|
|
132
|
-
const stagingDir = await mkdtemp(path.join(corporaRoot, `.${manifest.corpusId}.register-`));
|
|
133
|
-
try {
|
|
134
|
-
await writeJsonAtomic(path.join(stagingDir, "manifest.json"), manifest, {
|
|
135
|
-
repoRoot,
|
|
136
|
-
});
|
|
137
|
-
await writeTextAtomic(path.join(stagingDir, MANIFEST_INTEGRITY_FILE), `${sha256Hex(serializeManifest(manifest))}\n`, { repoRoot });
|
|
138
|
-
try {
|
|
139
|
-
await rename(stagingDir, corpusDir(repoRoot, manifest.corpusId));
|
|
140
|
-
}
|
|
141
|
-
catch (error) {
|
|
142
|
-
const code = error.code;
|
|
143
|
-
const isCreateRace = code === "EEXIST" ||
|
|
144
|
-
code === "ENOTEMPTY" ||
|
|
145
|
-
(code === "EPERM" &&
|
|
146
|
-
(await pathExists(corpusDir(repoRoot, manifest.corpusId))));
|
|
147
|
-
if (!isCreateRace)
|
|
148
|
-
throw error;
|
|
149
|
-
await rm(stagingDir, { recursive: true, force: true });
|
|
150
|
-
const existing = await readCorpusManifest(repoRoot, manifest.corpusId);
|
|
151
|
-
if (!manifestsEqual(existing, manifest)) {
|
|
152
|
-
throw new Error(`corpus already exists with different content: ${manifest.corpusId}`);
|
|
153
|
-
}
|
|
154
|
-
return { manifest: existing, idempotent: true, manifestPath };
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
catch (error) {
|
|
158
|
-
await rm(stagingDir, { recursive: true, force: true });
|
|
159
|
-
throw error;
|
|
160
|
-
}
|
|
161
|
-
return { manifest, idempotent: false, manifestPath };
|
|
162
|
-
}
|
|
163
|
-
export function summarizeCorpus(manifest) {
|
|
164
|
-
const splits = {
|
|
165
|
-
public: 0,
|
|
166
|
-
private: 0,
|
|
167
|
-
held_out: 0,
|
|
168
|
-
};
|
|
169
|
-
const classes = new Set();
|
|
170
|
-
for (const task of manifest.tasks) {
|
|
171
|
-
splits[task.split] += 1;
|
|
172
|
-
classes.add(task.class);
|
|
173
|
-
}
|
|
174
|
-
return {
|
|
175
|
-
corpusId: manifest.corpusId,
|
|
176
|
-
corpusHash: manifest.corpusHash,
|
|
177
|
-
taskCount: manifest.tasks.length,
|
|
178
|
-
splits,
|
|
179
|
-
classes: [...classes].sort(),
|
|
180
|
-
};
|
|
181
|
-
}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { access, mkdir, mkdtemp, readdir, readFile, rename, rm } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { experimentIdSchema, experimentRecordSchema, } from "../../application/evaluation/types.js";
|
|
4
|
-
import { sha256Hex } from "../../application/evaluation/candidate-hash.js";
|
|
5
|
-
import { writeJsonAtomic, writeTextAtomic } from "../harness/atomic-write.js";
|
|
6
|
-
import { EVALUATION_ROOT } from "./store.js";
|
|
7
|
-
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
8
|
-
const MANIFEST_INTEGRITY_FILE = "manifest.sha256";
|
|
9
|
-
export function assertExperimentId(value) {
|
|
10
|
-
experimentIdSchema.parse(value);
|
|
11
|
-
}
|
|
12
|
-
export function experimentDir(repoRoot, experimentId) {
|
|
13
|
-
assertExperimentId(experimentId);
|
|
14
|
-
return path.join(repoRoot, EVALUATION_ROOT, "experiments", experimentId);
|
|
15
|
-
}
|
|
16
|
-
export function experimentManifestPath(repoRoot, experimentId) {
|
|
17
|
-
return path.join(experimentDir(repoRoot, experimentId), "manifest.json");
|
|
18
|
-
}
|
|
19
|
-
/** Repo-relative content path for experimental learned proposal (allowed as contentRef). */
|
|
20
|
-
export function experimentProposalRepoPath(experimentId) {
|
|
21
|
-
assertExperimentId(experimentId);
|
|
22
|
-
return path.posix.join("ai_workspace/loop-agent/evaluation/experiments", experimentId, "learned-proposal.md");
|
|
23
|
-
}
|
|
24
|
-
async function pathExists(filePath) {
|
|
25
|
-
try {
|
|
26
|
-
await access(filePath);
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
function serializeRecord(record) {
|
|
34
|
-
return `${JSON.stringify(record, null, 2)}\n`;
|
|
35
|
-
}
|
|
36
|
-
function recordsEqual(a, b) {
|
|
37
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
38
|
-
}
|
|
39
|
-
export async function readExperimentRecord(repoRoot, experimentId) {
|
|
40
|
-
assertExperimentId(experimentId);
|
|
41
|
-
const manifestPath = experimentManifestPath(repoRoot, experimentId);
|
|
42
|
-
const rawText = await readFile(manifestPath, "utf-8");
|
|
43
|
-
const expected = (await readFile(path.join(experimentDir(repoRoot, experimentId), MANIFEST_INTEGRITY_FILE), "utf-8")).trim();
|
|
44
|
-
const actual = sha256Hex(rawText);
|
|
45
|
-
if (expected !== actual) {
|
|
46
|
-
throw new Error(`experiment manifest integrity mismatch for ${experimentId}`);
|
|
47
|
-
}
|
|
48
|
-
const stored = experimentRecordSchema.parse(JSON.parse(rawText));
|
|
49
|
-
if (stored.experimentId !== experimentId) {
|
|
50
|
-
throw new Error(`experiment identity mismatch: directory=${experimentId}, manifest=${stored.experimentId}`);
|
|
51
|
-
}
|
|
52
|
-
return stored;
|
|
53
|
-
}
|
|
54
|
-
export async function listExperimentIds(repoRoot) {
|
|
55
|
-
const root = path.join(repoRoot, EVALUATION_ROOT, "experiments");
|
|
56
|
-
try {
|
|
57
|
-
const entries = await readdir(root, { withFileTypes: true });
|
|
58
|
-
return entries
|
|
59
|
-
.filter((entry) => entry.isDirectory() && SAFE_ID.test(entry.name))
|
|
60
|
-
.map((entry) => entry.name)
|
|
61
|
-
.sort();
|
|
62
|
-
}
|
|
63
|
-
catch (error) {
|
|
64
|
-
const code = error.code;
|
|
65
|
-
if (code === "ENOENT")
|
|
66
|
-
return [];
|
|
67
|
-
throw error;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
export async function writeExperimentRecord(input) {
|
|
71
|
-
const { repoRoot, record } = input;
|
|
72
|
-
assertExperimentId(record.experimentId);
|
|
73
|
-
const parsed = experimentRecordSchema.parse(record);
|
|
74
|
-
const manifestPath = experimentManifestPath(repoRoot, parsed.experimentId);
|
|
75
|
-
if (await pathExists(manifestPath)) {
|
|
76
|
-
const existing = await readExperimentRecord(repoRoot, parsed.experimentId);
|
|
77
|
-
if (!recordsEqual(existing, parsed)) {
|
|
78
|
-
throw new Error(`experiment already exists with different content: ${parsed.experimentId}`);
|
|
79
|
-
}
|
|
80
|
-
return { record: existing, idempotent: true, manifestPath };
|
|
81
|
-
}
|
|
82
|
-
const root = path.dirname(experimentDir(repoRoot, parsed.experimentId));
|
|
83
|
-
await mkdir(root, { recursive: true });
|
|
84
|
-
const stagingDir = await mkdtemp(path.join(root, `.${parsed.experimentId}.register-`));
|
|
85
|
-
try {
|
|
86
|
-
await writeJsonAtomic(path.join(stagingDir, "manifest.json"), parsed, {
|
|
87
|
-
repoRoot,
|
|
88
|
-
});
|
|
89
|
-
await writeTextAtomic(path.join(stagingDir, MANIFEST_INTEGRITY_FILE), `${sha256Hex(serializeRecord(parsed))}\n`, { repoRoot });
|
|
90
|
-
try {
|
|
91
|
-
await rename(stagingDir, experimentDir(repoRoot, parsed.experimentId));
|
|
92
|
-
}
|
|
93
|
-
catch (error) {
|
|
94
|
-
const code = error.code;
|
|
95
|
-
const isCreateRace = code === "EEXIST" ||
|
|
96
|
-
code === "ENOTEMPTY" ||
|
|
97
|
-
(code === "EPERM" &&
|
|
98
|
-
(await pathExists(experimentDir(repoRoot, parsed.experimentId))));
|
|
99
|
-
if (!isCreateRace)
|
|
100
|
-
throw error;
|
|
101
|
-
await rm(stagingDir, { recursive: true, force: true });
|
|
102
|
-
const existing = await readExperimentRecord(repoRoot, parsed.experimentId);
|
|
103
|
-
if (!recordsEqual(existing, parsed)) {
|
|
104
|
-
throw new Error(`experiment already exists with different content: ${parsed.experimentId}`);
|
|
105
|
-
}
|
|
106
|
-
return { record: existing, idempotent: true, manifestPath };
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
catch (error) {
|
|
110
|
-
await rm(stagingDir, { recursive: true, force: true });
|
|
111
|
-
throw error;
|
|
112
|
-
}
|
|
113
|
-
return { record: parsed, idempotent: false, manifestPath };
|
|
114
|
-
}
|
|
115
|
-
export async function updateExperimentRecord(input) {
|
|
116
|
-
const parsed = experimentRecordSchema.parse(input.record);
|
|
117
|
-
const manifestPath = experimentManifestPath(input.repoRoot, parsed.experimentId);
|
|
118
|
-
if (!(await pathExists(manifestPath))) {
|
|
119
|
-
throw new Error(`experiment not found: ${parsed.experimentId}`);
|
|
120
|
-
}
|
|
121
|
-
await writeJsonAtomic(manifestPath, parsed, { repoRoot: input.repoRoot });
|
|
122
|
-
await writeTextAtomic(path.join(experimentDir(input.repoRoot, parsed.experimentId), MANIFEST_INTEGRITY_FILE), `${sha256Hex(serializeRecord(parsed))}\n`, { repoRoot: input.repoRoot });
|
|
123
|
-
return manifestPath;
|
|
124
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { access, mkdir, mkdtemp, readdir, readFile, rename, rm } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { ignitionIdSchema, ignitionReportSchema, } from "../../application/evaluation/types.js";
|
|
4
|
-
import { sha256Hex } from "../../application/evaluation/candidate-hash.js";
|
|
5
|
-
import { writeJsonAtomic, writeTextAtomic } from "../harness/atomic-write.js";
|
|
6
|
-
import { EVALUATION_ROOT } from "./store.js";
|
|
7
|
-
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
8
|
-
const MANIFEST_INTEGRITY_FILE = "manifest.sha256";
|
|
9
|
-
export function assertIgnitionId(value) {
|
|
10
|
-
ignitionIdSchema.parse(value);
|
|
11
|
-
}
|
|
12
|
-
export function ignitionDir(repoRoot, ignitionId) {
|
|
13
|
-
assertIgnitionId(ignitionId);
|
|
14
|
-
return path.join(repoRoot, EVALUATION_ROOT, "ignition", ignitionId);
|
|
15
|
-
}
|
|
16
|
-
export function ignitionReportPath(repoRoot, ignitionId) {
|
|
17
|
-
return path.join(ignitionDir(repoRoot, ignitionId), "report.json");
|
|
18
|
-
}
|
|
19
|
-
async function pathExists(filePath) {
|
|
20
|
-
try {
|
|
21
|
-
await access(filePath);
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
function serialize(report) {
|
|
29
|
-
return `${JSON.stringify(report, null, 2)}\n`;
|
|
30
|
-
}
|
|
31
|
-
export async function readIgnitionReport(repoRoot, ignitionId) {
|
|
32
|
-
assertIgnitionId(ignitionId);
|
|
33
|
-
const reportPath = ignitionReportPath(repoRoot, ignitionId);
|
|
34
|
-
if (!(await pathExists(reportPath)))
|
|
35
|
-
return undefined;
|
|
36
|
-
const rawText = await readFile(reportPath, "utf-8");
|
|
37
|
-
const expected = (await readFile(path.join(ignitionDir(repoRoot, ignitionId), MANIFEST_INTEGRITY_FILE), "utf-8")).trim();
|
|
38
|
-
if (sha256Hex(rawText) !== expected) {
|
|
39
|
-
throw new Error(`ignition report integrity mismatch for ${ignitionId}`);
|
|
40
|
-
}
|
|
41
|
-
return ignitionReportSchema.parse(JSON.parse(rawText));
|
|
42
|
-
}
|
|
43
|
-
export async function writeIgnitionReport(input) {
|
|
44
|
-
const parsed = ignitionReportSchema.parse(input.report);
|
|
45
|
-
const reportPath = ignitionReportPath(input.repoRoot, parsed.ignitionId);
|
|
46
|
-
const root = path.dirname(ignitionDir(input.repoRoot, parsed.ignitionId));
|
|
47
|
-
await mkdir(root, { recursive: true });
|
|
48
|
-
if (await pathExists(reportPath)) {
|
|
49
|
-
await writeJsonAtomic(reportPath, parsed, { repoRoot: input.repoRoot });
|
|
50
|
-
await writeTextAtomic(path.join(ignitionDir(input.repoRoot, parsed.ignitionId), MANIFEST_INTEGRITY_FILE), `${sha256Hex(serialize(parsed))}\n`, { repoRoot: input.repoRoot });
|
|
51
|
-
return reportPath;
|
|
52
|
-
}
|
|
53
|
-
const stagingDir = await mkdtemp(path.join(root, `.${parsed.ignitionId}.register-`));
|
|
54
|
-
try {
|
|
55
|
-
await writeJsonAtomic(path.join(stagingDir, "report.json"), parsed, {
|
|
56
|
-
repoRoot: input.repoRoot,
|
|
57
|
-
});
|
|
58
|
-
await writeTextAtomic(path.join(stagingDir, MANIFEST_INTEGRITY_FILE), `${sha256Hex(serialize(parsed))}\n`, { repoRoot: input.repoRoot });
|
|
59
|
-
await rename(stagingDir, ignitionDir(input.repoRoot, parsed.ignitionId));
|
|
60
|
-
}
|
|
61
|
-
catch (error) {
|
|
62
|
-
await rm(stagingDir, { recursive: true, force: true });
|
|
63
|
-
throw error;
|
|
64
|
-
}
|
|
65
|
-
return reportPath;
|
|
66
|
-
}
|
|
67
|
-
export async function listIgnitionIds(repoRoot) {
|
|
68
|
-
const root = path.join(repoRoot, EVALUATION_ROOT, "ignition");
|
|
69
|
-
try {
|
|
70
|
-
const entries = await readdir(root, { withFileTypes: true });
|
|
71
|
-
return entries
|
|
72
|
-
.filter((entry) => entry.isDirectory() && SAFE_ID.test(entry.name))
|
|
73
|
-
.map((entry) => entry.name)
|
|
74
|
-
.sort();
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
const code = error.code;
|
|
78
|
-
if (code === "ENOENT")
|
|
79
|
-
return [];
|
|
80
|
-
throw error;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { access, mkdir, mkdtemp, readdir, readFile, rename, rm } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { assertPrivateVerifierHash, computePrivateVerifierManifestHash, } from "../../application/evaluation/campaign-hash.js";
|
|
4
|
-
import { formatContentSha, normalizeContentSha, sha256Hex, } from "../../application/evaluation/candidate-hash.js";
|
|
5
|
-
import { privateVerifierIdSchema, privateVerifierManifestInputSchema, privateVerifierManifestSchema, } from "../../application/evaluation/types.js";
|
|
6
|
-
import { writeJsonAtomic, writeTextAtomic } from "../harness/atomic-write.js";
|
|
7
|
-
import { EVALUATION_ROOT } from "./store.js";
|
|
8
|
-
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
9
|
-
const MANIFEST_INTEGRITY_FILE = "manifest.sha256";
|
|
10
|
-
export function assertPrivateVerifierId(value) {
|
|
11
|
-
privateVerifierIdSchema.parse(value);
|
|
12
|
-
}
|
|
13
|
-
export function privateVerifierDir(repoRoot, privateVerifierId) {
|
|
14
|
-
assertPrivateVerifierId(privateVerifierId);
|
|
15
|
-
return path.join(repoRoot, EVALUATION_ROOT, "private-verifiers", privateVerifierId);
|
|
16
|
-
}
|
|
17
|
-
export function privateVerifierManifestPath(repoRoot, privateVerifierId) {
|
|
18
|
-
return path.join(privateVerifierDir(repoRoot, privateVerifierId), "manifest.json");
|
|
19
|
-
}
|
|
20
|
-
async function pathExists(filePath) {
|
|
21
|
-
try {
|
|
22
|
-
await access(filePath);
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
function serializeManifest(manifest) {
|
|
30
|
-
return `${JSON.stringify(manifest, null, 2)}\n`;
|
|
31
|
-
}
|
|
32
|
-
export async function materializePrivateVerifierManifest(raw) {
|
|
33
|
-
const input = privateVerifierManifestInputSchema.parse(raw);
|
|
34
|
-
const withoutHash = {
|
|
35
|
-
schemaVersion: 1,
|
|
36
|
-
privateVerifierId: input.privateVerifierId,
|
|
37
|
-
createdAt: input.createdAt,
|
|
38
|
-
...(input.description !== undefined
|
|
39
|
-
? { description: input.description }
|
|
40
|
-
: {}),
|
|
41
|
-
checks: input.checks,
|
|
42
|
-
};
|
|
43
|
-
const manifestHash = computePrivateVerifierManifestHash(withoutHash);
|
|
44
|
-
if (input.manifestHash) {
|
|
45
|
-
const provided = formatContentSha(normalizeContentSha(input.manifestHash));
|
|
46
|
-
if (provided !== manifestHash) {
|
|
47
|
-
throw new Error(`privateVerifier manifestHash mismatch: expected ${manifestHash}, got ${provided}`);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return privateVerifierManifestSchema.parse({
|
|
51
|
-
...withoutHash,
|
|
52
|
-
manifestHash,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
export async function loadPrivateVerifierManifestFromPath(repoRoot, manifestPath) {
|
|
56
|
-
const resolved = path.isAbsolute(manifestPath)
|
|
57
|
-
? manifestPath
|
|
58
|
-
: path.resolve(repoRoot, manifestPath);
|
|
59
|
-
const raw = JSON.parse(await readFile(resolved, "utf-8"));
|
|
60
|
-
return privateVerifierManifestInputSchema.parse(raw);
|
|
61
|
-
}
|
|
62
|
-
export async function readPrivateVerifierManifest(repoRoot, privateVerifierId) {
|
|
63
|
-
assertPrivateVerifierId(privateVerifierId);
|
|
64
|
-
const manifestPath = privateVerifierManifestPath(repoRoot, privateVerifierId);
|
|
65
|
-
const rawText = await readFile(manifestPath, "utf-8");
|
|
66
|
-
const integrityPath = path.join(privateVerifierDir(repoRoot, privateVerifierId), MANIFEST_INTEGRITY_FILE);
|
|
67
|
-
const expected = (await readFile(integrityPath, "utf-8")).trim();
|
|
68
|
-
const actual = sha256Hex(rawText);
|
|
69
|
-
if (expected !== actual) {
|
|
70
|
-
throw new Error(`privateVerifier manifest integrity mismatch for ${privateVerifierId}`);
|
|
71
|
-
}
|
|
72
|
-
const stored = privateVerifierManifestSchema.parse(JSON.parse(rawText));
|
|
73
|
-
if (stored.privateVerifierId !== privateVerifierId) {
|
|
74
|
-
throw new Error(`privateVerifier identity mismatch: directory=${privateVerifierId}, manifest=${stored.privateVerifierId}`);
|
|
75
|
-
}
|
|
76
|
-
assertPrivateVerifierHash(stored);
|
|
77
|
-
return stored;
|
|
78
|
-
}
|
|
79
|
-
export async function listPrivateVerifierIds(repoRoot) {
|
|
80
|
-
const root = path.join(repoRoot, EVALUATION_ROOT, "private-verifiers");
|
|
81
|
-
try {
|
|
82
|
-
const entries = await readdir(root, { withFileTypes: true });
|
|
83
|
-
return entries
|
|
84
|
-
.filter((entry) => entry.isDirectory() && SAFE_ID.test(entry.name))
|
|
85
|
-
.map((entry) => entry.name)
|
|
86
|
-
.sort();
|
|
87
|
-
}
|
|
88
|
-
catch (error) {
|
|
89
|
-
const code = error.code;
|
|
90
|
-
if (code === "ENOENT")
|
|
91
|
-
return [];
|
|
92
|
-
throw error;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
function manifestsEqual(a, b) {
|
|
96
|
-
return (a.privateVerifierId === b.privateVerifierId &&
|
|
97
|
-
a.manifestHash === b.manifestHash &&
|
|
98
|
-
a.createdAt === b.createdAt &&
|
|
99
|
-
(a.description ?? "") === (b.description ?? "") &&
|
|
100
|
-
JSON.stringify(a.checks) === JSON.stringify(b.checks));
|
|
101
|
-
}
|
|
102
|
-
export async function registerPrivateVerifierManifest(input) {
|
|
103
|
-
const { repoRoot, manifest } = input;
|
|
104
|
-
assertPrivateVerifierId(manifest.privateVerifierId);
|
|
105
|
-
const manifestPath = privateVerifierManifestPath(repoRoot, manifest.privateVerifierId);
|
|
106
|
-
if (await pathExists(manifestPath)) {
|
|
107
|
-
const existing = await readPrivateVerifierManifest(repoRoot, manifest.privateVerifierId);
|
|
108
|
-
if (!manifestsEqual(existing, manifest)) {
|
|
109
|
-
throw new Error(`privateVerifier already exists with different content: ${manifest.privateVerifierId}`);
|
|
110
|
-
}
|
|
111
|
-
return { manifest: existing, idempotent: true, manifestPath };
|
|
112
|
-
}
|
|
113
|
-
const root = path.dirname(privateVerifierDir(repoRoot, manifest.privateVerifierId));
|
|
114
|
-
await mkdir(root, { recursive: true });
|
|
115
|
-
const stagingDir = await mkdtemp(path.join(root, `.${manifest.privateVerifierId}.register-`));
|
|
116
|
-
try {
|
|
117
|
-
await writeJsonAtomic(path.join(stagingDir, "manifest.json"), manifest, {
|
|
118
|
-
repoRoot,
|
|
119
|
-
});
|
|
120
|
-
await writeTextAtomic(path.join(stagingDir, MANIFEST_INTEGRITY_FILE), `${sha256Hex(serializeManifest(manifest))}\n`, { repoRoot });
|
|
121
|
-
try {
|
|
122
|
-
await rename(stagingDir, privateVerifierDir(repoRoot, manifest.privateVerifierId));
|
|
123
|
-
}
|
|
124
|
-
catch (error) {
|
|
125
|
-
const code = error.code;
|
|
126
|
-
const isCreateRace = code === "EEXIST" ||
|
|
127
|
-
code === "ENOTEMPTY" ||
|
|
128
|
-
(code === "EPERM" &&
|
|
129
|
-
(await pathExists(privateVerifierDir(repoRoot, manifest.privateVerifierId))));
|
|
130
|
-
if (!isCreateRace)
|
|
131
|
-
throw error;
|
|
132
|
-
await rm(stagingDir, { recursive: true, force: true });
|
|
133
|
-
const existing = await readPrivateVerifierManifest(repoRoot, manifest.privateVerifierId);
|
|
134
|
-
if (!manifestsEqual(existing, manifest)) {
|
|
135
|
-
throw new Error(`privateVerifier already exists with different content: ${manifest.privateVerifierId}`);
|
|
136
|
-
}
|
|
137
|
-
return { manifest: existing, idempotent: true, manifestPath };
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
catch (error) {
|
|
141
|
-
await rm(stagingDir, { recursive: true, force: true });
|
|
142
|
-
throw error;
|
|
143
|
-
}
|
|
144
|
-
return { manifest, idempotent: false, manifestPath };
|
|
145
|
-
}
|