@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,300 @@
|
|
|
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
|
+
/**
|
|
5
|
+
* @file npm-update-gate.mjs
|
|
6
|
+
* @description API transport has authority only over the raw object that passed ordinary hooks.
|
|
7
|
+
* @module npm-updater
|
|
8
|
+
*/
|
|
9
|
+
import { writeFileSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { canonicalJson } from "../lisa-automation-provenance.mjs";
|
|
12
|
+
import {
|
|
13
|
+
FILES,
|
|
14
|
+
OBJECT,
|
|
15
|
+
required,
|
|
16
|
+
validateProposal,
|
|
17
|
+
validateRawCommit,
|
|
18
|
+
} from "./npm-update-contract.mjs";
|
|
19
|
+
import {
|
|
20
|
+
candidateEnvironment,
|
|
21
|
+
runProcess,
|
|
22
|
+
withPrivateRoot,
|
|
23
|
+
} from "./npm-update-process.mjs";
|
|
24
|
+
import { baseline } from "./npm-update-prepare.mjs";
|
|
25
|
+
import { sha256 } from "./github-attestation-verifier.mjs";
|
|
26
|
+
import {
|
|
27
|
+
createHostedGate,
|
|
28
|
+
assertHostedGate,
|
|
29
|
+
remainingGateTime,
|
|
30
|
+
} from "./npm-update-hosted-gate.mjs";
|
|
31
|
+
import {
|
|
32
|
+
originalHookInstallation,
|
|
33
|
+
runGateStreams,
|
|
34
|
+
} from "./npm-update-gate-hooks.mjs";
|
|
35
|
+
import {
|
|
36
|
+
installGateProof,
|
|
37
|
+
validateGateProof,
|
|
38
|
+
} from "./npm-update-gate-proof.mjs";
|
|
39
|
+
import { GitHub } from "./npm-update-github.mjs";
|
|
40
|
+
import {
|
|
41
|
+
destinationSnapshot,
|
|
42
|
+
ordinaryGateReceipt,
|
|
43
|
+
} from "./npm-update-publication.mjs";
|
|
44
|
+
import { executeCanonicalHelper } from "./npm-update-controller-factory.mjs";
|
|
45
|
+
|
|
46
|
+
const BOT = "github-actions[bot]";
|
|
47
|
+
const MAIL = "41898282+github-actions[bot]@users.noreply.github.com";
|
|
48
|
+
|
|
49
|
+
/** Ref transport is closed and complete; publish exactly one new branch. */
|
|
50
|
+
export function refTransport(branch, commit, refs) {
|
|
51
|
+
required(
|
|
52
|
+
/^lisa\/npm-[a-f0-9]{64}$/.test(branch) && OBJECT.test(commit),
|
|
53
|
+
"invalid publication ref"
|
|
54
|
+
);
|
|
55
|
+
const expected = `refs/heads/${branch} ${commit} refs/heads/${branch} ${"0".repeat(40)}\n`;
|
|
56
|
+
required(
|
|
57
|
+
refs === expected,
|
|
58
|
+
"omitted, foreign, duplicate or changed pushed ref"
|
|
59
|
+
);
|
|
60
|
+
return expected;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Ordinary Git retains literal argv/stdin and receives no provider credential. */
|
|
64
|
+
function git(cwd, env, args, input, deadline) {
|
|
65
|
+
return runProcess("git", args, {
|
|
66
|
+
cwd,
|
|
67
|
+
env,
|
|
68
|
+
input,
|
|
69
|
+
timeout: deadline === undefined ? 1_800_000 : remainingGateTime(deadline),
|
|
70
|
+
maximum: 3_145_728,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Preview writes owned Git objects without executing repository or npm code. */
|
|
75
|
+
export async function descriptorFor({
|
|
76
|
+
cwd,
|
|
77
|
+
proposal,
|
|
78
|
+
policy,
|
|
79
|
+
automation,
|
|
80
|
+
allocation,
|
|
81
|
+
runId,
|
|
82
|
+
runAttempt,
|
|
83
|
+
epoch,
|
|
84
|
+
}) {
|
|
85
|
+
validateProposal(proposal, policy);
|
|
86
|
+
required(
|
|
87
|
+
/^[1-9]\d*$/.test(runId) &&
|
|
88
|
+
/^[1-9]\d*$/.test(runAttempt) &&
|
|
89
|
+
Number.isSafeInteger(epoch) &&
|
|
90
|
+
epoch > 0,
|
|
91
|
+
"invalid actual Actions identity/time"
|
|
92
|
+
);
|
|
93
|
+
return withPrivateRoot(async (root, env) => {
|
|
94
|
+
await baseline(cwd, env, proposal);
|
|
95
|
+
const staged = { ...env, GIT_INDEX_FILE: join(root, "index") };
|
|
96
|
+
await git(cwd, staged, ["read-tree", proposal.parent]);
|
|
97
|
+
for (const file of FILES) {
|
|
98
|
+
const blob = (
|
|
99
|
+
await git(
|
|
100
|
+
cwd,
|
|
101
|
+
staged,
|
|
102
|
+
["hash-object", "-w", "--stdin"],
|
|
103
|
+
proposal.files[file]
|
|
104
|
+
)
|
|
105
|
+
).stdout
|
|
106
|
+
.toString()
|
|
107
|
+
.trim();
|
|
108
|
+
await git(cwd, staged, [
|
|
109
|
+
"update-index",
|
|
110
|
+
"--add",
|
|
111
|
+
"--cacheinfo",
|
|
112
|
+
`100644,${blob},${file}`,
|
|
113
|
+
]);
|
|
114
|
+
}
|
|
115
|
+
const tree = (await git(cwd, staged, ["write-tree"])).stdout
|
|
116
|
+
.toString()
|
|
117
|
+
.trim();
|
|
118
|
+
const identity = `${BOT} <${MAIL}> ${epoch} +0000`;
|
|
119
|
+
const message = `chore(deps): update selected npm dependencies\n\nWork-Item: ${allocation.workItem}\nAutomation-Provenance: actions/${runId}/attempts/${runAttempt}\n`;
|
|
120
|
+
const descriptor = {
|
|
121
|
+
version: 1,
|
|
122
|
+
parent: proposal.parent,
|
|
123
|
+
tree,
|
|
124
|
+
author: identity,
|
|
125
|
+
committer: identity,
|
|
126
|
+
files: proposal.hashes,
|
|
127
|
+
messageSha256: sha256(message),
|
|
128
|
+
updates: proposal.updates,
|
|
129
|
+
proposalKey: proposal.bindingKey,
|
|
130
|
+
policySha256: sha256(canonicalJson(automation)),
|
|
131
|
+
queue: policy.repository,
|
|
132
|
+
workItem: allocation.workItem,
|
|
133
|
+
claimCommentId: allocation.claimCommentId,
|
|
134
|
+
claimSha256: allocation.claimSha256,
|
|
135
|
+
runId,
|
|
136
|
+
runAttempt,
|
|
137
|
+
};
|
|
138
|
+
return { descriptor, message, epoch };
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Staging retains the ordinary link/branch commands and refuses foreign checkout bytes first. */
|
|
143
|
+
async function stageProposal(context, env) {
|
|
144
|
+
const { cwd, proposal } = context;
|
|
145
|
+
const command = args => git(cwd, env, args, undefined, context.deadline);
|
|
146
|
+
await baseline(cwd, env, proposal);
|
|
147
|
+
const parent = (await command(["rev-parse", "HEAD"])).stdout
|
|
148
|
+
.toString()
|
|
149
|
+
.trim();
|
|
150
|
+
required(parent === proposal.parent, "gate baseline differs");
|
|
151
|
+
required(
|
|
152
|
+
!(await command(["status", "--porcelain"])).stdout.toString().trim(),
|
|
153
|
+
"gate requires a fresh owned checkout"
|
|
154
|
+
);
|
|
155
|
+
const branch = `lisa/npm-${proposal.key}`;
|
|
156
|
+
await command(["checkout", "-b", branch]);
|
|
157
|
+
await executeCanonicalHelper(context, "stage-read", "link");
|
|
158
|
+
await executeCanonicalHelper(context, "stage-read", "attach-branch");
|
|
159
|
+
for (const file of FILES)
|
|
160
|
+
writeFileSync(join(cwd, file), proposal.files[file]);
|
|
161
|
+
await command(["add", "--", ...FILES]);
|
|
162
|
+
return branch;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Normal commit output is accepted only after exact raw object, diff and full-range readback. */
|
|
166
|
+
async function committedProposal(
|
|
167
|
+
{ cwd, proposal, preview, deadline },
|
|
168
|
+
env,
|
|
169
|
+
message
|
|
170
|
+
) {
|
|
171
|
+
const command = args => git(cwd, env, args, undefined, deadline);
|
|
172
|
+
const committed = await command([
|
|
173
|
+
"commit",
|
|
174
|
+
"--cleanup=verbatim",
|
|
175
|
+
"--file",
|
|
176
|
+
message,
|
|
177
|
+
]);
|
|
178
|
+
const head = (await command(["rev-parse", "HEAD"])).stdout.toString().trim();
|
|
179
|
+
const raw = (await command(["cat-file", "commit", head])).stdout;
|
|
180
|
+
const parsed = validateRawCommit(raw, preview.descriptor, cwd);
|
|
181
|
+
required(parsed.sha === head, "actual raw commit SHA differs");
|
|
182
|
+
const changed = (
|
|
183
|
+
await command(["diff", "--name-only", proposal.parent, head])
|
|
184
|
+
).stdout
|
|
185
|
+
.toString()
|
|
186
|
+
.trim()
|
|
187
|
+
.split("\n")
|
|
188
|
+
.sort();
|
|
189
|
+
required(
|
|
190
|
+
changed.join("\n") === FILES.join("\n"),
|
|
191
|
+
"committed diff includes foreign paths"
|
|
192
|
+
);
|
|
193
|
+
const ranges = (
|
|
194
|
+
await command(["rev-list", `${proposal.parent}..${head}`])
|
|
195
|
+
).stdout
|
|
196
|
+
.toString()
|
|
197
|
+
.trim()
|
|
198
|
+
.split("\n");
|
|
199
|
+
required(
|
|
200
|
+
ranges.length === 1 && ranges[0] === head,
|
|
201
|
+
"previous or omitted commit in publication range"
|
|
202
|
+
);
|
|
203
|
+
return { committed, head, raw, ranges };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Legacy transport retains exact original stdin and refuses any gate-induced checkout change. */
|
|
207
|
+
async function pushProposal(context, env, branch, object) {
|
|
208
|
+
const {
|
|
209
|
+
cwd,
|
|
210
|
+
proposal,
|
|
211
|
+
policy,
|
|
212
|
+
config,
|
|
213
|
+
token,
|
|
214
|
+
allocation,
|
|
215
|
+
hookInstallation,
|
|
216
|
+
deadline,
|
|
217
|
+
} = context;
|
|
218
|
+
const { head } = object;
|
|
219
|
+
const api = new GitHub(config.automationProvenance, token);
|
|
220
|
+
const destination = await destinationSnapshot(
|
|
221
|
+
api,
|
|
222
|
+
proposal,
|
|
223
|
+
allocation,
|
|
224
|
+
head
|
|
225
|
+
);
|
|
226
|
+
const command = args => git(cwd, env, args, undefined, deadline);
|
|
227
|
+
const hook = await originalHookInstallation(cwd, command, hookInstallation);
|
|
228
|
+
const remote = `https://github.com/${policy.repository}.git`;
|
|
229
|
+
const streams = await runGateStreams(
|
|
230
|
+
branch,
|
|
231
|
+
head,
|
|
232
|
+
proposal.parent,
|
|
233
|
+
destination.expectedBranchHead,
|
|
234
|
+
refs =>
|
|
235
|
+
runProcess(hook.path, ["origin", remote], {
|
|
236
|
+
cwd,
|
|
237
|
+
env: {
|
|
238
|
+
...env,
|
|
239
|
+
LISA_NPM_HOOK_ROLE: refs.includes(` ${proposal.parent}\n`)
|
|
240
|
+
? "audit"
|
|
241
|
+
: "destination",
|
|
242
|
+
},
|
|
243
|
+
input: refs,
|
|
244
|
+
timeout: remainingGateTime(deadline),
|
|
245
|
+
maximum: 8_388_608,
|
|
246
|
+
})
|
|
247
|
+
);
|
|
248
|
+
required(
|
|
249
|
+
!(await command(["status", "--porcelain"])).stdout.toString().trim(),
|
|
250
|
+
"ordinary gates changed checkout bytes"
|
|
251
|
+
);
|
|
252
|
+
return ordinaryGateReceipt(
|
|
253
|
+
context,
|
|
254
|
+
object,
|
|
255
|
+
hook,
|
|
256
|
+
streams,
|
|
257
|
+
destination,
|
|
258
|
+
remote
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Ordinary commit and original pre-push receive destination/stdin and all ranges. */
|
|
263
|
+
export async function gateProposal(context) {
|
|
264
|
+
const scoped = { ...context, deadline: Date.now() + 1_800_000 };
|
|
265
|
+
const { proposal, policy, token, preview } = context;
|
|
266
|
+
validateProposal(proposal, policy);
|
|
267
|
+
assertHostedGate(context);
|
|
268
|
+
validateGateProof(context);
|
|
269
|
+
required(
|
|
270
|
+
typeof token === "string" && token.length > 0,
|
|
271
|
+
"readonly gate token is absent"
|
|
272
|
+
);
|
|
273
|
+
return withPrivateRoot(async root => {
|
|
274
|
+
const env = {
|
|
275
|
+
...candidateEnvironment(root),
|
|
276
|
+
GIT_AUTHOR_NAME: BOT,
|
|
277
|
+
GIT_AUTHOR_EMAIL: MAIL,
|
|
278
|
+
GIT_COMMITTER_NAME: BOT,
|
|
279
|
+
GIT_COMMITTER_EMAIL: MAIL,
|
|
280
|
+
GIT_AUTHOR_DATE: `${preview.epoch} +0000`,
|
|
281
|
+
GIT_COMMITTER_DATE: `${preview.epoch} +0000`,
|
|
282
|
+
};
|
|
283
|
+
const branch = await stageProposal(scoped, env);
|
|
284
|
+
const message = await installGateProof(context, root, args =>
|
|
285
|
+
git(context.cwd, env, args, undefined, scoped.deadline)
|
|
286
|
+
);
|
|
287
|
+
const hosted = await createHostedGate(scoped, root, env);
|
|
288
|
+
try {
|
|
289
|
+
const gated = { ...scoped, hookInstallation: hosted.installation };
|
|
290
|
+
const object = await committedProposal(
|
|
291
|
+
gated,
|
|
292
|
+
{ ...hosted.env, LISA_NPM_HOOK_ROLE: "commit" },
|
|
293
|
+
message
|
|
294
|
+
);
|
|
295
|
+
return await pushProposal(gated, hosted.env, branch, object);
|
|
296
|
+
} finally {
|
|
297
|
+
await hosted.close();
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
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
|
+
/** Credentialed native GH runs only for immutable controller-selected canonical requests. */
|
|
5
|
+
import { realpathSync, lstatSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { required, keys } from "./npm-update-contract.mjs";
|
|
8
|
+
import { binaryDigest } from "./npm-update-isolation.mjs";
|
|
9
|
+
import { isProviderTransport } from "./npm-update-worker-environment.mjs";
|
|
10
|
+
import {
|
|
11
|
+
createGhRequests,
|
|
12
|
+
createGhState,
|
|
13
|
+
prepareGhRequest,
|
|
14
|
+
observeGhResponse,
|
|
15
|
+
} from "./npm-update-gh-requests.mjs";
|
|
16
|
+
|
|
17
|
+
const OPTIONS = new Set([
|
|
18
|
+
"cwd",
|
|
19
|
+
"encoding",
|
|
20
|
+
"env",
|
|
21
|
+
"input",
|
|
22
|
+
"timeout",
|
|
23
|
+
"maxBuffer",
|
|
24
|
+
"killSignal",
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
export function validateGhProfile(profile) {
|
|
28
|
+
keys(profile, [
|
|
29
|
+
"version",
|
|
30
|
+
"deadline",
|
|
31
|
+
"cwd",
|
|
32
|
+
"home",
|
|
33
|
+
"nativeGh",
|
|
34
|
+
"subject",
|
|
35
|
+
"invocation",
|
|
36
|
+
]);
|
|
37
|
+
keys(profile.nativeGh, ["path", "sha256"]);
|
|
38
|
+
keys(profile.invocation, ["entry", "args", "cwd"]);
|
|
39
|
+
const home = lstatSync(profile.home);
|
|
40
|
+
required(
|
|
41
|
+
profile.version === 1 &&
|
|
42
|
+
Number.isSafeInteger(profile.deadline) &&
|
|
43
|
+
profile.deadline > Date.now() &&
|
|
44
|
+
profile.deadline <= Date.now() + 1_800_000 &&
|
|
45
|
+
profile.cwd === realpathSync(profile.cwd) &&
|
|
46
|
+
profile.home === realpathSync(profile.home) &&
|
|
47
|
+
home.isDirectory() &&
|
|
48
|
+
!home.isSymbolicLink() &&
|
|
49
|
+
home.uid === process.getuid() &&
|
|
50
|
+
(home.mode & 0o077) === 0,
|
|
51
|
+
"invalid private native GH phase context"
|
|
52
|
+
);
|
|
53
|
+
required(
|
|
54
|
+
typeof profile.invocation.entry === "string" &&
|
|
55
|
+
profile.invocation.entry.startsWith("/") &&
|
|
56
|
+
profile.invocation.cwd === profile.cwd &&
|
|
57
|
+
Array.isArray(profile.invocation.args) &&
|
|
58
|
+
profile.invocation.args.length > 0 &&
|
|
59
|
+
profile.invocation.args.length <= 512 &&
|
|
60
|
+
profile.invocation.args.every(
|
|
61
|
+
value =>
|
|
62
|
+
typeof value === "string" &&
|
|
63
|
+
!value.includes("\0") &&
|
|
64
|
+
Buffer.byteLength(value) <= 65536
|
|
65
|
+
) &&
|
|
66
|
+
Buffer.byteLength(JSON.stringify(profile.invocation.args)) <= 262144,
|
|
67
|
+
"invalid immutable GH caller tuple"
|
|
68
|
+
);
|
|
69
|
+
checkedNative(profile.nativeGh);
|
|
70
|
+
createGhRequests(profile.subject);
|
|
71
|
+
return JSON.parse(JSON.stringify(profile));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function checkedNative(identity) {
|
|
75
|
+
const stat = lstatSync(identity.path);
|
|
76
|
+
required(
|
|
77
|
+
stat.isFile() &&
|
|
78
|
+
!stat.isSymbolicLink() &&
|
|
79
|
+
identity.path === realpathSync(identity.path) &&
|
|
80
|
+
/^[a-f0-9]{64}$/.test(identity.sha256) &&
|
|
81
|
+
binaryDigest(identity.path) === identity.sha256,
|
|
82
|
+
"native GH identity changed"
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Caller options keep their original limits; no alternate transport or subprocess authority is accepted. */
|
|
87
|
+
function nativeOptions(profile, options, token) {
|
|
88
|
+
required(
|
|
89
|
+
options &&
|
|
90
|
+
typeof options === "object" &&
|
|
91
|
+
!Array.isArray(options) &&
|
|
92
|
+
Object.keys(options).every(name => OPTIONS.has(name)),
|
|
93
|
+
"unsupported native GH subprocess option"
|
|
94
|
+
);
|
|
95
|
+
required(
|
|
96
|
+
(options.cwd ?? profile.cwd) === profile.cwd &&
|
|
97
|
+
options.encoding === "utf8" &&
|
|
98
|
+
options.input === undefined &&
|
|
99
|
+
(options.killSignal === undefined || options.killSignal === "SIGKILL"),
|
|
100
|
+
"native GH cwd/stdin/encoding differs"
|
|
101
|
+
);
|
|
102
|
+
const timeout = options.timeout ?? 30_000;
|
|
103
|
+
const maximum = options.maxBuffer ?? 1_048_576;
|
|
104
|
+
required(
|
|
105
|
+
Number.isSafeInteger(timeout) &&
|
|
106
|
+
timeout > 0 &&
|
|
107
|
+
timeout <= 120_000 &&
|
|
108
|
+
Number.isSafeInteger(maximum) &&
|
|
109
|
+
maximum > 0 &&
|
|
110
|
+
maximum <= 3_145_728,
|
|
111
|
+
"native GH original capture bounds differ"
|
|
112
|
+
);
|
|
113
|
+
const environment = options.env ?? {};
|
|
114
|
+
required(
|
|
115
|
+
typeof environment === "object" &&
|
|
116
|
+
!Array.isArray(environment) &&
|
|
117
|
+
Object.values(environment).every(
|
|
118
|
+
value =>
|
|
119
|
+
value === undefined ||
|
|
120
|
+
(typeof value === "string" && !value.includes("\0"))
|
|
121
|
+
) &&
|
|
122
|
+
(!environment.GH_TOKEN || environment.GH_TOKEN === token) &&
|
|
123
|
+
!environment.GITHUB_TOKEN,
|
|
124
|
+
"native GH caller token or environment differs"
|
|
125
|
+
);
|
|
126
|
+
required(
|
|
127
|
+
Object.entries(environment).every(
|
|
128
|
+
([name, value]) => !value || !isProviderTransport(name)
|
|
129
|
+
) &&
|
|
130
|
+
(!environment.GH_HOST || environment.GH_HOST === "github.com") &&
|
|
131
|
+
(!environment.GITHUB_API_URL ||
|
|
132
|
+
environment.GITHUB_API_URL === "https://api.github.com") &&
|
|
133
|
+
(!environment.GITHUB_SERVER_URL ||
|
|
134
|
+
environment.GITHUB_SERVER_URL === "https://github.com"),
|
|
135
|
+
"native GH transport environment override refused"
|
|
136
|
+
);
|
|
137
|
+
const remaining = profile.deadline - Date.now();
|
|
138
|
+
required(remaining > 0, "native GH phase expired");
|
|
139
|
+
return {
|
|
140
|
+
cwd: profile.cwd,
|
|
141
|
+
encoding: "utf8",
|
|
142
|
+
timeout: Math.min(timeout, remaining),
|
|
143
|
+
maxBuffer: maximum,
|
|
144
|
+
killSignal: "SIGKILL",
|
|
145
|
+
env: {
|
|
146
|
+
PATH: "/usr/bin:/bin",
|
|
147
|
+
HOME: profile.home,
|
|
148
|
+
LANG: "C.UTF-8",
|
|
149
|
+
GH_HOST: "github.com",
|
|
150
|
+
GH_TOKEN: token,
|
|
151
|
+
GH_CONFIG_DIR: join(profile.home, "gh-config"),
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Saved ORIGINAL.spawnSync is supplied by trusted instrumentation, never serialized as a recipe. */
|
|
157
|
+
export function createGhDispatcher(suppliedProfile, token) {
|
|
158
|
+
required(
|
|
159
|
+
typeof token === "string" && token.length > 0 && !/[\0\r\n]/.test(token),
|
|
160
|
+
"native GH token is absent or invalid"
|
|
161
|
+
);
|
|
162
|
+
const profile = validateGhProfile(suppliedProfile);
|
|
163
|
+
const scope = createGhRequests(profile.subject);
|
|
164
|
+
const state = createGhState(scope);
|
|
165
|
+
return (method, command, args, options, nativeSpawnSync) => {
|
|
166
|
+
required(
|
|
167
|
+
method === "spawnSync" &&
|
|
168
|
+
(command === "gh" || command === profile.nativeGh.path),
|
|
169
|
+
"unqualified native GH invocation"
|
|
170
|
+
);
|
|
171
|
+
checkedNative(profile.nativeGh);
|
|
172
|
+
const actualOptions = nativeOptions(profile, options, token);
|
|
173
|
+
const request = prepareGhRequest(scope, state, args);
|
|
174
|
+
const result = nativeSpawnSync(
|
|
175
|
+
profile.nativeGh.path,
|
|
176
|
+
[...args],
|
|
177
|
+
actualOptions
|
|
178
|
+
);
|
|
179
|
+
observeGhResponse(scope, state, request, result);
|
|
180
|
+
return result;
|
|
181
|
+
};
|
|
182
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
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
|
+
/** Native successful responses grant only one exact canonical successor request. */
|
|
5
|
+
import { canonicalJson } from "../lisa-automation-provenance.mjs";
|
|
6
|
+
import { required } from "./npm-update-contract.mjs";
|
|
7
|
+
import {
|
|
8
|
+
githubHierarchyArgs,
|
|
9
|
+
backlinkBody,
|
|
10
|
+
partitionBacklinks,
|
|
11
|
+
githubBacklinkComments,
|
|
12
|
+
} from "../lisa-work-item.mjs";
|
|
13
|
+
const key = args => canonicalJson(args);
|
|
14
|
+
const ID = /^[1-9]\d*$/;
|
|
15
|
+
|
|
16
|
+
export function recoveryPage(subject, page) {
|
|
17
|
+
const head = encodeURIComponent(
|
|
18
|
+
`${subject.repository.split("/")[0]}:${subject.branch}`
|
|
19
|
+
);
|
|
20
|
+
return [
|
|
21
|
+
"api",
|
|
22
|
+
"--hostname",
|
|
23
|
+
"github.com",
|
|
24
|
+
`repos/${subject.repository}/pulls?state=all&head=${head}&per_page=100&page=${page}`,
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createGhState(scope) {
|
|
29
|
+
return {
|
|
30
|
+
grants: new Map(
|
|
31
|
+
scope.records
|
|
32
|
+
.filter(record => record.kind !== "plain")
|
|
33
|
+
.map(record => [key(record.args), record])
|
|
34
|
+
),
|
|
35
|
+
requests: 0,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Consume every successor before native dispatch, including a repeated response cursor. */
|
|
40
|
+
export function prepareGhRequest(scope, state, args) {
|
|
41
|
+
required(
|
|
42
|
+
Array.isArray(args) &&
|
|
43
|
+
args.every(value => typeof value === "string" && !value.includes("\0")),
|
|
44
|
+
"invalid literal GH request"
|
|
45
|
+
);
|
|
46
|
+
const serialized = key(args);
|
|
47
|
+
const record =
|
|
48
|
+
scope.records.find(
|
|
49
|
+
value => value.kind === "plain" && key(value.args) === serialized
|
|
50
|
+
) ?? state.grants.get(serialized);
|
|
51
|
+
required(
|
|
52
|
+
record && state.requests < 1024,
|
|
53
|
+
"unqualified or exhausted GH request"
|
|
54
|
+
);
|
|
55
|
+
if (record.kind !== "plain") state.grants.delete(serialized);
|
|
56
|
+
state.requests++;
|
|
57
|
+
return record;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Primitive and file coercions in gh's typed -F grammar are never accepted as cursors. */
|
|
61
|
+
function cursorValue(value) {
|
|
62
|
+
required(
|
|
63
|
+
typeof value === "string" &&
|
|
64
|
+
value.length > 0 &&
|
|
65
|
+
Buffer.byteLength(value) <= 1024 &&
|
|
66
|
+
!/[\0\n\r]/.test(value) &&
|
|
67
|
+
!value.startsWith("@") &&
|
|
68
|
+
!/^(?:true|false|null|-?\d+(?:\.\d+)?)$/.test(value),
|
|
69
|
+
"invalid typed GH hierarchy cursor"
|
|
70
|
+
);
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Only an actual successful bounded response can grant the next exact request. */
|
|
75
|
+
export function observeGhResponse(scope, state, request, result) {
|
|
76
|
+
if (
|
|
77
|
+
request.kind === "plain" ||
|
|
78
|
+
request.kind === "write" ||
|
|
79
|
+
result.error ||
|
|
80
|
+
result.signal ||
|
|
81
|
+
result.status !== 0
|
|
82
|
+
)
|
|
83
|
+
return;
|
|
84
|
+
required(
|
|
85
|
+
typeof result.stdout === "string" &&
|
|
86
|
+
Buffer.byteLength(result.stdout) <= 3_145_728,
|
|
87
|
+
"unbounded GH response grant"
|
|
88
|
+
);
|
|
89
|
+
const value = JSON.parse(result.stdout);
|
|
90
|
+
let successor;
|
|
91
|
+
if (request.kind === "hierarchy") {
|
|
92
|
+
const page = value.data?.repository?.issue?.subIssues;
|
|
93
|
+
required(
|
|
94
|
+
Array.isArray(page?.nodes) &&
|
|
95
|
+
page.nodes.length <= 100 &&
|
|
96
|
+
typeof page.pageInfo?.hasNextPage === "boolean",
|
|
97
|
+
"invalid GH hierarchy grant"
|
|
98
|
+
);
|
|
99
|
+
if (page.pageInfo.hasNextPage)
|
|
100
|
+
successor = {
|
|
101
|
+
kind: "hierarchy",
|
|
102
|
+
args: githubHierarchyArgs(
|
|
103
|
+
{ repository: scope.subject.tracker },
|
|
104
|
+
scope.subject.issue,
|
|
105
|
+
cursorValue(page.pageInfo.endCursor)
|
|
106
|
+
),
|
|
107
|
+
};
|
|
108
|
+
} else if (request.kind === "recovery-page") {
|
|
109
|
+
required(
|
|
110
|
+
Array.isArray(value) && value.length <= 100,
|
|
111
|
+
"invalid GH recovery page grant"
|
|
112
|
+
);
|
|
113
|
+
if (value.length === 100) {
|
|
114
|
+
required(request.page < 20, "incomplete GH recovery pages");
|
|
115
|
+
successor = {
|
|
116
|
+
kind: "recovery-page",
|
|
117
|
+
page: request.page + 1,
|
|
118
|
+
args: recoveryPage(scope.subject, request.page + 1),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
} else if (request.kind === "comments")
|
|
122
|
+
successor = backlinkRequest(scope.subject, githubBacklinkComments(value));
|
|
123
|
+
if (successor) state.grants.set(key(successor.args), successor);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Canonical marker partitioning keeps every other PR's comment outside writer authority. */
|
|
127
|
+
function backlinkRequest(subject, comments) {
|
|
128
|
+
required(
|
|
129
|
+
subject.phase === "publication-backlink" &&
|
|
130
|
+
subject.pr &&
|
|
131
|
+
Array.isArray(comments) &&
|
|
132
|
+
comments.length <= 10000,
|
|
133
|
+
"invalid GH backlink response"
|
|
134
|
+
);
|
|
135
|
+
const { mine } = partitionBacklinks(
|
|
136
|
+
comments,
|
|
137
|
+
subject.pr.url,
|
|
138
|
+
comment => comment?.body
|
|
139
|
+
);
|
|
140
|
+
const body = backlinkBody(subject.pr.url);
|
|
141
|
+
if (mine?.body === body) return null;
|
|
142
|
+
required(
|
|
143
|
+
!mine ||
|
|
144
|
+
((typeof mine.id === "string" || Number.isSafeInteger(mine.id)) &&
|
|
145
|
+
ID.test(String(mine.id))),
|
|
146
|
+
"invalid GH backlink comment ID"
|
|
147
|
+
);
|
|
148
|
+
const method = mine ? "PATCH" : "POST";
|
|
149
|
+
const endpoint = mine
|
|
150
|
+
? `repos/${subject.tracker}/issues/comments/${mine.id}`
|
|
151
|
+
: `repos/${subject.tracker}/issues/${subject.issue}/comments`;
|
|
152
|
+
return {
|
|
153
|
+
kind: "write",
|
|
154
|
+
args: ["api", "--method", method, endpoint, "--field", `body=${body}`],
|
|
155
|
+
};
|
|
156
|
+
}
|