@codyswann/lisa 4.70.4 → 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/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-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 +598 -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 +157 -11
- 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
|
@@ -0,0 +1,278 @@
|
|
|
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
|
+
/** Parent-authenticated canonical helpers receive immutable phase scopes; their token stays in broker memory. */
|
|
5
|
+
import { mkdirSync, realpathSync, existsSync } from "node:fs";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { canonicalJson } from "../lisa-automation-provenance.mjs";
|
|
8
|
+
import { required, OBJECT } from "./npm-update-contract.mjs";
|
|
9
|
+
import { leafRoles } from "./npm-update-leaf-contract.mjs";
|
|
10
|
+
import { qualifiedControllerGraph } from "./npm-update-helper.mjs";
|
|
11
|
+
import { withPrivateRoot, writeJson } from "./npm-update-process.mjs";
|
|
12
|
+
import { assertPinnedVerifier } from "./github-attestation-verifier.mjs";
|
|
13
|
+
import { binaryDigest } from "./npm-update-isolation.mjs";
|
|
14
|
+
import { createGhRequests } from "./npm-update-gh-requests.mjs";
|
|
15
|
+
import { startControllerBroker } from "./npm-update-controller-broker.mjs";
|
|
16
|
+
import { invokeControllerRecipe } from "./npm-update-broker-client.mjs";
|
|
17
|
+
import { boundedSpawnSync } from "./bounded-spawn.mjs";
|
|
18
|
+
|
|
19
|
+
/** One original phase expiry caps every helper; a later invocation cannot renew its budget. */
|
|
20
|
+
export function controllerDeadline(context, now = Date.now()) {
|
|
21
|
+
required(
|
|
22
|
+
Number.isSafeInteger(context.deadline) &&
|
|
23
|
+
context.deadline > now &&
|
|
24
|
+
context.deadline <= now + 1_800_000,
|
|
25
|
+
"original controller phase deadline is absent or expired"
|
|
26
|
+
);
|
|
27
|
+
return Math.min(context.deadline, now + 120_000);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Existing canonical validators still authorize the allocation; this projection narrows helper authority. */
|
|
31
|
+
export function controllerSubject(context, phase, pr, proofs = []) {
|
|
32
|
+
const { config, policy, proposal, allocation } = context;
|
|
33
|
+
const descriptor = context.descriptor ?? context.preview?.descriptor;
|
|
34
|
+
leafRoles(config, policy.repository);
|
|
35
|
+
required(
|
|
36
|
+
proposal.repository === policy.repository &&
|
|
37
|
+
Number.isSafeInteger(allocation.number) &&
|
|
38
|
+
allocation.number > 0 &&
|
|
39
|
+
allocation.workItem === `${policy.repository}#${allocation.number}` &&
|
|
40
|
+
descriptor?.parent === proposal.parent &&
|
|
41
|
+
descriptor.workItem === allocation.workItem &&
|
|
42
|
+
descriptor.claimCommentId === allocation.claimCommentId,
|
|
43
|
+
"controller allocation or descriptor differs"
|
|
44
|
+
);
|
|
45
|
+
required(
|
|
46
|
+
pr === null ||
|
|
47
|
+
(Number.isSafeInteger(pr?.number) &&
|
|
48
|
+
pr.number > 0 &&
|
|
49
|
+
pr.html_url ===
|
|
50
|
+
`https://github.com/${policy.repository}/pull/${pr.number}`),
|
|
51
|
+
"controller actual PR differs"
|
|
52
|
+
);
|
|
53
|
+
const recovery = context.recoveryDescriptor;
|
|
54
|
+
const subject = {
|
|
55
|
+
phase,
|
|
56
|
+
repository: policy.repository,
|
|
57
|
+
tracker: policy.repository,
|
|
58
|
+
issue: String(allocation.number),
|
|
59
|
+
branch: `lisa/npm-${proposal.key}`,
|
|
60
|
+
parent: proposal.parent,
|
|
61
|
+
origin: { runId: descriptor.runId, runAttempt: descriptor.runAttempt },
|
|
62
|
+
claim: allocation.claimCommentId,
|
|
63
|
+
recovery: recovery
|
|
64
|
+
? {
|
|
65
|
+
runId: recovery.runId,
|
|
66
|
+
runAttempt: recovery.runAttempt,
|
|
67
|
+
branch: `lisa/npm-${proposal.key}`,
|
|
68
|
+
}
|
|
69
|
+
: null,
|
|
70
|
+
maintainer: policy.maintainer,
|
|
71
|
+
pr: pr === null ? null : { number: String(pr.number), url: pr.html_url },
|
|
72
|
+
proofs,
|
|
73
|
+
};
|
|
74
|
+
createGhRequests(subject);
|
|
75
|
+
return subject;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Only exact current stage/writer tuples are constructed; arbitrary helper operations cannot enter this factory. */
|
|
79
|
+
export function canonicalHelperArguments(context, phase, operation, pr) {
|
|
80
|
+
controllerSubject(context, phase, pr);
|
|
81
|
+
if (phase === "stage-read") {
|
|
82
|
+
required(pr === null, "stage phase cannot carry publication authority");
|
|
83
|
+
if (operation === "link") return ["link", context.allocation.workItem];
|
|
84
|
+
if (operation === "attach-branch") return ["attach-branch"];
|
|
85
|
+
}
|
|
86
|
+
if (phase === "publication-backlink" && operation === "backlink" && pr)
|
|
87
|
+
return [
|
|
88
|
+
"backlink",
|
|
89
|
+
"--ref",
|
|
90
|
+
context.allocation.workItem,
|
|
91
|
+
"--pr-url",
|
|
92
|
+
pr.html_url,
|
|
93
|
+
];
|
|
94
|
+
if (
|
|
95
|
+
phase === "publication-validate-pr" &&
|
|
96
|
+
operation === "validate-pr" &&
|
|
97
|
+
pr
|
|
98
|
+
) {
|
|
99
|
+
required(
|
|
100
|
+
OBJECT.test(context.commit?.sha ?? ""),
|
|
101
|
+
"controller publication head is missing"
|
|
102
|
+
);
|
|
103
|
+
return [
|
|
104
|
+
"validate-pr",
|
|
105
|
+
"--base",
|
|
106
|
+
context.proposal.parent,
|
|
107
|
+
"--head",
|
|
108
|
+
context.commit.sha,
|
|
109
|
+
"--pr-number",
|
|
110
|
+
String(pr.number),
|
|
111
|
+
"--repo",
|
|
112
|
+
context.policy.repository,
|
|
113
|
+
"--pr-url",
|
|
114
|
+
pr.html_url,
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
throw Error("unsupported canonical helper phase/operation");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Trusted released factory resolves actual tools outside candidate influence and rechecks them before dispatch. */
|
|
121
|
+
export function controllerTools(automation) {
|
|
122
|
+
assertPinnedVerifier(automation);
|
|
123
|
+
required(
|
|
124
|
+
process.version === "v22.23.3",
|
|
125
|
+
"unsupported native controller Node bootstrap"
|
|
126
|
+
);
|
|
127
|
+
const path = realpathSync(process.execPath);
|
|
128
|
+
const git = realpathSync(
|
|
129
|
+
[
|
|
130
|
+
"/Library/Developer/CommandLineTools/usr/bin/git",
|
|
131
|
+
"/Applications/Xcode.app/Contents/Developer/usr/bin/git",
|
|
132
|
+
"/usr/bin/git",
|
|
133
|
+
].find(existsSync)
|
|
134
|
+
);
|
|
135
|
+
const result = boundedSpawnSync(git, ["--version"], {
|
|
136
|
+
encoding: "utf8",
|
|
137
|
+
timeout: 5000,
|
|
138
|
+
maxBuffer: 4096,
|
|
139
|
+
env: { PATH: "/usr/bin:/bin", HOME: "/nonexistent" },
|
|
140
|
+
});
|
|
141
|
+
required(
|
|
142
|
+
!result.error &&
|
|
143
|
+
!result.signal &&
|
|
144
|
+
result.status === 0 &&
|
|
145
|
+
/^git version [\w. -]+\n$/.test(result.stdout),
|
|
146
|
+
"native controller Git identity is unavailable"
|
|
147
|
+
);
|
|
148
|
+
return {
|
|
149
|
+
node: { path, version: "22.23.3", sha256: binaryDigest(path) },
|
|
150
|
+
git: {
|
|
151
|
+
path: git,
|
|
152
|
+
version: result.stdout.trim(),
|
|
153
|
+
sha256: binaryDigest(git),
|
|
154
|
+
},
|
|
155
|
+
gh: { path: automation.ghExecutable, sha256: automation.ghSha256 },
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Construct private JSON separately from the memory-only credentialed recipe. */
|
|
160
|
+
function canonicalRecipe(context, authority) {
|
|
161
|
+
const {
|
|
162
|
+
root,
|
|
163
|
+
cwd,
|
|
164
|
+
graph,
|
|
165
|
+
native,
|
|
166
|
+
entry,
|
|
167
|
+
argv,
|
|
168
|
+
phase,
|
|
169
|
+
operation,
|
|
170
|
+
pr,
|
|
171
|
+
deadline,
|
|
172
|
+
} = authority;
|
|
173
|
+
mkdirSync(join(root, "gh-config"), { mode: 0o700 });
|
|
174
|
+
const launcher = {
|
|
175
|
+
version: 3,
|
|
176
|
+
root,
|
|
177
|
+
cwd,
|
|
178
|
+
graph,
|
|
179
|
+
routes: [],
|
|
180
|
+
controller: { node: native.node, git: native.git },
|
|
181
|
+
runtime: { tools: { node: native.node } },
|
|
182
|
+
boundary: null,
|
|
183
|
+
provider: {
|
|
184
|
+
version: 1,
|
|
185
|
+
deadline,
|
|
186
|
+
cwd,
|
|
187
|
+
home: root,
|
|
188
|
+
nativeGh: native.gh,
|
|
189
|
+
invocation: { entry, args: argv, cwd },
|
|
190
|
+
subject: controllerSubject(context, phase, pr),
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
writeJson(join(root, "launcher.json"), launcher);
|
|
194
|
+
return {
|
|
195
|
+
id: operation,
|
|
196
|
+
command: native.node.path,
|
|
197
|
+
args: argv,
|
|
198
|
+
cwd,
|
|
199
|
+
input: Buffer.alloc(0),
|
|
200
|
+
allowed: [0],
|
|
201
|
+
timeout: 120_000,
|
|
202
|
+
maximum: 3_145_728,
|
|
203
|
+
context: launcher,
|
|
204
|
+
env: {
|
|
205
|
+
PATH: `${dirname(native.node.path)}:${dirname(native.gh.path)}:/usr/bin:/bin`,
|
|
206
|
+
HOME: root,
|
|
207
|
+
LANG: "C.UTF-8",
|
|
208
|
+
GH_TOKEN: context.token,
|
|
209
|
+
GH_HOST: "github.com",
|
|
210
|
+
GITHUB_REPOSITORY: context.policy.repository,
|
|
211
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Every invocation has fresh private scope/credential-free JSON; no mutable privilege growth across phases. */
|
|
217
|
+
export async function executeCanonicalHelper(
|
|
218
|
+
context,
|
|
219
|
+
phase,
|
|
220
|
+
operation,
|
|
221
|
+
pr = null,
|
|
222
|
+
suppliedArgs
|
|
223
|
+
) {
|
|
224
|
+
controllerDeadline(context);
|
|
225
|
+
const args = canonicalHelperArguments(context, phase, operation, pr);
|
|
226
|
+
required(
|
|
227
|
+
suppliedArgs === undefined ||
|
|
228
|
+
canonicalJson(args) === canonicalJson(suppliedArgs),
|
|
229
|
+
"canonical helper invocation differs from controller recipe"
|
|
230
|
+
);
|
|
231
|
+
const cwd = realpathSync(context.cwd);
|
|
232
|
+
const graph = await qualifiedControllerGraph(cwd, context.config, [
|
|
233
|
+
"lisa-work-item.mjs",
|
|
234
|
+
"lib/npm-update-execution-adapter.mjs",
|
|
235
|
+
]);
|
|
236
|
+
const entry = join(cwd, "scripts/lisa-work-item.mjs");
|
|
237
|
+
required(
|
|
238
|
+
Object.hasOwn(graph, entry),
|
|
239
|
+
"authenticated canonical helper entry is absent"
|
|
240
|
+
);
|
|
241
|
+
const native = controllerTools(context.config.automationProvenance);
|
|
242
|
+
const argv = [entry, ...args];
|
|
243
|
+
return withPrivateRoot(async root => {
|
|
244
|
+
const deadline = controllerDeadline(context);
|
|
245
|
+
const recipe = canonicalRecipe(context, {
|
|
246
|
+
root,
|
|
247
|
+
cwd,
|
|
248
|
+
graph,
|
|
249
|
+
native,
|
|
250
|
+
entry,
|
|
251
|
+
argv,
|
|
252
|
+
phase,
|
|
253
|
+
operation,
|
|
254
|
+
pr,
|
|
255
|
+
deadline,
|
|
256
|
+
});
|
|
257
|
+
const broker = await startControllerBroker({
|
|
258
|
+
root,
|
|
259
|
+
node: native.node,
|
|
260
|
+
graph,
|
|
261
|
+
deadline,
|
|
262
|
+
recipes: [recipe],
|
|
263
|
+
});
|
|
264
|
+
try {
|
|
265
|
+
const result = await invokeControllerRecipe(
|
|
266
|
+
{ root, path: broker.path, deadline },
|
|
267
|
+
operation
|
|
268
|
+
);
|
|
269
|
+
required(
|
|
270
|
+
result.code === 0,
|
|
271
|
+
`canonical ${operation} helper refused with native exit ${result.code}`
|
|
272
|
+
);
|
|
273
|
+
return result;
|
|
274
|
+
} finally {
|
|
275
|
+
await broker.close();
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
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
|
+
/** Immutable recipes retain native status/capture while scoped helpers receive their authenticated bootstrap. */
|
|
5
|
+
import { realpathSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { canonicalJson } from "../lisa-automation-provenance.mjs";
|
|
8
|
+
import { required, keys } from "./npm-update-contract.mjs";
|
|
9
|
+
import { runProcess } from "./npm-update-process.mjs";
|
|
10
|
+
import { binaryDigest } from "./npm-update-isolation.mjs";
|
|
11
|
+
import {
|
|
12
|
+
literalArguments,
|
|
13
|
+
launcherContext,
|
|
14
|
+
dispatchTool,
|
|
15
|
+
} from "./npm-update-tool-launcher.mjs";
|
|
16
|
+
|
|
17
|
+
/** The context is controller-created private data; every helper/graph/native tuple is rechecked at dispatch. */
|
|
18
|
+
export function checkedRecipe(recipe, node, graph) {
|
|
19
|
+
keys(recipe, [
|
|
20
|
+
"id",
|
|
21
|
+
"command",
|
|
22
|
+
"args",
|
|
23
|
+
"cwd",
|
|
24
|
+
"input",
|
|
25
|
+
"env",
|
|
26
|
+
"allowed",
|
|
27
|
+
"timeout",
|
|
28
|
+
"maximum",
|
|
29
|
+
...(recipe.context !== undefined ? ["context"] : []),
|
|
30
|
+
]);
|
|
31
|
+
literalArguments(recipe.args);
|
|
32
|
+
required(
|
|
33
|
+
/^[a-zA-Z0-9-]{1,128}$/.test(recipe.id) &&
|
|
34
|
+
recipe.command === node.path &&
|
|
35
|
+
Object.hasOwn(graph, recipe.args[0]) &&
|
|
36
|
+
recipe.cwd === realpathSync(recipe.cwd),
|
|
37
|
+
"unqualified or duplicate broker recipe"
|
|
38
|
+
);
|
|
39
|
+
required(
|
|
40
|
+
Buffer.isBuffer(recipe.input) &&
|
|
41
|
+
recipe.input.length <= 4_194_304 &&
|
|
42
|
+
Number.isSafeInteger(recipe.timeout) &&
|
|
43
|
+
recipe.timeout > 0 &&
|
|
44
|
+
recipe.timeout <= 1_800_000 &&
|
|
45
|
+
Number.isSafeInteger(recipe.maximum) &&
|
|
46
|
+
recipe.maximum > 0 &&
|
|
47
|
+
recipe.maximum <= 8_388_608,
|
|
48
|
+
"broker recipe input or capture bound differs"
|
|
49
|
+
);
|
|
50
|
+
required(
|
|
51
|
+
recipe.env &&
|
|
52
|
+
!recipe.env.NODE_OPTIONS &&
|
|
53
|
+
!recipe.env.NODE_PATH &&
|
|
54
|
+
Array.isArray(recipe.allowed) &&
|
|
55
|
+
recipe.allowed.length > 0 &&
|
|
56
|
+
recipe.allowed.length <= 256 &&
|
|
57
|
+
recipe.allowed.every(
|
|
58
|
+
code => Number.isInteger(code) && code >= 0 && code <= 255
|
|
59
|
+
),
|
|
60
|
+
"broker recipe environment or status policy differs"
|
|
61
|
+
);
|
|
62
|
+
if (recipe.context !== undefined) {
|
|
63
|
+
const actual = launcherContext(join(recipe.context.root, "launcher.json"));
|
|
64
|
+
required(
|
|
65
|
+
actual.version === 3 &&
|
|
66
|
+
actual.provider !== null &&
|
|
67
|
+
canonicalJson(actual) === canonicalJson(recipe.context) &&
|
|
68
|
+
canonicalJson(actual.graph) === canonicalJson(graph) &&
|
|
69
|
+
actual.controller.node.path === node.path &&
|
|
70
|
+
actual.controller.node.sha256 === node.sha256 &&
|
|
71
|
+
actual.provider.invocation.entry === recipe.args[0] &&
|
|
72
|
+
canonicalJson(actual.provider.invocation.args) ===
|
|
73
|
+
canonicalJson(recipe.args) &&
|
|
74
|
+
actual.provider.invocation.cwd === recipe.cwd,
|
|
75
|
+
"broker scoped helper context differs"
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
...recipe,
|
|
80
|
+
args: [...recipe.args],
|
|
81
|
+
input: Buffer.from(recipe.input),
|
|
82
|
+
env: { ...recipe.env },
|
|
83
|
+
allowed: [...recipe.allowed],
|
|
84
|
+
...(recipe.context !== undefined
|
|
85
|
+
? { context: JSON.parse(JSON.stringify(recipe.context)) }
|
|
86
|
+
: {}),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Completed nonzero children keep native status/output; cancellation never becomes success. */
|
|
91
|
+
export async function executeRecipe(recipe, deadline, signal, node) {
|
|
92
|
+
const timeout = Math.min(recipe.timeout, deadline - Date.now());
|
|
93
|
+
required(timeout > 0 && !signal.aborted, "broker phase expired");
|
|
94
|
+
required(
|
|
95
|
+
binaryDigest(node.path) === node.sha256,
|
|
96
|
+
"broker native controller changed before dispatch"
|
|
97
|
+
);
|
|
98
|
+
const contract = {
|
|
99
|
+
timeout,
|
|
100
|
+
allowed: recipe.allowed,
|
|
101
|
+
maximum: recipe.maximum,
|
|
102
|
+
};
|
|
103
|
+
try {
|
|
104
|
+
if (recipe.context !== undefined)
|
|
105
|
+
return await dispatchTool(
|
|
106
|
+
recipe.context,
|
|
107
|
+
"node",
|
|
108
|
+
recipe.args,
|
|
109
|
+
recipe.input,
|
|
110
|
+
recipe.env,
|
|
111
|
+
recipe.cwd,
|
|
112
|
+
{ ...contract, signal }
|
|
113
|
+
);
|
|
114
|
+
return await runProcess(recipe.command, recipe.args, {
|
|
115
|
+
cwd: recipe.cwd,
|
|
116
|
+
env: recipe.env,
|
|
117
|
+
input: recipe.input,
|
|
118
|
+
...contract,
|
|
119
|
+
signal,
|
|
120
|
+
});
|
|
121
|
+
} catch (error) {
|
|
122
|
+
required(
|
|
123
|
+
error.nativeCompleted === true &&
|
|
124
|
+
Number.isInteger(error.code) &&
|
|
125
|
+
error.code >= 0 &&
|
|
126
|
+
error.code <= 255 &&
|
|
127
|
+
Buffer.isBuffer(error.stdout) &&
|
|
128
|
+
Buffer.isBuffer(error.stderr) &&
|
|
129
|
+
error.stdout.length + error.stderr.length <= recipe.maximum &&
|
|
130
|
+
!signal.aborted,
|
|
131
|
+
"broker child did not complete normally"
|
|
132
|
+
);
|
|
133
|
+
return { code: error.code, stdout: error.stdout, stderr: error.stderr };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
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
|
+
/** Trusted canonical Node code forwards nested real tools before application code loads. */
|
|
5
|
+
import childProcess from "node:child_process";
|
|
6
|
+
import { syncBuiltinESMExports } from "node:module";
|
|
7
|
+
import { resolve, basename } from "node:path";
|
|
8
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
9
|
+
import { realpathSync } from "node:fs";
|
|
10
|
+
import {
|
|
11
|
+
launcherContext,
|
|
12
|
+
literalArguments,
|
|
13
|
+
} from "./npm-update-tool-launcher.mjs";
|
|
14
|
+
import {
|
|
15
|
+
currentGraph,
|
|
16
|
+
controllerRoute,
|
|
17
|
+
controllerGraph,
|
|
18
|
+
} from "./npm-update-orchestrator.mjs";
|
|
19
|
+
import { binaryDigest } from "./npm-update-isolation.mjs";
|
|
20
|
+
import { required } from "./npm-update-contract.mjs";
|
|
21
|
+
import { canonicalJson } from "../lisa-automation-provenance.mjs";
|
|
22
|
+
import { createGhDispatcher } from "./npm-update-gh-dispatch.mjs";
|
|
23
|
+
|
|
24
|
+
const ENTRY = fileURLToPath(import.meta.url);
|
|
25
|
+
const LAUNCHER = fileURLToPath(
|
|
26
|
+
new URL("./npm-update-tool-launcher.mjs", import.meta.url)
|
|
27
|
+
);
|
|
28
|
+
const ORIGINAL = Object.fromEntries(
|
|
29
|
+
["spawn", "spawnSync", "execFile", "execFileSync"].map(name => [
|
|
30
|
+
name,
|
|
31
|
+
childProcess[name],
|
|
32
|
+
])
|
|
33
|
+
);
|
|
34
|
+
const SPELLINGS = {
|
|
35
|
+
node: "node",
|
|
36
|
+
npm: "npm",
|
|
37
|
+
bun: "bun",
|
|
38
|
+
ruby: "ruby",
|
|
39
|
+
bundle: "bundle",
|
|
40
|
+
sh: "shell",
|
|
41
|
+
bash: "bash",
|
|
42
|
+
gh: "gh",
|
|
43
|
+
gitleaks: "gitleaks",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** Matching filenames never qualify an arbitrary absolute native executable. */
|
|
47
|
+
function commandTool(context, command) {
|
|
48
|
+
required(
|
|
49
|
+
typeof command === "string" &&
|
|
50
|
+
command.length <= 4096 &&
|
|
51
|
+
!/[\n\0]/.test(command),
|
|
52
|
+
"invalid nested native command"
|
|
53
|
+
);
|
|
54
|
+
const name = basename(command);
|
|
55
|
+
if (command === process.execPath || command === context.controller.node.path)
|
|
56
|
+
return "node";
|
|
57
|
+
if (command === "git" || command === context.controller.git?.path)
|
|
58
|
+
return "git";
|
|
59
|
+
const tool = SPELLINGS[name];
|
|
60
|
+
required(
|
|
61
|
+
tool &&
|
|
62
|
+
(!command.includes("/") ||
|
|
63
|
+
command === context.runtime.tools[tool]?.path ||
|
|
64
|
+
command === context.controller[tool]?.path ||
|
|
65
|
+
(["/bin/sh", "/usr/bin/sh"].includes(command) && tool === "shell") ||
|
|
66
|
+
(["/bin/bash", "/usr/bin/bash"].includes(command) && tool === "bash")),
|
|
67
|
+
"unqualified nested native command route"
|
|
68
|
+
);
|
|
69
|
+
return tool;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Private instrumentation may be inherited only by authenticated canonical Node children. */
|
|
73
|
+
function childEnvironment(options, contextFile, canonical) {
|
|
74
|
+
const env = { ...(options.env ?? process.env) };
|
|
75
|
+
required(
|
|
76
|
+
!env.NODE_OPTIONS && !env.NODE_PATH,
|
|
77
|
+
"unqualified nested Node environment loader"
|
|
78
|
+
);
|
|
79
|
+
delete env.LISA_NPM_DISPATCH_CONTEXT;
|
|
80
|
+
delete env.GH_TOKEN;
|
|
81
|
+
delete env.GITHUB_TOKEN;
|
|
82
|
+
if (canonical) env.LISA_NPM_DISPATCH_CONTEXT = contextFile;
|
|
83
|
+
return env;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The proxy itself is genuine qualified Node; its exit is the real dispatched command exit. */
|
|
87
|
+
function nestedInvocation(context, contextFile, command, args, options) {
|
|
88
|
+
literalArguments(args);
|
|
89
|
+
required(
|
|
90
|
+
options.shell !== true && typeof options.shell !== "string",
|
|
91
|
+
"unqualified nested shell option; use the literal shell tool"
|
|
92
|
+
);
|
|
93
|
+
const cwd = resolve(options.cwd ?? process.cwd());
|
|
94
|
+
const tool = commandTool(context, command);
|
|
95
|
+
const node = context.controller.node;
|
|
96
|
+
required(
|
|
97
|
+
binaryDigest(node.path) === node.sha256 && node.version === "22.23.3",
|
|
98
|
+
"controller Node identity changed"
|
|
99
|
+
);
|
|
100
|
+
if (tool === "git") {
|
|
101
|
+
const git = context.controller.git;
|
|
102
|
+
required(
|
|
103
|
+
git &&
|
|
104
|
+
realpathSync(git.path) === git.path &&
|
|
105
|
+
binaryDigest(git.path) === git.sha256,
|
|
106
|
+
"controller Git identity is unqualified"
|
|
107
|
+
);
|
|
108
|
+
return {
|
|
109
|
+
command: git.path,
|
|
110
|
+
args,
|
|
111
|
+
options: {
|
|
112
|
+
...options,
|
|
113
|
+
cwd,
|
|
114
|
+
env: childEnvironment(options, contextFile, false),
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const canonical =
|
|
119
|
+
controllerRoute(
|
|
120
|
+
tool,
|
|
121
|
+
args,
|
|
122
|
+
context.graph,
|
|
123
|
+
currentGraph(controllerGraph(context.graph, context.routes)),
|
|
124
|
+
cwd,
|
|
125
|
+
context.routes,
|
|
126
|
+
options.env ?? process.env
|
|
127
|
+
) === "controller";
|
|
128
|
+
if (canonical && tool === "node") {
|
|
129
|
+
const instrumentedArgs = ["--import", pathToFileURL(ENTRY).href, ...args];
|
|
130
|
+
literalArguments(instrumentedArgs);
|
|
131
|
+
return {
|
|
132
|
+
command: node.path,
|
|
133
|
+
args: instrumentedArgs,
|
|
134
|
+
options: {
|
|
135
|
+
...options,
|
|
136
|
+
cwd,
|
|
137
|
+
env: childEnvironment(options, contextFile, true),
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const proxyArgs = [
|
|
142
|
+
LAUNCHER,
|
|
143
|
+
"--context",
|
|
144
|
+
contextFile,
|
|
145
|
+
"--tool",
|
|
146
|
+
tool,
|
|
147
|
+
"--",
|
|
148
|
+
...args,
|
|
149
|
+
];
|
|
150
|
+
literalArguments(proxyArgs);
|
|
151
|
+
return {
|
|
152
|
+
command: node.path,
|
|
153
|
+
args: proxyArgs,
|
|
154
|
+
options: {
|
|
155
|
+
...options,
|
|
156
|
+
cwd,
|
|
157
|
+
env: childEnvironment(options, contextFile, false),
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Normalize only Node's documented argument overloads, retaining buffers, FDs and callbacks. */
|
|
163
|
+
function invocationArguments(args, options) {
|
|
164
|
+
if (!Array.isArray(args)) return { args: [], options: args ?? {} };
|
|
165
|
+
return { args, options: options ?? {} };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** This instruments exclusively trusted code; it is not a sandbox for arbitrary same-process JS. */
|
|
169
|
+
export function initializeAdapter(contextFile) {
|
|
170
|
+
const context = launcherContext(contextFile);
|
|
171
|
+
const provider = scopedProvider(context);
|
|
172
|
+
for (const method of ["spawn", "spawnSync", "execFileSync"])
|
|
173
|
+
childProcess[method] = (command, args, options) => {
|
|
174
|
+
const normalized = invocationArguments(args, options);
|
|
175
|
+
if (
|
|
176
|
+
provider &&
|
|
177
|
+
(command === "gh" || command === context.provider.nativeGh.path)
|
|
178
|
+
)
|
|
179
|
+
return provider(
|
|
180
|
+
method,
|
|
181
|
+
command,
|
|
182
|
+
normalized.args,
|
|
183
|
+
normalized.options,
|
|
184
|
+
ORIGINAL.spawnSync
|
|
185
|
+
);
|
|
186
|
+
const selected = nestedInvocation(
|
|
187
|
+
context,
|
|
188
|
+
contextFile,
|
|
189
|
+
command,
|
|
190
|
+
normalized.args,
|
|
191
|
+
normalized.options
|
|
192
|
+
);
|
|
193
|
+
return ORIGINAL[method](
|
|
194
|
+
selected.command,
|
|
195
|
+
selected.args,
|
|
196
|
+
selected.options
|
|
197
|
+
);
|
|
198
|
+
};
|
|
199
|
+
childProcess.execFile = (
|
|
200
|
+
command,
|
|
201
|
+
suppliedArgs,
|
|
202
|
+
suppliedOptions,
|
|
203
|
+
suppliedCallback
|
|
204
|
+
) => {
|
|
205
|
+
let args = suppliedArgs;
|
|
206
|
+
let options = suppliedOptions;
|
|
207
|
+
let callback = suppliedCallback;
|
|
208
|
+
if (typeof args === "function") {
|
|
209
|
+
callback = args;
|
|
210
|
+
args = [];
|
|
211
|
+
options = {};
|
|
212
|
+
} else if (typeof options === "function") {
|
|
213
|
+
callback = options;
|
|
214
|
+
options = {};
|
|
215
|
+
}
|
|
216
|
+
const normalized = invocationArguments(args, options);
|
|
217
|
+
required(
|
|
218
|
+
!provider ||
|
|
219
|
+
(command !== "gh" && command !== context.provider.nativeGh.path),
|
|
220
|
+
"credentialed GH requires synchronous canonical dispatch"
|
|
221
|
+
);
|
|
222
|
+
const selected = nestedInvocation(
|
|
223
|
+
context,
|
|
224
|
+
contextFile,
|
|
225
|
+
command,
|
|
226
|
+
normalized.args,
|
|
227
|
+
normalized.options
|
|
228
|
+
);
|
|
229
|
+
return ORIGINAL.execFile(
|
|
230
|
+
selected.command,
|
|
231
|
+
selected.args,
|
|
232
|
+
selected.options,
|
|
233
|
+
callback
|
|
234
|
+
);
|
|
235
|
+
};
|
|
236
|
+
syncBuiltinESMExports();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Graph membership and immutable argv/cwd qualify this genuine parent-owned canonical caller. */
|
|
240
|
+
function scopedProvider(context) {
|
|
241
|
+
if (!context.provider) return null;
|
|
242
|
+
const profile = context.provider;
|
|
243
|
+
const preload = ["--import", pathToFileURL(ENTRY).href];
|
|
244
|
+
required(
|
|
245
|
+
canonicalJson(process.execArgv.slice(0, 2)) === canonicalJson(preload),
|
|
246
|
+
"GH caller lacks qualified instrumentation"
|
|
247
|
+
);
|
|
248
|
+
const actual = [...process.execArgv.slice(2), ...process.argv.slice(1)];
|
|
249
|
+
required(
|
|
250
|
+
canonicalJson(actual) === canonicalJson(profile.invocation.args) &&
|
|
251
|
+
realpathSync(process.cwd()) === profile.invocation.cwd &&
|
|
252
|
+
Object.hasOwn(context.graph, profile.invocation.entry),
|
|
253
|
+
"GH caller tuple differs"
|
|
254
|
+
);
|
|
255
|
+
required(
|
|
256
|
+
actual[0] === profile.invocation.entry ||
|
|
257
|
+
context.routes?.some(
|
|
258
|
+
route =>
|
|
259
|
+
route.tool === "node" &&
|
|
260
|
+
route.entry === profile.invocation.entry &&
|
|
261
|
+
canonicalJson(route.args) === canonicalJson(actual) &&
|
|
262
|
+
route.cwd === profile.cwd
|
|
263
|
+
),
|
|
264
|
+
"GH caller entry differs"
|
|
265
|
+
);
|
|
266
|
+
const route = controllerRoute(
|
|
267
|
+
"node",
|
|
268
|
+
actual,
|
|
269
|
+
context.graph,
|
|
270
|
+
currentGraph(controllerGraph(context.graph, context.routes)),
|
|
271
|
+
profile.cwd,
|
|
272
|
+
context.routes,
|
|
273
|
+
process.env
|
|
274
|
+
);
|
|
275
|
+
required(
|
|
276
|
+
route === "controller" &&
|
|
277
|
+
realpathSync(process.execPath) === context.controller.node.path &&
|
|
278
|
+
binaryDigest(process.execPath) === context.controller.node.sha256,
|
|
279
|
+
"GH caller graph or interpreter differs"
|
|
280
|
+
);
|
|
281
|
+
return createGhDispatcher(profile, process.env.GH_TOKEN);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (
|
|
285
|
+
process.env.LISA_NPM_DISPATCH_CONTEXT &&
|
|
286
|
+
process.execArgv.some(
|
|
287
|
+
(value, index, values) =>
|
|
288
|
+
value === `--import=${pathToFileURL(ENTRY).href}` ||
|
|
289
|
+
(value === "--import" && values[index + 1] === pathToFileURL(ENTRY).href)
|
|
290
|
+
)
|
|
291
|
+
)
|
|
292
|
+
initializeAdapter(process.env.LISA_NPM_DISPATCH_CONTEXT);
|