@codyswann/lisa 4.70.3 → 4.71.0
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/all/copy-overwrite/scripts/lib/automation-provenance-contract.mjs +174 -0
- package/all/copy-overwrite/scripts/lib/automation-provenance-local.mjs +279 -0
- package/all/copy-overwrite/scripts/lib/gate-failure-diagnosis.mjs +17 -7
- package/all/copy-overwrite/scripts/lib/github-attestation-provider.mjs +241 -0
- package/all/copy-overwrite/scripts/lib/github-attestation-recovery.mjs +283 -0
- package/all/copy-overwrite/scripts/lib/github-attestation-verifier.mjs +244 -0
- package/all/copy-overwrite/scripts/lib/history-secret-evidence-shape.mjs +188 -0
- package/all/copy-overwrite/scripts/lib/history-secret-evidence.mjs +271 -0
- package/all/copy-overwrite/scripts/lib/history-secret-scanner.mjs +6 -296
- package/all/copy-overwrite/scripts/lib/npm-update-allocate.mjs +289 -0
- package/all/copy-overwrite/scripts/lib/npm-update-authorization.mjs +235 -0
- package/all/copy-overwrite/scripts/lib/npm-update-broker-client.mjs +92 -0
- package/all/copy-overwrite/scripts/lib/npm-update-checkpoint.mjs +241 -0
- package/all/copy-overwrite/scripts/lib/npm-update-contract.mjs +278 -0
- package/all/copy-overwrite/scripts/lib/npm-update-controller-broker.mjs +234 -0
- package/all/copy-overwrite/scripts/lib/npm-update-controller-factory.mjs +278 -0
- package/all/copy-overwrite/scripts/lib/npm-update-controller-recipe.mjs +135 -0
- package/all/copy-overwrite/scripts/lib/npm-update-execution-adapter.mjs +292 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate-hooks.mjs +214 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate-install.mjs +267 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate-proof.mjs +99 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate.mjs +300 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gh-dispatch.mjs +182 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gh-grants.mjs +156 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gh-requests.mjs +221 -0
- package/all/copy-overwrite/scripts/lib/npm-update-github.mjs +214 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper-graph.mjs +238 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper-inventory.mjs +126 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper.mjs +292 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-installation.mjs +116 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-preload.mjs +153 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-provider.mjs +207 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-read-client.mjs +164 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hosted-gate.mjs +272 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hosted-hook.mjs +157 -0
- package/all/copy-overwrite/scripts/lib/npm-update-invariants.mjs +28 -0
- package/all/copy-overwrite/scripts/lib/npm-update-isolation.mjs +11 -0
- package/all/copy-overwrite/scripts/lib/npm-update-leaf-contract.mjs +171 -0
- package/all/copy-overwrite/scripts/lib/npm-update-leaf.mjs +194 -0
- package/all/copy-overwrite/scripts/lib/npm-update-native-process.mjs +155 -0
- package/all/copy-overwrite/scripts/lib/npm-update-npm.mjs +190 -0
- package/all/copy-overwrite/scripts/lib/npm-update-object.mjs +112 -0
- package/all/copy-overwrite/scripts/lib/npm-update-orchestrator.mjs +273 -0
- package/all/copy-overwrite/scripts/lib/npm-update-owner.mjs +292 -0
- package/all/copy-overwrite/scripts/lib/npm-update-prepare.mjs +263 -0
- package/all/copy-overwrite/scripts/lib/npm-update-process-core.mjs +179 -0
- package/all/copy-overwrite/scripts/lib/npm-update-process.mjs +16 -0
- package/all/copy-overwrite/scripts/lib/npm-update-publication.mjs +258 -0
- package/all/copy-overwrite/scripts/lib/npm-update-publish.mjs +258 -0
- package/all/copy-overwrite/scripts/lib/npm-update-quality.mjs +153 -0
- package/all/copy-overwrite/scripts/lib/npm-update-recovery.mjs +264 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime-archive.mjs +204 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime-graph.mjs +185 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime-transport.mjs +221 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime.mjs +284 -0
- package/all/copy-overwrite/scripts/lib/npm-update-tool-launcher.mjs +287 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-environment.mjs +161 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-inspection.mjs +142 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-lifecycle.mjs +256 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-policy.mjs +266 -0
- package/all/copy-overwrite/scripts/lib/process-tree-runner.mjs +230 -36
- package/all/copy-overwrite/scripts/lisa-automation-provenance.mjs +161 -0
- package/all/copy-overwrite/scripts/lisa-history-secrets.mjs +8 -1
- package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +89 -5
- package/all/copy-overwrite/scripts/lisa-npm-updater.mjs +204 -0
- package/all/copy-overwrite/scripts/lisa-run-gates.mjs +48 -123
- package/all/copy-overwrite/scripts/lisa-work-item.mjs +107 -41
- package/all/copy-overwrite/scripts/npm-updater-gate-runtime.json +1 -0
- package/all/copy-overwrite/scripts/npm-updater-gate-supervisor.c +121 -0
- package/all/copy-overwrite/scripts/npm-updater-gate.Dockerfile +49 -0
- package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +1085 -0
- package/dist/codex/scripts/block-no-verify.sh +89 -5
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +620 -0
- package/dist/core/lisa-owned-hash-ledger.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 +165 -13
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/block-no-verify.sh +89 -5
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/hooks/block-no-verify.agy.sh +89 -5
- package/plugins/lisa/hooks/block-no-verify.sh +89 -5
- package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +89 -5
- package/plugins/lisa-agy/plugin.json +1 -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/hooks/block-no-verify.sh +89 -5
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-no-verify.sh +89 -5
- 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/hooks/block-no-verify.agy.sh +89 -5
- package/plugins/src/base/hooks/block-no-verify.sh +89 -5
- package/scripts/check-shipped-surface-removals.mjs +96 -24
- package/scripts/generate-npm-updater-helper-graph.mjs +184 -0
- package/scripts/two-channel-couplings.json +31 -7
- package/typescript/copy-contents/.husky/commit-msg +18 -0
- package/typescript/copy-overwrite/.prettierignore +2 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Do not edit directly — durable changes belong upstream in Lisa.
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Run one shell command in a process boundary and reap every descendant.
|
|
6
|
+
* Run one shell command or explicit literal argv in a process boundary and reap every descendant.
|
|
7
7
|
*
|
|
8
8
|
* Node's synchronous timeout signals only the direct child. A gate command is
|
|
9
9
|
* a tree (shell, package manager, test workers), so killing only the shell
|
|
@@ -33,15 +33,87 @@
|
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
35
|
import { spawn, spawnSync } from "node:child_process";
|
|
36
|
+
import { writeSync } from "node:fs";
|
|
36
37
|
|
|
37
38
|
import { invokedAsScript } from "./invoked-as-script.mjs";
|
|
38
39
|
import { startWindowsProcessJob } from "./windows-process-job.mjs";
|
|
39
40
|
|
|
41
|
+
/** Diagnostic memory stays bounded independently of the complete live stream. */
|
|
42
|
+
const CAPTURE_TAIL_BYTES = 512 * 1024;
|
|
40
43
|
const KILL_GRACE_MS = 750;
|
|
41
44
|
const REAP_POLL_MS = 25;
|
|
42
45
|
const WINDOWS_TIMEOUT_EXIT_CODE = 255;
|
|
43
46
|
const TERMINATING_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
44
47
|
|
|
48
|
+
/** Same literal-dispatch envelope as the launcher; control overhead is not free. */
|
|
49
|
+
function snapshotArguments(argv) {
|
|
50
|
+
if (!Array.isArray(argv)) throw new Error("direct argv requires an array");
|
|
51
|
+
const copied = [...argv];
|
|
52
|
+
if (
|
|
53
|
+
copied.length > 512 ||
|
|
54
|
+
copied.some(
|
|
55
|
+
value =>
|
|
56
|
+
typeof value !== "string" ||
|
|
57
|
+
value.includes("\0") ||
|
|
58
|
+
Buffer.byteLength(value) > 65_536
|
|
59
|
+
) ||
|
|
60
|
+
Buffer.byteLength(JSON.stringify(copied)) > 262_144
|
|
61
|
+
)
|
|
62
|
+
throw new Error("invalid or unbounded direct argument vector");
|
|
63
|
+
return copied;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The direct API fails before any handlers or child backend acquire ownership. */
|
|
67
|
+
function directInvocation(command, args, timeoutMs) {
|
|
68
|
+
if (!["linux", "darwin"].includes(process.platform))
|
|
69
|
+
throw new Error("direct argv requires Linux or macOS");
|
|
70
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 1_800_000)
|
|
71
|
+
throw new Error("invalid direct command deadline");
|
|
72
|
+
if (
|
|
73
|
+
typeof command !== "string" ||
|
|
74
|
+
command.length === 0 ||
|
|
75
|
+
!Array.isArray(args)
|
|
76
|
+
)
|
|
77
|
+
throw new Error(
|
|
78
|
+
"direct argv requires a nonempty executable and argument array"
|
|
79
|
+
);
|
|
80
|
+
return snapshotArguments([command, ...args]).slice(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Only the explicit new-mode prefix is closed; legacy parsing remains tolerant. */
|
|
84
|
+
function parseDirectArguments(argv, separator) {
|
|
85
|
+
snapshotArguments([...process.execArgv, process.argv[1], ...argv]);
|
|
86
|
+
const vector = snapshotArguments(argv);
|
|
87
|
+
if (separator < 0) throw new Error("direct argv requires a separator");
|
|
88
|
+
const flags = vector.slice(0, separator);
|
|
89
|
+
const timeouts = flags.filter(value => /^--timeout-ms=\d+$/.test(value));
|
|
90
|
+
const capture = flags.filter(value => value === "--capture-fd=3");
|
|
91
|
+
const watches = flags.filter(value => /^--watch-pid=\d+$/.test(value));
|
|
92
|
+
if (
|
|
93
|
+
flags.filter(value => value === "--direct-argv").length !== 1 ||
|
|
94
|
+
timeouts.length !== 1 ||
|
|
95
|
+
capture.length > 1 ||
|
|
96
|
+
watches.some(
|
|
97
|
+
value => !isWatchablePid(Number(value.slice("--watch-pid=".length)))
|
|
98
|
+
) ||
|
|
99
|
+
flags.length !== 1 + timeouts.length + capture.length + watches.length
|
|
100
|
+
)
|
|
101
|
+
throw new Error("invalid direct supervisor control prefix");
|
|
102
|
+
const command = vector[separator + 1];
|
|
103
|
+
const timeoutMs = Number(timeouts[0].slice("--timeout-ms=".length));
|
|
104
|
+
return {
|
|
105
|
+
command,
|
|
106
|
+
timeoutMs,
|
|
107
|
+
directArgs: directInvocation(
|
|
108
|
+
command,
|
|
109
|
+
vector.slice(separator + 2),
|
|
110
|
+
timeoutMs
|
|
111
|
+
),
|
|
112
|
+
watchPids: parseWatchPids(watches),
|
|
113
|
+
captureFd: capture.length === 1 ? 3 : undefined,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
45
117
|
/**
|
|
46
118
|
* How often the supervisor asks whether the run it serves is still there.
|
|
47
119
|
*
|
|
@@ -103,6 +175,8 @@ function parseArguments(argv) {
|
|
|
103
175
|
const separator = argv.indexOf("--");
|
|
104
176
|
const timeoutMs = Number(timeoutArg?.slice("--timeout-ms=".length));
|
|
105
177
|
const flags = separator >= 0 ? argv.slice(0, separator) : argv;
|
|
178
|
+
if (flags.includes("--direct-argv"))
|
|
179
|
+
return parseDirectArguments(argv, separator);
|
|
106
180
|
const command = separator >= 0 ? argv.slice(separator + 1).join(" ") : "";
|
|
107
181
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || !command) {
|
|
108
182
|
throw new Error(
|
|
@@ -110,7 +184,20 @@ function parseArguments(argv) {
|
|
|
110
184
|
"[--watch-pid=<pid>]... -- <command>"
|
|
111
185
|
);
|
|
112
186
|
}
|
|
113
|
-
|
|
187
|
+
const captureFlags = flags.filter(value => value.startsWith("--capture-fd="));
|
|
188
|
+
if (
|
|
189
|
+
captureFlags.length > 1 ||
|
|
190
|
+
captureFlags.some(value => value !== "--capture-fd=3")
|
|
191
|
+
) {
|
|
192
|
+
throw new Error("capture requires the fixed caller descriptor 3");
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
command,
|
|
196
|
+
timeoutMs,
|
|
197
|
+
watchPids: parseWatchPids(flags),
|
|
198
|
+
captureFd: captureFlags.length === 1 ? 3 : undefined,
|
|
199
|
+
directArgs: undefined,
|
|
200
|
+
};
|
|
114
201
|
}
|
|
115
202
|
|
|
116
203
|
/**
|
|
@@ -403,6 +490,79 @@ export async function reapTree(pid, controls = DEFAULT_REAP_CONTROLS) {
|
|
|
403
490
|
throw new Error(`gate process tree ${pid} survived SIGKILL`);
|
|
404
491
|
}
|
|
405
492
|
|
|
493
|
+
/**
|
|
494
|
+
* Bound pipe drainage after tree reap by the existing two-phase cleanup budget.
|
|
495
|
+
* A missing close notification cannot postpone a verdict indefinitely; it is
|
|
496
|
+
* cleanup failure, and retained pipes are detached before the failure returns.
|
|
497
|
+
* @param {import("node:child_process").ChildProcess} child Owned command shell.
|
|
498
|
+
* @param {Promise<unknown>} closed Already-armed output completion observation.
|
|
499
|
+
* @returns {Promise<void>} Confirmed drainage, or a terminal cleanup failure.
|
|
500
|
+
*/
|
|
501
|
+
async function waitForOutputClose(child, closed) {
|
|
502
|
+
let timer;
|
|
503
|
+
try {
|
|
504
|
+
await Promise.race([
|
|
505
|
+
closed,
|
|
506
|
+
new Promise((_, reject) => {
|
|
507
|
+
timer = setTimeout(() => {
|
|
508
|
+
child.stdout?.destroy();
|
|
509
|
+
child.stderr?.destroy();
|
|
510
|
+
reject(
|
|
511
|
+
new Error("gate diagnostic output did not close after tree reap")
|
|
512
|
+
);
|
|
513
|
+
}, KILL_GRACE_MS * 2);
|
|
514
|
+
}),
|
|
515
|
+
]);
|
|
516
|
+
} finally {
|
|
517
|
+
clearTimeout(timer);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Forward command output immediately, retaining only a bounded diagnostic tail.
|
|
523
|
+
* The fixed caller descriptor is deliberately absent from the child's stdio.
|
|
524
|
+
* Waiting for close drains the pipes AFTER exit has already started tree reap.
|
|
525
|
+
* A broken diagnostic sink cannot replace the actual process verdict.
|
|
526
|
+
* @param {import("node:child_process").ChildProcess} child Owned command shell.
|
|
527
|
+
* @param {number|undefined} descriptor Optional caller-only descriptor.
|
|
528
|
+
* @returns {() => Promise<void>} Drain and publish diagnostics once.
|
|
529
|
+
*/
|
|
530
|
+
function captureChildOutput(child, descriptor) {
|
|
531
|
+
if (descriptor === undefined) return async () => {};
|
|
532
|
+
let tail = Buffer.alloc(0);
|
|
533
|
+
let publication;
|
|
534
|
+
const closed = new Promise(resolve => child.once("close", resolve));
|
|
535
|
+
for (const stream of [child.stdout, child.stderr]) {
|
|
536
|
+
stream.on("data", chunk => {
|
|
537
|
+
tail =
|
|
538
|
+
chunk.length >= CAPTURE_TAIL_BYTES
|
|
539
|
+
? Buffer.from(chunk.subarray(-CAPTURE_TAIL_BYTES))
|
|
540
|
+
: Buffer.concat([
|
|
541
|
+
tail.subarray(-Math.max(0, CAPTURE_TAIL_BYTES - chunk.length)),
|
|
542
|
+
chunk,
|
|
543
|
+
]);
|
|
544
|
+
});
|
|
545
|
+
stream.pipe(process.stdout, { end: false });
|
|
546
|
+
}
|
|
547
|
+
const publish = async () => {
|
|
548
|
+
await waitForOutputClose(child, closed);
|
|
549
|
+
try {
|
|
550
|
+
let offset = 0;
|
|
551
|
+
while (offset < tail.length) {
|
|
552
|
+
offset += writeSync(descriptor, tail, offset, tail.length - offset);
|
|
553
|
+
}
|
|
554
|
+
} catch {
|
|
555
|
+
process.stderr.write(
|
|
556
|
+
"gate diagnostic capture unavailable; OS verdict retained\n"
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
return () => {
|
|
561
|
+
publication ??= publish();
|
|
562
|
+
return publication;
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
406
566
|
/**
|
|
407
567
|
* Run one command as a supervised process tree.
|
|
408
568
|
*
|
|
@@ -410,7 +570,7 @@ export async function reapTree(pid, controls = DEFAULT_REAP_CONTROLS) {
|
|
|
410
570
|
* terminating signal, and now the run it serves going away — and all three
|
|
411
571
|
* share one reap, because a tree reaped twice is a tree whose second kill
|
|
412
572
|
* lands on a recycled pid.
|
|
413
|
-
* @param {string} command Shell source
|
|
573
|
+
* @param {string} command Shell source, or literal executable when directArgs is present.
|
|
414
574
|
* @param {number} timeoutMs Deadline for the whole tree.
|
|
415
575
|
* @param {typeof reapTree} [reap] Injectable POSIX group reaper. Windows retains its native job owner.
|
|
416
576
|
* @param {object} [options] Interrupt-watch seams, injectable for tests.
|
|
@@ -419,16 +579,32 @@ export async function reapTree(pid, controls = DEFAULT_REAP_CONTROLS) {
|
|
|
419
579
|
* @param {number} [options.pollMs] How often to ask.
|
|
420
580
|
* @param {(line: string) => void} [options.report] Where the report goes.
|
|
421
581
|
* @param {number} [options.launchParentPid] Parent pid captured at start.
|
|
582
|
+
* @param {3} [options.captureFd] Fixed caller-only diagnostic descriptor.
|
|
583
|
+
* @param {readonly string[]} [options.directArgs] Literal arguments; even [] selects POSIX direct spawning.
|
|
422
584
|
* @returns {Promise<{code: number|null, signal: string|null}>} The verdict.
|
|
423
585
|
*/
|
|
424
586
|
export function supervise(command, timeoutMs, reap = reapTree, options = {}) {
|
|
587
|
+
// Snapshot before reading other option getters or arming handlers. The caller
|
|
588
|
+
// cannot change the selected argv while lifetime ownership is being set up.
|
|
589
|
+
const suppliedArgs = options.directArgs;
|
|
590
|
+
const directArgs =
|
|
591
|
+
suppliedArgs === undefined
|
|
592
|
+
? undefined
|
|
593
|
+
: directInvocation(command, suppliedArgs, timeoutMs);
|
|
425
594
|
const {
|
|
426
595
|
watchPids = [],
|
|
427
596
|
detect = interruptionReason,
|
|
428
597
|
pollMs = ORPHAN_POLL_MS,
|
|
429
598
|
report = line => process.stderr.write(`${line}\n`),
|
|
430
599
|
launchParentPid = process.ppid,
|
|
600
|
+
captureFd,
|
|
431
601
|
} = options;
|
|
602
|
+
if (
|
|
603
|
+
captureFd !== undefined &&
|
|
604
|
+
(captureFd !== 3 || process.platform === "win32")
|
|
605
|
+
) {
|
|
606
|
+
throw new Error("diagnostic capture requires POSIX caller descriptor 3");
|
|
607
|
+
}
|
|
432
608
|
return new Promise((resolve, reject) => {
|
|
433
609
|
let windowsJob;
|
|
434
610
|
let pendingSignal;
|
|
@@ -457,11 +633,16 @@ export function supervise(command, timeoutMs, reap = reapTree, options = {}) {
|
|
|
457
633
|
windowsJob = startWindowsProcessJob(command);
|
|
458
634
|
return windowsJob.child;
|
|
459
635
|
}
|
|
460
|
-
return spawn(
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
636
|
+
return spawn(
|
|
637
|
+
directArgs === undefined ? "/bin/sh" : command,
|
|
638
|
+
directArgs === undefined ? ["-c", command] : directArgs,
|
|
639
|
+
{
|
|
640
|
+
detached: true,
|
|
641
|
+
env: process.env,
|
|
642
|
+
stdio:
|
|
643
|
+
captureFd === undefined ? "inherit" : ["inherit", "pipe", "pipe"],
|
|
644
|
+
}
|
|
645
|
+
);
|
|
465
646
|
} catch (error) {
|
|
466
647
|
clearSignalHandlers();
|
|
467
648
|
reject(error);
|
|
@@ -486,6 +667,7 @@ export function supervise(command, timeoutMs, reap = reapTree, options = {}) {
|
|
|
486
667
|
return;
|
|
487
668
|
}
|
|
488
669
|
|
|
670
|
+
const publishCapture = captureChildOutput(child, captureFd);
|
|
489
671
|
let timedOut = false;
|
|
490
672
|
let settled = false;
|
|
491
673
|
let settling = false;
|
|
@@ -500,7 +682,7 @@ export function supervise(command, timeoutMs, reap = reapTree, options = {}) {
|
|
|
500
682
|
clearSignalHandlers();
|
|
501
683
|
};
|
|
502
684
|
// Every exit path shares one reap. A signal that arrives while the direct
|
|
503
|
-
// shell's
|
|
685
|
+
// shell's exit handler is reaping must join that operation instead of
|
|
504
686
|
// starting a second kill sequence or taking the default signal action.
|
|
505
687
|
const reapOnce = () => {
|
|
506
688
|
reapPromise ??= windowsJob ? windowsJob.reap() : reap(pid);
|
|
@@ -514,6 +696,7 @@ export function supervise(command, timeoutMs, reap = reapTree, options = {}) {
|
|
|
514
696
|
// exits. Reap that residue fully before returning a verdict too.
|
|
515
697
|
try {
|
|
516
698
|
await reapOnce();
|
|
699
|
+
await publishCapture();
|
|
517
700
|
settled = true;
|
|
518
701
|
cleanup();
|
|
519
702
|
resolve({ code, signal });
|
|
@@ -530,6 +713,8 @@ export function supervise(command, timeoutMs, reap = reapTree, options = {}) {
|
|
|
530
713
|
// the handle and rejecting makes the supervisor terminal instead of
|
|
531
714
|
// leaving its promise pending behind a live event-loop reference.
|
|
532
715
|
child.unref();
|
|
716
|
+
child.stdout?.destroy();
|
|
717
|
+
child.stderr?.destroy();
|
|
533
718
|
reject(
|
|
534
719
|
new Error(
|
|
535
720
|
`gate process-tree supervisor failed: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -547,12 +732,15 @@ export function supervise(command, timeoutMs, reap = reapTree, options = {}) {
|
|
|
547
732
|
settling = true;
|
|
548
733
|
clearDeadline();
|
|
549
734
|
clearInterval(interruptWatch);
|
|
550
|
-
reapOnce()
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
735
|
+
reapOnce()
|
|
736
|
+
.then(async () => {
|
|
737
|
+
await publishCapture();
|
|
738
|
+
cleanup();
|
|
739
|
+
report(interruptionReport(reason, pid));
|
|
740
|
+
// Restore the signal-shaped exit after the detached tree is gone.
|
|
741
|
+
process.kill(process.pid, signal);
|
|
742
|
+
})
|
|
743
|
+
.catch(failReap);
|
|
556
744
|
};
|
|
557
745
|
const relaySignal = signal => {
|
|
558
746
|
stopWith(`it received ${signal}`, signal);
|
|
@@ -579,21 +767,24 @@ export function supervise(command, timeoutMs, reap = reapTree, options = {}) {
|
|
|
579
767
|
terminating = true;
|
|
580
768
|
settling = true;
|
|
581
769
|
clearDeadline();
|
|
582
|
-
reapOnce()
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
//
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
770
|
+
reapOnce()
|
|
771
|
+
.then(async () => {
|
|
772
|
+
await publishCapture();
|
|
773
|
+
cleanup();
|
|
774
|
+
const verdict = timeoutVerdictForPlatform(process.platform);
|
|
775
|
+
// A signal-shaped result is the existing gate runner vocabulary for
|
|
776
|
+
// "no verdict". It also prevents an ordinary exit code such as 124
|
|
777
|
+
// from being confused with a user command that returned that code.
|
|
778
|
+
if (verdict.signal === null) {
|
|
779
|
+
// Windows has no signal-shaped process result. 255 is a dedicated
|
|
780
|
+
// supervisor timeout code with a documented (but unavoidable)
|
|
781
|
+
// collision risk, well outside ordinary command exit conventions.
|
|
782
|
+
process.exit(verdict.code);
|
|
783
|
+
} else {
|
|
784
|
+
process.kill(process.pid, verdict.signal);
|
|
785
|
+
}
|
|
786
|
+
})
|
|
787
|
+
.catch(failReap);
|
|
597
788
|
}, timeoutMs);
|
|
598
789
|
|
|
599
790
|
dispatchSignal = relaySignal;
|
|
@@ -607,9 +798,9 @@ export function supervise(command, timeoutMs, reap = reapTree, options = {}) {
|
|
|
607
798
|
reject(error);
|
|
608
799
|
}, failReap);
|
|
609
800
|
});
|
|
610
|
-
child.once("
|
|
801
|
+
child.once("exit", (code, signal) => {
|
|
611
802
|
// On timeout, wait for the forced group reap above before this supervisor
|
|
612
|
-
// ends. Exiting
|
|
803
|
+
// ends. Exiting before the complete group reap is the race that used to leave
|
|
613
804
|
// its descendants alive.
|
|
614
805
|
if (!timedOut) void finish(code, signal);
|
|
615
806
|
});
|
|
@@ -619,10 +810,13 @@ export function supervise(command, timeoutMs, reap = reapTree, options = {}) {
|
|
|
619
810
|
}
|
|
620
811
|
|
|
621
812
|
async function main() {
|
|
622
|
-
const { command, timeoutMs, watchPids } =
|
|
623
|
-
process.argv.slice(2)
|
|
624
|
-
|
|
625
|
-
|
|
813
|
+
const { command, timeoutMs, watchPids, captureFd, directArgs } =
|
|
814
|
+
parseArguments(process.argv.slice(2));
|
|
815
|
+
const result = await supervise(command, timeoutMs, reapTree, {
|
|
816
|
+
watchPids,
|
|
817
|
+
captureFd,
|
|
818
|
+
directArgs,
|
|
819
|
+
});
|
|
626
820
|
if (result.signal) {
|
|
627
821
|
process.kill(process.pid, result.signal);
|
|
628
822
|
} else {
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// This file is managed by Lisa and IS replaced on each `lisa` run.
|
|
2
|
+
// Do not edit directly — durable changes belong upstream in Lisa.
|
|
3
|
+
|
|
4
|
+
/** Authenticated non-AI attribution supplements ordinary tracking and lint gates. */
|
|
5
|
+
import {
|
|
6
|
+
predictedCommit,
|
|
7
|
+
verifyRecoveryOrigin,
|
|
8
|
+
} from "./lib/github-attestation-recovery.mjs";
|
|
9
|
+
import { invokedAsScript } from "./lib/invoked-as-script.mjs";
|
|
10
|
+
import {
|
|
11
|
+
assertPinnedVerifier,
|
|
12
|
+
requireProof,
|
|
13
|
+
sha256,
|
|
14
|
+
verifyCurrentProvider,
|
|
15
|
+
verifyDescriptorAttestation,
|
|
16
|
+
} from "./lib/github-attestation-verifier.mjs";
|
|
17
|
+
import {
|
|
18
|
+
canonicalJson,
|
|
19
|
+
trustedConfiguration,
|
|
20
|
+
npmProposal,
|
|
21
|
+
} from "./lib/automation-provenance-contract.mjs";
|
|
22
|
+
import {
|
|
23
|
+
git,
|
|
24
|
+
privateBytes,
|
|
25
|
+
localDescriptor,
|
|
26
|
+
proofSnapshot,
|
|
27
|
+
canonicalContext,
|
|
28
|
+
} from "./lib/automation-provenance-local.mjs";
|
|
29
|
+
export { canonicalJson } from "./lib/automation-provenance-contract.mjs";
|
|
30
|
+
|
|
31
|
+
/** A present but malformed reference is a failure, even with ordinary AI trailers. */
|
|
32
|
+
export function automationReference(message) {
|
|
33
|
+
const lines = message
|
|
34
|
+
.split(/\r?\n/)
|
|
35
|
+
.filter(line => /^\s*Automation-Provenance\s*:/i.test(line));
|
|
36
|
+
if (lines.length === 0) return undefined;
|
|
37
|
+
requireProof(lines.length === 1, "ambiguous automation reference");
|
|
38
|
+
const match =
|
|
39
|
+
/^Automation-Provenance: actions\/([1-9]\d*)\/attempts\/([1-9]\d*)$/.exec(
|
|
40
|
+
lines[0]
|
|
41
|
+
);
|
|
42
|
+
requireProof(Boolean(match), "malformed automation reference");
|
|
43
|
+
return { runId: match[1], runAttempt: match[2] };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Fresh recovery is a fixed separate role; original proof never acquires current authority. */
|
|
47
|
+
function recoveryPermission(
|
|
48
|
+
policy,
|
|
49
|
+
descriptor,
|
|
50
|
+
context,
|
|
51
|
+
config,
|
|
52
|
+
bytes,
|
|
53
|
+
paths,
|
|
54
|
+
recoveryBytes,
|
|
55
|
+
recoveryBundle,
|
|
56
|
+
messageBytes
|
|
57
|
+
) {
|
|
58
|
+
const recovery = JSON.parse(recoveryBytes.toString("utf8"));
|
|
59
|
+
requireProof(
|
|
60
|
+
recoveryBytes.toString("utf8") === `${canonicalJson(recovery)}\n`,
|
|
61
|
+
"noncanonical/duplicate recovery fields"
|
|
62
|
+
);
|
|
63
|
+
const npmPolicySha256 = sha256(canonicalJson(config.npmUpdater));
|
|
64
|
+
verifyRecoveryOrigin(policy, descriptor, recovery, sha256(bytes), paths, {
|
|
65
|
+
npmPolicySha256,
|
|
66
|
+
proposalKey: sha256(
|
|
67
|
+
canonicalJson({
|
|
68
|
+
repository: policy.repository,
|
|
69
|
+
target: "main",
|
|
70
|
+
ecosystem: "npm",
|
|
71
|
+
directory: ".",
|
|
72
|
+
parent: descriptor.parent,
|
|
73
|
+
policySha256: npmPolicySha256,
|
|
74
|
+
updates: descriptor.updates,
|
|
75
|
+
})
|
|
76
|
+
),
|
|
77
|
+
leafBodySha256: sha256(context.issue.body),
|
|
78
|
+
commit: predictedCommit(descriptor, messageBytes),
|
|
79
|
+
maintainer: config.npmUpdater.maintainer,
|
|
80
|
+
recoverySha256: sha256(recoveryBytes),
|
|
81
|
+
});
|
|
82
|
+
requireProof(
|
|
83
|
+
privateBytes(paths.recovery, 65_536).equals(recoveryBytes) &&
|
|
84
|
+
privateBytes(paths.recoveryBundle, 1_048_576).equals(recoveryBundle),
|
|
85
|
+
"recovery proof changed during verification"
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Verify fixed private proof against the final message, tree, binding and provider. */
|
|
90
|
+
export function verifyAutomationProvenance(messageFile) {
|
|
91
|
+
const messageBytes = privateBytes(messageFile, 65_536, false);
|
|
92
|
+
const message = messageBytes.toString("utf8");
|
|
93
|
+
const reference = automationReference(message);
|
|
94
|
+
if (!reference) return false;
|
|
95
|
+
const { config, policy } = trustedConfiguration(git);
|
|
96
|
+
const { paths, optional, bytes, bundleBytes, descriptor } = proofSnapshot();
|
|
97
|
+
localDescriptor(descriptor, messageBytes, reference, policy);
|
|
98
|
+
npmProposal(descriptor, git);
|
|
99
|
+
requireProof(
|
|
100
|
+
descriptor.proposalKey ===
|
|
101
|
+
sha256(
|
|
102
|
+
canonicalJson({
|
|
103
|
+
repository: policy.repository,
|
|
104
|
+
parent: descriptor.parent,
|
|
105
|
+
updates: descriptor.updates,
|
|
106
|
+
})
|
|
107
|
+
),
|
|
108
|
+
"deterministic proposal key differs"
|
|
109
|
+
);
|
|
110
|
+
assertPinnedVerifier(policy);
|
|
111
|
+
const context = canonicalContext(message, config, policy, descriptor);
|
|
112
|
+
const recoveryBytes = optional[0]
|
|
113
|
+
? privateBytes(paths.recovery, 65_536)
|
|
114
|
+
: null;
|
|
115
|
+
const recoveryBundle = optional[0]
|
|
116
|
+
? privateBytes(paths.recoveryBundle, 1_048_576)
|
|
117
|
+
: null;
|
|
118
|
+
if (recoveryBytes) {
|
|
119
|
+
recoveryPermission(
|
|
120
|
+
policy,
|
|
121
|
+
descriptor,
|
|
122
|
+
context,
|
|
123
|
+
config,
|
|
124
|
+
bytes,
|
|
125
|
+
paths,
|
|
126
|
+
recoveryBytes,
|
|
127
|
+
recoveryBundle,
|
|
128
|
+
messageBytes
|
|
129
|
+
);
|
|
130
|
+
} else {
|
|
131
|
+
verifyDescriptorAttestation(policy, descriptor, paths, sha256(bytes));
|
|
132
|
+
verifyCurrentProvider(policy, descriptor);
|
|
133
|
+
}
|
|
134
|
+
requireProof(
|
|
135
|
+
privateBytes(paths.bundle, 1_048_576).equals(bundleBytes),
|
|
136
|
+
"origin bundle changed during verification"
|
|
137
|
+
);
|
|
138
|
+
requireProof(
|
|
139
|
+
privateBytes(paths.descriptor, 65_536).equals(bytes),
|
|
140
|
+
"descriptor changed during verification"
|
|
141
|
+
);
|
|
142
|
+
localDescriptor(
|
|
143
|
+
descriptor,
|
|
144
|
+
privateBytes(messageFile, 65_536, false),
|
|
145
|
+
reference,
|
|
146
|
+
policy
|
|
147
|
+
);
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (invokedAsScript(import.meta.url)) {
|
|
152
|
+
try {
|
|
153
|
+
const verified = verifyAutomationProvenance(process.argv[2]);
|
|
154
|
+
process.exitCode = verified ? 0 : 10;
|
|
155
|
+
} catch {
|
|
156
|
+
console.error(
|
|
157
|
+
"Invalid automation provenance: required proof, policy or provider evidence failed"
|
|
158
|
+
);
|
|
159
|
+
process.exitCode = 1;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -25,6 +25,7 @@ export const main = async (
|
|
|
25
25
|
const {
|
|
26
26
|
HistorySecretError,
|
|
27
27
|
objectWidth,
|
|
28
|
+
gitRead,
|
|
28
29
|
parsePush,
|
|
29
30
|
eventPairs,
|
|
30
31
|
introducedCommits,
|
|
@@ -91,7 +92,13 @@ export const main = async (
|
|
|
91
92
|
console.log("No introduced history. Scanner was not run.");
|
|
92
93
|
return 0;
|
|
93
94
|
}
|
|
94
|
-
const
|
|
95
|
+
const heads = pairs
|
|
96
|
+
.filter(pair => !/^0+$/u.test(pair.after))
|
|
97
|
+
.map(pair =>
|
|
98
|
+
gitRead(["rev-parse", "--verify", `${pair.after}^{commit}`], cwd)
|
|
99
|
+
)
|
|
100
|
+
.filter(head => commits.includes(head));
|
|
101
|
+
const result = scanCommits(commits, cwd, scanner, heads);
|
|
95
102
|
console.log(JSON.stringify(result));
|
|
96
103
|
if (result.findings.length > 0) {
|
|
97
104
|
console.error(
|