@codyswann/lisa 4.73.9 → 4.74.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/README.md +52 -3
- package/all/create-only/.github/workflows/continuous-gates.yml +2 -2
- package/all/create-only/.github/workflows/workflow-load-failure-sweep.yml +1 -1
- package/cdk/create-only/.github/workflows/ci.yml +2 -2
- package/cdk/create-only/.github/workflows/deploy.yml +3 -3
- package/cdk/package-lisa/package.lisa.json +1 -1
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +60 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/lisa-owned-templates.d.ts +9 -2
- package/dist/core/lisa-owned-templates.d.ts.map +1 -1
- package/dist/core/lisa-owned-templates.js +13 -3
- package/dist/core/lisa-owned-templates.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +87 -33
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/expo/copy-overwrite/jest.setup.pre.js +15 -1
- package/expo/copy-overwrite/scripts/bdd/ci-summary.mjs +37 -0
- package/expo/copy-overwrite/scripts/bdd/projection-model.mjs +157 -0
- package/expo/copy-overwrite/scripts/bdd/projection-ownership.mjs +300 -0
- package/expo/copy-overwrite/scripts/bdd/projection-paths.mjs +152 -0
- package/expo/copy-overwrite/scripts/bdd/projection-render.mjs +303 -0
- package/expo/copy-overwrite/scripts/bdd/projection-write.mjs +84 -0
- package/expo/copy-overwrite/scripts/bdd/projection.mjs +30 -0
- package/expo/copy-overwrite/scripts/bdd-matrix.mjs +54 -18
- package/expo/copy-overwrite/scripts/check-bdd-coverage.mjs +36 -27
- package/expo/create-only/.github/workflows/ci.yml +3 -3
- package/expo/create-only/.github/workflows/deploy.yml +4 -4
- package/expo/create-only/.github/workflows/nightly-e2e-bypass-reaper.yml +1 -1
- package/expo/create-only/.github/workflows/playwright-e2e.yml +1 -1
- package/expo/create-only/eas.json +1 -1
- package/harper-fabric/copy-overwrite/.github/workflows/ci.yml +1 -1
- package/harper-fabric/create-only/.github/workflows/deploy.yml +3 -3
- package/harper-fabric/create-only/.github/workflows/zap-baseline.yml +1 -1
- package/harper-fabric/package-lisa/package.lisa.json +2 -2
- package/nestjs/create-only/.github/workflows/ci.yml +2 -2
- package/nestjs/create-only/.github/workflows/deploy.yml +5 -5
- package/package.json +6 -6
- package/phaser/copy-overwrite/.github/workflows/ci.yml +1 -1
- package/phaser/package-lisa/package.lisa.json +2 -2
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-parity-sentry-sdk-setup/SKILL.md +104 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-workstation/scripts/workstation.mjs +3 -1
- package/plugins/lisa/rules/reference/bdd-e2e-coverage.md +9 -7
- package/plugins/lisa/skills/lisa-parity-sentry-sdk-setup/SKILL.md +104 -0
- package/plugins/lisa/skills/lisa-setup-workstation/scripts/workstation.mjs +3 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-parity-sentry-sdk-setup/SKILL.md +104 -0
- package/plugins/lisa-agy/skills/lisa-setup-workstation/scripts/workstation.mjs +3 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/rules/reference/bdd-e2e-coverage.md +9 -7
- package/plugins/lisa-copilot/skills/lisa-parity-sentry-sdk-setup/SKILL.md +104 -0
- package/plugins/lisa-copilot/skills/lisa-setup-workstation/scripts/workstation.mjs +3 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/rules/bdd-e2e-coverage-reference.mdc +9 -7
- package/plugins/lisa-cursor/skills/lisa-parity-sentry-sdk-setup/SKILL.md +104 -0
- package/plugins/lisa-cursor/skills/lisa-setup-workstation/scripts/workstation.mjs +3 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/rules/reference/bdd-e2e-coverage.md +9 -7
- package/plugins/src/base/skills/lisa-parity-sentry-sdk-setup/SKILL.md +104 -0
- package/plugins/src/base/skills/lisa-setup-workstation/scripts/workstation.mjs +3 -1
- package/scripts/check-shell-guard-refusal-coverage.mjs +65 -7
- package/scripts/generate-lisa-owned-hash-ledger.mjs +3 -0
- package/scripts/lib/shell-guard-shard-runner.mjs +381 -0
- package/scripts/lib/shell-guard-shards.mjs +383 -0
- package/scripts/lib/shell-guard-terminal-reporter.mjs +89 -0
- package/scripts/two-channel-couplings.json +2 -2
- package/scripts/update-node-version.ts +52 -6
- package/typescript/copy-overwrite/.nvmrc +1 -1
- package/typescript/create-only/.github/workflows/ci.yml +1 -1
- package/typescript/create-only/.github/workflows/review-evidence.yml +1 -1
- package/typescript/create-only/.github/workflows/third-party-review-evidence.yml +1 -1
- package/typescript/package-lisa/package.lisa.json +1 -1
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/** Collect and combine complete native shell-guard shard receipts. */
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
4
|
+
import {
|
|
5
|
+
closeSync,
|
|
6
|
+
lstatSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
openSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
readdirSync,
|
|
11
|
+
realpathSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import process from "node:process";
|
|
16
|
+
import {
|
|
17
|
+
guardPopulation,
|
|
18
|
+
tracerIndex,
|
|
19
|
+
} from "./shell-guard-refusal-coverage.mjs";
|
|
20
|
+
import {
|
|
21
|
+
identityHash,
|
|
22
|
+
mergeBundles,
|
|
23
|
+
nativeArgv,
|
|
24
|
+
parseShard,
|
|
25
|
+
sha256,
|
|
26
|
+
validateBundle,
|
|
27
|
+
validateSnapshot,
|
|
28
|
+
} from "./shell-guard-shards.mjs";
|
|
29
|
+
|
|
30
|
+
const INDEX_FILE = "guard-index.json";
|
|
31
|
+
const EXECUTION_FILE = "execution.json";
|
|
32
|
+
const STDOUT_FILE = "stdout.log";
|
|
33
|
+
const STDERR_FILE = "stderr.log";
|
|
34
|
+
const RUN_TIMEOUT_MS = 45 * 60_000;
|
|
35
|
+
const GIT_TIMEOUT_MS = 60_000;
|
|
36
|
+
const MAX_ARTIFACT_BYTES = 256 * 1024 * 1024;
|
|
37
|
+
const SOURCES = [
|
|
38
|
+
"scripts/check-shell-guard-refusal-coverage.mjs",
|
|
39
|
+
"scripts/lib/shell-guard-trace.mjs",
|
|
40
|
+
"scripts/lib/shell-guard-refusal-coverage.mjs",
|
|
41
|
+
"scripts/lib/shell-guard-shards.mjs",
|
|
42
|
+
"scripts/lib/shell-guard-shard-runner.mjs",
|
|
43
|
+
"scripts/lib/shell-guard-terminal-reporter.mjs",
|
|
44
|
+
"vitest.config.ts",
|
|
45
|
+
"vitest.config.local.ts",
|
|
46
|
+
"vitest.thresholds.json",
|
|
47
|
+
"bun.lock",
|
|
48
|
+
"package.json",
|
|
49
|
+
".github/workflows/plugins-sync.yml",
|
|
50
|
+
"dist/cli/lisa-test-run.js",
|
|
51
|
+
"node_modules/vitest/package.json",
|
|
52
|
+
"node_modules/vitest/vitest.mjs",
|
|
53
|
+
];
|
|
54
|
+
const git = (root, args) =>
|
|
55
|
+
execFileSync("git", args, {
|
|
56
|
+
cwd: root,
|
|
57
|
+
encoding: "utf8",
|
|
58
|
+
timeout: GIT_TIMEOUT_MS,
|
|
59
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
60
|
+
}).trim();
|
|
61
|
+
const bytes = file => {
|
|
62
|
+
const stat = lstatSync(file);
|
|
63
|
+
assert(
|
|
64
|
+
stat.isFile() && stat.size <= MAX_ARTIFACT_BYTES,
|
|
65
|
+
"missing/link/oversized artifact"
|
|
66
|
+
);
|
|
67
|
+
return readFileSync(file);
|
|
68
|
+
};
|
|
69
|
+
function provider() {
|
|
70
|
+
const row = {
|
|
71
|
+
runId: process.env.GITHUB_RUN_ID,
|
|
72
|
+
attempt: process.env.GITHUB_RUN_ATTEMPT,
|
|
73
|
+
sourceSha: process.env.GITHUB_SHA,
|
|
74
|
+
prHead: process.env.LISA_GUARD_PR_HEAD,
|
|
75
|
+
workflowRef: process.env.GITHUB_WORKFLOW_REF,
|
|
76
|
+
};
|
|
77
|
+
assert(
|
|
78
|
+
Object.values(row).every(
|
|
79
|
+
value => typeof value === "string" && value.length > 0
|
|
80
|
+
),
|
|
81
|
+
"actual native Actions provenance required for shard mode"
|
|
82
|
+
);
|
|
83
|
+
return row;
|
|
84
|
+
}
|
|
85
|
+
function binary(file) {
|
|
86
|
+
assert(
|
|
87
|
+
typeof file === "string" && path.isAbsolute(file),
|
|
88
|
+
"qualified executable missing"
|
|
89
|
+
);
|
|
90
|
+
const actual = realpathSync(file);
|
|
91
|
+
return {
|
|
92
|
+
sha256: sha256(bytes(actual)),
|
|
93
|
+
version: execFileSync(actual, ["--version"], {
|
|
94
|
+
encoding: "utf8",
|
|
95
|
+
timeout: 15_000,
|
|
96
|
+
}).trim(),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function codeFiles(root, directory) {
|
|
100
|
+
const files = [];
|
|
101
|
+
const walk = current => {
|
|
102
|
+
for (const entry of readdirSync(path.join(root, current), {
|
|
103
|
+
withFileTypes: true,
|
|
104
|
+
})) {
|
|
105
|
+
const file = path.join(current, entry.name);
|
|
106
|
+
assert(!entry.isSymbolicLink(), "unexpected linked code member");
|
|
107
|
+
if (entry.isDirectory()) walk(file);
|
|
108
|
+
else if (/\.(?:js|mjs|ts)$/u.test(entry.name)) files.push(file);
|
|
109
|
+
assert(files.length <= 50_000, "code inventory exceeds finite bound");
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
walk(directory);
|
|
113
|
+
return files.sort();
|
|
114
|
+
}
|
|
115
|
+
export function snapshot(root, discover) {
|
|
116
|
+
// A dirty tracked tree cannot claim the immutable HEAD/tree it names.
|
|
117
|
+
execFileSync("git", ["diff", "--quiet", "HEAD", "--"], {
|
|
118
|
+
cwd: root,
|
|
119
|
+
timeout: GIT_TIMEOUT_MS,
|
|
120
|
+
});
|
|
121
|
+
const head = git(root, ["rev-parse", "HEAD"]),
|
|
122
|
+
tree = git(root, ["rev-parse", "HEAD^{tree}"]);
|
|
123
|
+
const candidates = discover(root).map(file => ({
|
|
124
|
+
path: file,
|
|
125
|
+
sha256: sha256(readFileSync(path.join(root, file))),
|
|
126
|
+
}));
|
|
127
|
+
const population = guardPopulation(root);
|
|
128
|
+
const bunPath = execFileSync("bun", ["-p", "process.execPath"], {
|
|
129
|
+
encoding: "utf8",
|
|
130
|
+
timeout: 15_000,
|
|
131
|
+
}).trim();
|
|
132
|
+
const runtime = {
|
|
133
|
+
node: binary(process.execPath),
|
|
134
|
+
abi: process.versions.modules,
|
|
135
|
+
platform: process.platform,
|
|
136
|
+
arch: process.arch,
|
|
137
|
+
bun: binary(bunPath),
|
|
138
|
+
updaterNode: binary(process.env.LISA_TEST_UPDATER_NODE),
|
|
139
|
+
nativeHostBun: binary(process.env.LISA_TEST_HOST_BUN),
|
|
140
|
+
};
|
|
141
|
+
const row = {
|
|
142
|
+
head,
|
|
143
|
+
tree,
|
|
144
|
+
provider: provider(),
|
|
145
|
+
candidates,
|
|
146
|
+
candidateHash: identityHash(candidates),
|
|
147
|
+
population,
|
|
148
|
+
populationHash: identityHash(population),
|
|
149
|
+
scriptHashes: Object.fromEntries(
|
|
150
|
+
[
|
|
151
|
+
...SOURCES,
|
|
152
|
+
...codeFiles(root, "src/configs/vitest"),
|
|
153
|
+
...codeFiles(root, "dist/configs/vitest"),
|
|
154
|
+
...codeFiles(root, "node_modules/vitest/dist"),
|
|
155
|
+
].map(file => [file, sha256(bytes(path.join(root, file)))])
|
|
156
|
+
),
|
|
157
|
+
runtime,
|
|
158
|
+
};
|
|
159
|
+
return validateSnapshot(row);
|
|
160
|
+
}
|
|
161
|
+
function value(args, flag) {
|
|
162
|
+
const indexes = args.flatMap((arg, at) => (arg === flag ? [at] : []));
|
|
163
|
+
assert(
|
|
164
|
+
indexes.length === 1 &&
|
|
165
|
+
args[indexes[0] + 1] &&
|
|
166
|
+
!args[indexes[0] + 1].startsWith("--"),
|
|
167
|
+
`explicit unique ${flag} required`
|
|
168
|
+
);
|
|
169
|
+
return args[indexes[0] + 1];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function collectShard(root, discover, args) {
|
|
173
|
+
assert(!args.includes("--trace") && !args.includes("--merge-traces"));
|
|
174
|
+
const shard = parseShard(value(args, "--shard"));
|
|
175
|
+
const tracePath = path.resolve(root, value(args, "--trace-out"));
|
|
176
|
+
// Require a newly owned per-shard directory. No stale files can survive a retry.
|
|
177
|
+
const directory = path.dirname(tracePath);
|
|
178
|
+
assert(
|
|
179
|
+
directory !== root && path.basename(tracePath) === "trace.jsonl",
|
|
180
|
+
"owned trace directory/trace.jsonl required"
|
|
181
|
+
);
|
|
182
|
+
mkdirSync(directory, { recursive: false, mode: 0o700 });
|
|
183
|
+
assert(
|
|
184
|
+
realpathSync(directory) === directory,
|
|
185
|
+
"symlinked collection directory"
|
|
186
|
+
);
|
|
187
|
+
const before = snapshot(root, discover);
|
|
188
|
+
const reportPath = path.join(directory, "results.json"),
|
|
189
|
+
terminalPath = path.join(directory, "terminal.json");
|
|
190
|
+
writeFileSync(tracePath, "", { flag: "wx" });
|
|
191
|
+
writeFileSync(
|
|
192
|
+
path.join(directory, INDEX_FILE),
|
|
193
|
+
JSON.stringify(tracerIndex(before.population)),
|
|
194
|
+
{ flag: "wx" }
|
|
195
|
+
);
|
|
196
|
+
const command = path.join(root, "node_modules/.bin/vitest");
|
|
197
|
+
const argv = nativeArgv(root, directory, shard, before.candidates);
|
|
198
|
+
const stdout = openSync(path.join(directory, STDOUT_FILE), "wx"),
|
|
199
|
+
stderr = openSync(path.join(directory, STDERR_FILE), "wx");
|
|
200
|
+
const startedAt = new Date().toISOString();
|
|
201
|
+
let child;
|
|
202
|
+
console.log(
|
|
203
|
+
`Collecting native shard ${shard.index}/${shard.total} from ALL ${before.candidates.length} candidates; raw logs: ${directory}`
|
|
204
|
+
);
|
|
205
|
+
try {
|
|
206
|
+
child = spawnSync(command, argv, {
|
|
207
|
+
cwd: root,
|
|
208
|
+
timeout: RUN_TIMEOUT_MS,
|
|
209
|
+
stdio: ["ignore", stdout, stderr],
|
|
210
|
+
env: {
|
|
211
|
+
...process.env,
|
|
212
|
+
LISA_SHELL_GUARD_TRACE: tracePath,
|
|
213
|
+
LISA_SHELL_GUARD_INDEX: path.join(directory, INDEX_FILE),
|
|
214
|
+
LISA_GUARD_TERMINAL_REPORT: terminalPath,
|
|
215
|
+
NODE_OPTIONS:
|
|
216
|
+
`${process.env.NODE_OPTIONS ?? ""} --import=${path.join(root, "scripts/lib/shell-guard-trace.mjs")}`.trim(),
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
} finally {
|
|
220
|
+
closeSync(stdout);
|
|
221
|
+
closeSync(stderr);
|
|
222
|
+
}
|
|
223
|
+
const execution = {
|
|
224
|
+
command,
|
|
225
|
+
argv,
|
|
226
|
+
startedAt,
|
|
227
|
+
endedAt: new Date().toISOString(),
|
|
228
|
+
status: child.status,
|
|
229
|
+
signal: child.signal,
|
|
230
|
+
error: child.error ? String(child.error) : null,
|
|
231
|
+
};
|
|
232
|
+
writeFileSync(
|
|
233
|
+
path.join(directory, EXECUTION_FILE),
|
|
234
|
+
JSON.stringify(execution)
|
|
235
|
+
);
|
|
236
|
+
assert(
|
|
237
|
+
execution.status === 0 &&
|
|
238
|
+
execution.signal === null &&
|
|
239
|
+
execution.error === null,
|
|
240
|
+
"native shard failed/killed/not started; no eligible manifest"
|
|
241
|
+
);
|
|
242
|
+
assert.deepEqual(
|
|
243
|
+
snapshot(root, discover),
|
|
244
|
+
before,
|
|
245
|
+
"source/runtime changed during shard"
|
|
246
|
+
);
|
|
247
|
+
const traceRaw = bytes(tracePath).toString("utf8"),
|
|
248
|
+
reportRaw = bytes(reportPath).toString("utf8"),
|
|
249
|
+
terminalRaw = bytes(terminalPath).toString("utf8");
|
|
250
|
+
assert(process.env.GITHUB_JOB, "actual producer job identifier required");
|
|
251
|
+
const raw = Object.fromEntries(
|
|
252
|
+
[INDEX_FILE, EXECUTION_FILE, STDOUT_FILE, STDERR_FILE].map(name => [
|
|
253
|
+
name,
|
|
254
|
+
bytes(path.join(directory, name)),
|
|
255
|
+
])
|
|
256
|
+
);
|
|
257
|
+
const manifest = {
|
|
258
|
+
schema: 1,
|
|
259
|
+
complete: true,
|
|
260
|
+
snapshot: before,
|
|
261
|
+
shard,
|
|
262
|
+
checkoutRoot: root,
|
|
263
|
+
artifactRoot: directory,
|
|
264
|
+
producerJob: process.env.GITHUB_JOB,
|
|
265
|
+
child: execution,
|
|
266
|
+
rawHashes: Object.fromEntries(
|
|
267
|
+
Object.entries(raw).map(([name, data]) => [name, sha256(data)])
|
|
268
|
+
),
|
|
269
|
+
traceSha256: sha256(traceRaw),
|
|
270
|
+
reportSha256: sha256(reportRaw),
|
|
271
|
+
terminalSha256: sha256(terminalRaw),
|
|
272
|
+
};
|
|
273
|
+
// Derive names ONLY from the actual terminal native reporter, never a guessed shard algorithm.
|
|
274
|
+
const names = JSON.parse(reportRaw)
|
|
275
|
+
.testResults.map(file =>
|
|
276
|
+
path.relative(root, file.name).split(path.sep).join("/")
|
|
277
|
+
)
|
|
278
|
+
.sort((a, b) => a.localeCompare(b));
|
|
279
|
+
manifest.reportedFiles = names;
|
|
280
|
+
manifest.reportedFileCount = names.length;
|
|
281
|
+
validateBundle({
|
|
282
|
+
snapshot: before,
|
|
283
|
+
manifest,
|
|
284
|
+
traceRaw,
|
|
285
|
+
reportRaw,
|
|
286
|
+
terminalRaw,
|
|
287
|
+
indexRaw: raw[INDEX_FILE].toString("utf8"),
|
|
288
|
+
executionRaw: raw[EXECUTION_FILE].toString("utf8"),
|
|
289
|
+
stdoutRaw: raw[STDOUT_FILE],
|
|
290
|
+
stderrRaw: raw[STDERR_FILE],
|
|
291
|
+
});
|
|
292
|
+
writeFileSync(
|
|
293
|
+
path.join(directory, "manifest.json"),
|
|
294
|
+
JSON.stringify(manifest, null, 2),
|
|
295
|
+
{ flag: "wx" }
|
|
296
|
+
);
|
|
297
|
+
console.log(
|
|
298
|
+
`Eligible completed native shard ${shard.index}/${shard.total}: ${names.length} reported files`
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function mergeCollected(root, discover, args) {
|
|
303
|
+
assert(
|
|
304
|
+
!args.includes("--collect-only") &&
|
|
305
|
+
!args.includes("--shard") &&
|
|
306
|
+
!args.includes("--trace")
|
|
307
|
+
);
|
|
308
|
+
const directory = path.resolve(root, value(args, "--merge-traces"));
|
|
309
|
+
assert(
|
|
310
|
+
directory !== root &&
|
|
311
|
+
realpathSync(directory) === directory &&
|
|
312
|
+
lstatSync(directory).isDirectory(),
|
|
313
|
+
"explicit owned download directory required"
|
|
314
|
+
);
|
|
315
|
+
const entries = readdirSync(directory, { withFileTypes: true });
|
|
316
|
+
const current = snapshot(root, discover);
|
|
317
|
+
const prefix = `lisa-shell-guard-${current.provider.runId}-${current.provider.attempt}-shard-`;
|
|
318
|
+
assert(
|
|
319
|
+
entries.length === 3 &&
|
|
320
|
+
entries.every(
|
|
321
|
+
entry =>
|
|
322
|
+
entry.isDirectory() &&
|
|
323
|
+
[1, 2, 3].some(index => entry.name === prefix + index)
|
|
324
|
+
),
|
|
325
|
+
"missing/extra/nonowned/stale-attempt shard bundle"
|
|
326
|
+
);
|
|
327
|
+
const bundles = entries.map(entry => {
|
|
328
|
+
const base = path.join(directory, entry.name);
|
|
329
|
+
assert(realpathSync(base) === base);
|
|
330
|
+
const manifestRaw = bytes(path.join(base, "manifest.json")).toString(
|
|
331
|
+
"utf8"
|
|
332
|
+
);
|
|
333
|
+
const manifest = JSON.parse(manifestRaw);
|
|
334
|
+
assert(
|
|
335
|
+
entry.name === prefix + manifest.shard.index,
|
|
336
|
+
"bundle name/shard mismatch"
|
|
337
|
+
);
|
|
338
|
+
return {
|
|
339
|
+
manifest,
|
|
340
|
+
manifestRaw,
|
|
341
|
+
traceRaw: bytes(path.join(base, "trace.jsonl")).toString("utf8"),
|
|
342
|
+
reportRaw: bytes(path.join(base, "results.json")).toString("utf8"),
|
|
343
|
+
terminalRaw: bytes(path.join(base, "terminal.json")).toString("utf8"),
|
|
344
|
+
indexRaw: bytes(path.join(base, INDEX_FILE)).toString("utf8"),
|
|
345
|
+
executionRaw: bytes(path.join(base, EXECUTION_FILE)).toString("utf8"),
|
|
346
|
+
stdoutRaw: bytes(path.join(base, STDOUT_FILE)),
|
|
347
|
+
stderrRaw: bytes(path.join(base, STDERR_FILE)),
|
|
348
|
+
};
|
|
349
|
+
});
|
|
350
|
+
const trace = mergeBundles(current, bundles);
|
|
351
|
+
const target = path.resolve(root, value(args, "--trace-out"));
|
|
352
|
+
mkdirSync(path.dirname(target), { recursive: true });
|
|
353
|
+
// Aggregate provenance says complete shards were validated, not that guards passed.
|
|
354
|
+
const provenance = {
|
|
355
|
+
schema: 1,
|
|
356
|
+
snapshot: current,
|
|
357
|
+
traceSha256: sha256(trace),
|
|
358
|
+
reportedFileCount: current.candidates.length,
|
|
359
|
+
shards: bundles.map(bundle => ({
|
|
360
|
+
index: bundle.manifest.shard.index,
|
|
361
|
+
manifestSha256: sha256(bundle.manifestRaw),
|
|
362
|
+
producerJob: bundle.manifest.producerJob,
|
|
363
|
+
child: bundle.manifest.child,
|
|
364
|
+
})),
|
|
365
|
+
scope:
|
|
366
|
+
"complete current native shards validated; ONE existing guard judgment remains required",
|
|
367
|
+
providerArtifactDigestVerified: false,
|
|
368
|
+
};
|
|
369
|
+
writeFileSync(target, trace);
|
|
370
|
+
writeFileSync(
|
|
371
|
+
`${target}.provenance.json`,
|
|
372
|
+
JSON.stringify(provenance, null, 2)
|
|
373
|
+
);
|
|
374
|
+
return {
|
|
375
|
+
trace,
|
|
376
|
+
suites: current.candidates.length,
|
|
377
|
+
ok: true,
|
|
378
|
+
detail: "",
|
|
379
|
+
snapshot: current,
|
|
380
|
+
};
|
|
381
|
+
}
|