@codyswann/lisa 4.71.0 → 4.71.2
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/history-secret-scanner.mjs +46 -37
- package/all/copy-overwrite/scripts/lib/npm-update-gate.mjs +52 -35
- package/all/copy-overwrite/scripts/lib/npm-update-hosted-gate.mjs +80 -30
- package/all/copy-overwrite/scripts/lib/npm-update-invariants.mjs +97 -0
- package/all/copy-overwrite/scripts/lib/npm-update-native-process.mjs +24 -6
- package/all/copy-overwrite/scripts/lib/npm-update-publish.mjs +0 -4
- package/all/copy-overwrite/scripts/lisa-npm-updater.mjs +37 -16
- package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +11 -7
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +34 -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 +14 -9
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- 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-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-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/rails/copy-overwrite/ast-grep/rules/ruby/no-unsafe-send.yml +17 -0
|
@@ -52,6 +52,18 @@ export const PINS = Object.freeze({
|
|
|
52
52
|
const digest = bytes => createHash("sha256").update(bytes).digest("hex");
|
|
53
53
|
const shellQuote = value => `'${value.replaceAll("'", "'\\''")}'`;
|
|
54
54
|
|
|
55
|
+
/** Keep vendor recreation private without changing the calling process's mask. */
|
|
56
|
+
export const scannerInvocation = (binary, argv) => ({
|
|
57
|
+
binary: "/bin/sh",
|
|
58
|
+
argv: [
|
|
59
|
+
"-c",
|
|
60
|
+
'umask 077 || exit 1; exec "$@"',
|
|
61
|
+
"lisa-private-scanner",
|
|
62
|
+
binary,
|
|
63
|
+
...argv,
|
|
64
|
+
],
|
|
65
|
+
});
|
|
66
|
+
|
|
55
67
|
/** Audit the actual vendor Git children: Gitleaks can swallow their failures. */
|
|
56
68
|
const auditedGit = scratch => {
|
|
57
69
|
const environment = gitEnvironment();
|
|
@@ -244,8 +256,8 @@ export const scanCommits = (commits, cwd, requested, heads = []) => {
|
|
|
244
256
|
const report = join(scratch, "report.json");
|
|
245
257
|
const ignore = join(scratch, "ignore");
|
|
246
258
|
writeFileSync(config, "[extend]\nuseDefault = true\n", { mode: 0o600 });
|
|
247
|
-
// Gitleaks
|
|
248
|
-
//
|
|
259
|
+
// Gitleaks removes and recreates this file; its dedicated child sets umask
|
|
260
|
+
// before vendor creation, while the caller's mask remains unchanged.
|
|
249
261
|
writeFileSync(report, "[]", { mode: 0o600 });
|
|
250
262
|
writeFileSync(ignore, "", { mode: 0o600 });
|
|
251
263
|
// An isolated bare view excludes caller ignore files even in Git metadata.
|
|
@@ -272,41 +284,38 @@ export const scanCommits = (commits, cwd, requested, heads = []) => {
|
|
|
272
284
|
for (let offset = 0; offset < commits.length; offset += 100) {
|
|
273
285
|
const group = commits.slice(offset, offset + 100);
|
|
274
286
|
audit.begin();
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
308
|
-
}
|
|
309
|
-
);
|
|
287
|
+
const invocation = scannerInvocation(scanner, [
|
|
288
|
+
"git",
|
|
289
|
+
source,
|
|
290
|
+
"--config",
|
|
291
|
+
config,
|
|
292
|
+
"--gitleaks-ignore-path",
|
|
293
|
+
ignore,
|
|
294
|
+
"--ignore-gitleaks-allow",
|
|
295
|
+
"--redact=100",
|
|
296
|
+
"--no-banner",
|
|
297
|
+
"--no-color",
|
|
298
|
+
"--log-level",
|
|
299
|
+
"error",
|
|
300
|
+
"--platform",
|
|
301
|
+
"none",
|
|
302
|
+
"--exit-code",
|
|
303
|
+
"42",
|
|
304
|
+
"--timeout",
|
|
305
|
+
"120",
|
|
306
|
+
"--report-format",
|
|
307
|
+
"json",
|
|
308
|
+
"--report-path",
|
|
309
|
+
report,
|
|
310
|
+
"--log-opts",
|
|
311
|
+
`--no-walk=unsorted --root --diff-merges=separate --no-ext-diff --no-textconv --no-renames ${group.join(" ")}`,
|
|
312
|
+
]);
|
|
313
|
+
const result = spawnSync(invocation.binary, invocation.argv, {
|
|
314
|
+
cwd: scratch,
|
|
315
|
+
env: audit.environment,
|
|
316
|
+
timeout: 130000,
|
|
317
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
318
|
+
});
|
|
310
319
|
if (
|
|
311
320
|
result.error ||
|
|
312
321
|
result.signal ||
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
ordinaryGateReceipt,
|
|
43
43
|
} from "./npm-update-publication.mjs";
|
|
44
44
|
import { executeCanonicalHelper } from "./npm-update-controller-factory.mjs";
|
|
45
|
+
import { withStage } from "./npm-update-invariants.mjs";
|
|
45
46
|
|
|
46
47
|
const BOT = "github-actions[bot]";
|
|
47
48
|
const MAIL = "41898282+github-actions[bot]@users.noreply.github.com";
|
|
@@ -143,7 +144,7 @@ export async function descriptorFor({
|
|
|
143
144
|
async function stageProposal(context, env) {
|
|
144
145
|
const { cwd, proposal } = context;
|
|
145
146
|
const command = args => git(cwd, env, args, undefined, context.deadline);
|
|
146
|
-
await baseline(cwd, env, proposal);
|
|
147
|
+
await withStage("gate-baseline", () => baseline(cwd, env, proposal));
|
|
147
148
|
const parent = (await command(["rev-parse", "HEAD"])).stdout
|
|
148
149
|
.toString()
|
|
149
150
|
.trim();
|
|
@@ -154,8 +155,12 @@ async function stageProposal(context, env) {
|
|
|
154
155
|
);
|
|
155
156
|
const branch = `lisa/npm-${proposal.key}`;
|
|
156
157
|
await command(["checkout", "-b", branch]);
|
|
157
|
-
await
|
|
158
|
-
|
|
158
|
+
await withStage("gate-link", () =>
|
|
159
|
+
executeCanonicalHelper(context, "stage-read", "link")
|
|
160
|
+
);
|
|
161
|
+
await withStage("gate-attach", () =>
|
|
162
|
+
executeCanonicalHelper(context, "stage-read", "attach-branch")
|
|
163
|
+
);
|
|
159
164
|
for (const file of FILES)
|
|
160
165
|
writeFileSync(join(cwd, file), proposal.files[file]);
|
|
161
166
|
await command(["add", "--", ...FILES]);
|
|
@@ -263,38 +268,50 @@ async function pushProposal(context, env, branch, object) {
|
|
|
263
268
|
export async function gateProposal(context) {
|
|
264
269
|
const scoped = { ...context, deadline: Date.now() + 1_800_000 };
|
|
265
270
|
const { proposal, policy, token, preview } = context;
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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)
|
|
271
|
+
await withStage("gate-validate", () => {
|
|
272
|
+
validateProposal(proposal, policy);
|
|
273
|
+
assertHostedGate(context);
|
|
274
|
+
validateGateProof(context);
|
|
275
|
+
required(
|
|
276
|
+
typeof token === "string" && token.length > 0,
|
|
277
|
+
"readonly gate token is absent"
|
|
286
278
|
);
|
|
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
279
|
});
|
|
280
|
+
return withStage("gate-scratch", () =>
|
|
281
|
+
withPrivateRoot(async root => {
|
|
282
|
+
const env = {
|
|
283
|
+
...candidateEnvironment(root),
|
|
284
|
+
GIT_AUTHOR_NAME: BOT,
|
|
285
|
+
GIT_AUTHOR_EMAIL: MAIL,
|
|
286
|
+
GIT_COMMITTER_NAME: BOT,
|
|
287
|
+
GIT_COMMITTER_EMAIL: MAIL,
|
|
288
|
+
GIT_AUTHOR_DATE: `${preview.epoch} +0000`,
|
|
289
|
+
GIT_COMMITTER_DATE: `${preview.epoch} +0000`,
|
|
290
|
+
};
|
|
291
|
+
const branch = await withStage("gate-stage", () =>
|
|
292
|
+
stageProposal(scoped, env)
|
|
293
|
+
);
|
|
294
|
+
const proofGit = args =>
|
|
295
|
+
git(context.cwd, env, args, undefined, scoped.deadline);
|
|
296
|
+
const message = await withStage("gate-proof", () =>
|
|
297
|
+
installGateProof(context, root, proofGit)
|
|
298
|
+
);
|
|
299
|
+
const hosted = await createHostedGate(scoped, root, env);
|
|
300
|
+
try {
|
|
301
|
+
const gated = { ...scoped, hookInstallation: hosted.installation };
|
|
302
|
+
const object = await withStage("gate-commit", () =>
|
|
303
|
+
committedProposal(
|
|
304
|
+
gated,
|
|
305
|
+
{ ...hosted.env, LISA_NPM_HOOK_ROLE: "commit" },
|
|
306
|
+
message
|
|
307
|
+
)
|
|
308
|
+
);
|
|
309
|
+
return await withStage("gate-push", () =>
|
|
310
|
+
pushProposal(gated, hosted.env, branch, object)
|
|
311
|
+
);
|
|
312
|
+
} finally {
|
|
313
|
+
await withStage("gate-close", () => hosted.close());
|
|
314
|
+
}
|
|
315
|
+
})
|
|
316
|
+
);
|
|
300
317
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import {
|
|
6
6
|
existsSync,
|
|
7
7
|
lstatSync,
|
|
8
|
+
realpathSync,
|
|
8
9
|
readFileSync,
|
|
9
10
|
mkdirSync,
|
|
10
11
|
writeFileSync,
|
|
@@ -12,7 +13,7 @@ import {
|
|
|
12
13
|
import { join, resolve } from "node:path";
|
|
13
14
|
import { fileURLToPath } from "node:url";
|
|
14
15
|
import { required } from "./npm-update-contract.mjs";
|
|
15
|
-
import { runProcess, writeJson } from "./npm-update-process.mjs";
|
|
16
|
+
import { readBytes, runProcess, writeJson } from "./npm-update-process.mjs";
|
|
16
17
|
import { qualifiedControllerGraph } from "./npm-update-helper.mjs";
|
|
17
18
|
import {
|
|
18
19
|
controllerTools,
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
} from "./npm-update-controller-factory.mjs";
|
|
21
22
|
import { startHookReadBroker } from "./npm-update-hook-provider.mjs";
|
|
22
23
|
import { originalHookInstallation } from "./npm-update-hook-installation.mjs";
|
|
24
|
+
import { withStage } from "./npm-update-invariants.mjs";
|
|
23
25
|
import {
|
|
24
26
|
sha256,
|
|
25
27
|
PROPOSAL_PREDICATE,
|
|
@@ -109,18 +111,45 @@ async function installHostedDependencies(context, root, env) {
|
|
|
109
111
|
return installed;
|
|
110
112
|
}
|
|
111
113
|
|
|
114
|
+
/** Husky 8 declares its real installer in package metadata; dependency install remains script-disabled. */
|
|
115
|
+
function huskyInstaller(cwd) {
|
|
116
|
+
const root = join(cwd, "node_modules/husky");
|
|
117
|
+
const metadata = JSON.parse(
|
|
118
|
+
readBytes(join(root, "package.json"), 65_536, false)
|
|
119
|
+
);
|
|
120
|
+
const bin =
|
|
121
|
+
typeof metadata.bin === "string" ? metadata.bin : metadata.bin?.husky;
|
|
122
|
+
required(
|
|
123
|
+
metadata.name === "husky" &&
|
|
124
|
+
/^8\.\d+\.\d+$/.test(metadata.version) &&
|
|
125
|
+
typeof bin === "string" &&
|
|
126
|
+
/^(?:[A-Za-z0-9_-]+\/)*[A-Za-z0-9_-]+\.js$/.test(bin),
|
|
127
|
+
"original Husky 8 installer declaration is unsupported"
|
|
128
|
+
);
|
|
129
|
+
const path = join(root, bin);
|
|
130
|
+
required(
|
|
131
|
+
realpathSync(root) === root &&
|
|
132
|
+
realpathSync(path) === path &&
|
|
133
|
+
lstatSync(path).isFile(),
|
|
134
|
+
"original Husky installer is unavailable or aliased"
|
|
135
|
+
);
|
|
136
|
+
return path;
|
|
137
|
+
}
|
|
138
|
+
|
|
112
139
|
/** The original supported manager installs its own wrappers; no synthetic replacement hook is written. */
|
|
113
140
|
async function installOriginalManager(context, env) {
|
|
114
141
|
const lefthook = existsSync(join(context.cwd, "lefthook.yml"));
|
|
115
142
|
if (lefthook)
|
|
116
143
|
await nativeStep(context, env, "bundle", ["exec", "lefthook", "install"]);
|
|
117
144
|
else {
|
|
118
|
-
const husky = join(context.cwd, "node_modules/husky/bin.js");
|
|
119
145
|
required(
|
|
120
|
-
existsSync(
|
|
146
|
+
existsSync(join(context.cwd, ".husky/pre-push")),
|
|
121
147
|
"original hook manager installation is unavailable"
|
|
122
148
|
);
|
|
123
|
-
await nativeStep(context, env, process.execPath, [
|
|
149
|
+
await nativeStep(context, env, process.execPath, [
|
|
150
|
+
huskyInstaller(context.cwd),
|
|
151
|
+
"install",
|
|
152
|
+
]);
|
|
124
153
|
}
|
|
125
154
|
const git = args => nativeStep(context, env, "/usr/bin/git", args);
|
|
126
155
|
const result = await git(["rev-parse", "--git-path", "hooks"]);
|
|
@@ -206,22 +235,9 @@ function nodeGateway(root, context) {
|
|
|
206
235
|
return bin;
|
|
207
236
|
}
|
|
208
237
|
|
|
209
|
-
/**
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const native = controllerTools(context.config.automationProvenance);
|
|
213
|
-
const graph = await qualifiedControllerGraph(context.cwd, context.config, [
|
|
214
|
-
"lisa-work-item.mjs",
|
|
215
|
-
"lisa-automation-provenance.mjs",
|
|
216
|
-
"lisa-rails-prepush.mjs",
|
|
217
|
-
"lib/npm-update-hosted-hook.mjs",
|
|
218
|
-
"lib/npm-update-hook-preload.mjs",
|
|
219
|
-
]);
|
|
220
|
-
const installed = await installHostedDependencies(context, root, env);
|
|
221
|
-
const installation = await installOriginalManager(context, installed);
|
|
222
|
-
const scope = await proofScope(context, installed);
|
|
223
|
-
mkdirSync(join(root, "gh-config"), { mode: 0o700 });
|
|
224
|
-
const profile = {
|
|
238
|
+
/** The unchanged read-only subject is constructed separately so diagnostic wrapping stays within function budgets. */
|
|
239
|
+
function hookProfile(context, root, native, scope) {
|
|
240
|
+
return {
|
|
225
241
|
version: 1,
|
|
226
242
|
deadline: context.deadline,
|
|
227
243
|
cwd: context.cwd,
|
|
@@ -239,16 +255,50 @@ export async function createHostedGate(context, root, env) {
|
|
|
239
255
|
scope.proofs
|
|
240
256
|
),
|
|
241
257
|
};
|
|
242
|
-
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** A separate read-only capability is available during original gates; it cannot acquire publisher/issuer permissions. */
|
|
261
|
+
export async function createHostedGate(context, root, env) {
|
|
262
|
+
await withStage("gate-validate", () => assertHostedGate(context));
|
|
263
|
+
const native = await withStage("gate-tools", () =>
|
|
264
|
+
controllerTools(context.config.automationProvenance)
|
|
265
|
+
);
|
|
266
|
+
const graph = await withStage("gate-graph", () =>
|
|
267
|
+
qualifiedControllerGraph(context.cwd, context.config, [
|
|
268
|
+
"lisa-work-item.mjs",
|
|
269
|
+
"lisa-automation-provenance.mjs",
|
|
270
|
+
"lisa-rails-prepush.mjs",
|
|
271
|
+
"lib/npm-update-hosted-hook.mjs",
|
|
272
|
+
"lib/npm-update-hook-preload.mjs",
|
|
273
|
+
])
|
|
274
|
+
);
|
|
275
|
+
const installed = await withStage("gate-install", () =>
|
|
276
|
+
installHostedDependencies(context, root, env)
|
|
277
|
+
);
|
|
278
|
+
const installation = await withStage("gate-hooks", () =>
|
|
279
|
+
installOriginalManager(context, installed)
|
|
280
|
+
);
|
|
281
|
+
const scope = await withStage("gate-scope", () =>
|
|
282
|
+
proofScope(context, installed)
|
|
283
|
+
);
|
|
284
|
+
const profile = await withStage("gate-scope", () => {
|
|
285
|
+
mkdirSync(join(root, "gh-config"), { mode: 0o700 });
|
|
286
|
+
return hookProfile(context, root, native, scope);
|
|
287
|
+
});
|
|
288
|
+
const broker = await withStage("gate-broker", () =>
|
|
289
|
+
startHookReadBroker(profile, context.token)
|
|
290
|
+
);
|
|
243
291
|
try {
|
|
244
|
-
const bin =
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
292
|
+
const bin = await withStage("gate-gateway", () =>
|
|
293
|
+
nodeGateway(root, {
|
|
294
|
+
version: 1,
|
|
295
|
+
root,
|
|
296
|
+
cwd: context.cwd,
|
|
297
|
+
graph,
|
|
298
|
+
native,
|
|
299
|
+
reader: { root, path: broker.path, deadline: context.deadline },
|
|
300
|
+
})
|
|
301
|
+
);
|
|
252
302
|
return {
|
|
253
303
|
installation,
|
|
254
304
|
env: {
|
|
@@ -266,7 +316,7 @@ export async function createHostedGate(context, root, env) {
|
|
|
266
316
|
close: broker.close,
|
|
267
317
|
};
|
|
268
318
|
} catch (error) {
|
|
269
|
-
await broker.close();
|
|
319
|
+
await withStage("gate-close", () => broker.close());
|
|
270
320
|
throw error;
|
|
271
321
|
}
|
|
272
322
|
}
|
|
@@ -2,6 +2,103 @@
|
|
|
2
2
|
// Do not edit directly — durable changes belong upstream in Lisa.
|
|
3
3
|
|
|
4
4
|
/** Pure shared diagnostics retain one error identity without importing producer orchestration. */
|
|
5
|
+
const stages = new Set([
|
|
6
|
+
"configuration",
|
|
7
|
+
"gate-input",
|
|
8
|
+
"gate-validate",
|
|
9
|
+
"gate-scratch",
|
|
10
|
+
"gate-stage",
|
|
11
|
+
"gate-baseline",
|
|
12
|
+
"gate-link",
|
|
13
|
+
"gate-attach",
|
|
14
|
+
"gate-proof",
|
|
15
|
+
"gate-tools",
|
|
16
|
+
"gate-graph",
|
|
17
|
+
"gate-install",
|
|
18
|
+
"gate-hooks",
|
|
19
|
+
"gate-scope",
|
|
20
|
+
"gate-broker",
|
|
21
|
+
"gate-gateway",
|
|
22
|
+
"gate-commit",
|
|
23
|
+
"gate-push",
|
|
24
|
+
"gate-close",
|
|
25
|
+
"gate-output",
|
|
26
|
+
]);
|
|
27
|
+
const diagnostics = new WeakMap();
|
|
28
|
+
const signals = new Set([
|
|
29
|
+
"SIGTERM",
|
|
30
|
+
"SIGINT",
|
|
31
|
+
"SIGKILL",
|
|
32
|
+
"SIGHUP",
|
|
33
|
+
"SIGQUIT",
|
|
34
|
+
"SIGABRT",
|
|
35
|
+
"SIGPIPE",
|
|
36
|
+
"SIGSEGV",
|
|
37
|
+
"SIGBUS",
|
|
38
|
+
"SIGILL",
|
|
39
|
+
"SIGTRAP",
|
|
40
|
+
"SIGFPE",
|
|
41
|
+
]);
|
|
42
|
+
const errnos = new Set([
|
|
43
|
+
"EACCES",
|
|
44
|
+
"ENOENT",
|
|
45
|
+
"ENOEXEC",
|
|
46
|
+
"EMFILE",
|
|
47
|
+
"ENFILE",
|
|
48
|
+
"EAGAIN",
|
|
49
|
+
"EIO",
|
|
50
|
+
"EPIPE",
|
|
51
|
+
]);
|
|
52
|
+
const categories = new Set([
|
|
53
|
+
"exit",
|
|
54
|
+
"signal",
|
|
55
|
+
"cancelled",
|
|
56
|
+
"deadline",
|
|
57
|
+
"output-bound",
|
|
58
|
+
"input-failed",
|
|
59
|
+
"spawn-error",
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
/** Nested stages preserve the innermost observed boundary and the exact original thrown value. */
|
|
63
|
+
export async function withStage(stage, operation) {
|
|
64
|
+
required(stages.has(stage), "invalid updater diagnostic stage");
|
|
65
|
+
try {
|
|
66
|
+
return await operation();
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (
|
|
69
|
+
error !== null &&
|
|
70
|
+
(typeof error === "object" || typeof error === "function")
|
|
71
|
+
) {
|
|
72
|
+
const prior = diagnostics.get(error) ?? {};
|
|
73
|
+
diagnostics.set(error, { ...prior, stage: prior.stage ?? stage });
|
|
74
|
+
}
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Only native capture registers observed close facts; exception properties never establish provenance. */
|
|
80
|
+
export function recordNativeFailure(error, category, status, signal, errno) {
|
|
81
|
+
diagnostics.set(error, {
|
|
82
|
+
...diagnostics.get(error),
|
|
83
|
+
native: categories.has(category) ? category : "unknown",
|
|
84
|
+
status:
|
|
85
|
+
Number.isInteger(status) && status >= 0 && status <= 255 ? status : null,
|
|
86
|
+
signal: signals.has(signal) ? signal : null,
|
|
87
|
+
errno: errnos.has(errno) ? errno : null,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Never inspect arbitrary messages, stacks, getters, commands, paths or captured child output. */
|
|
92
|
+
export function publicFailure(error) {
|
|
93
|
+
const facts = diagnostics.get(error);
|
|
94
|
+
const parts = [`stage=${facts?.stage ?? "unknown"}`];
|
|
95
|
+
if (facts?.native) {
|
|
96
|
+
parts.push(`native=${facts.native}`, `status=${facts.status ?? "null"}`);
|
|
97
|
+
if (facts.signal) parts.push(`signal=${facts.signal}`);
|
|
98
|
+
if (facts.errno) parts.push(`errno=${facts.errno}`);
|
|
99
|
+
}
|
|
100
|
+
return `npm updater failed: required policy, authorization, installation, gate or exact publication evidence was unavailable (${parts.join(" ")})`;
|
|
101
|
+
}
|
|
5
102
|
|
|
6
103
|
/** Only trusted static boundary reasons may leave a phase as a public diagnostic. */
|
|
7
104
|
export class UpdaterError extends Error {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { required } from "./npm-update-contract.mjs";
|
|
8
|
+
import { recordNativeFailure } from "./npm-update-invariants.mjs";
|
|
8
9
|
|
|
9
10
|
/** The complete supervisor vector, including its control overhead, is bounded literal data. */
|
|
10
11
|
function supervisedArguments(vector) {
|
|
@@ -104,27 +105,37 @@ function captureProcess(
|
|
|
104
105
|
const errors = [];
|
|
105
106
|
let size = 0;
|
|
106
107
|
let failure;
|
|
108
|
+
let category;
|
|
109
|
+
let errno;
|
|
107
110
|
let closed = false;
|
|
108
|
-
const stop = reason => {
|
|
109
|
-
|
|
111
|
+
const stop = (reason, kind) => {
|
|
112
|
+
if (!failure) {
|
|
113
|
+
failure = new Error(`npm updater: ${reason}`);
|
|
114
|
+
category = kind;
|
|
115
|
+
}
|
|
110
116
|
if (!closed && child.pid) child.kill("SIGTERM");
|
|
111
117
|
};
|
|
112
|
-
const abort = () => stop("child was cancelled");
|
|
118
|
+
const abort = () => stop("child was cancelled", "cancelled");
|
|
113
119
|
abortSignal?.addEventListener("abort", abort, { once: true });
|
|
114
120
|
if (abortSignal?.aborted) abort();
|
|
115
|
-
const timer = setTimeout(
|
|
121
|
+
const timer = setTimeout(
|
|
122
|
+
() => stop("child exceeded deadline", "deadline"),
|
|
123
|
+
timeout
|
|
124
|
+
);
|
|
116
125
|
const collect = target => data => {
|
|
117
126
|
size += data.length;
|
|
118
|
-
if (size > maximum) stop("child output exceeded bound");
|
|
127
|
+
if (size > maximum) stop("child output exceeded bound", "output-bound");
|
|
119
128
|
else target.push(data);
|
|
120
129
|
};
|
|
121
130
|
child.stdout.on("data", collect(chunks));
|
|
122
131
|
child.stderr.on("data", collect(errors));
|
|
123
132
|
child.on("error", error => {
|
|
124
133
|
failure = error;
|
|
134
|
+
category = "spawn-error";
|
|
135
|
+
errno = error.code;
|
|
125
136
|
});
|
|
126
137
|
child.stdin.on("error", error => {
|
|
127
|
-
if (error.code !== "EPIPE") stop("child input failed");
|
|
138
|
+
if (error.code !== "EPIPE") stop("child input failed", "input-failed");
|
|
128
139
|
});
|
|
129
140
|
child.stdin.end(input);
|
|
130
141
|
child.on("close", (code, signal) => {
|
|
@@ -134,6 +145,13 @@ function captureProcess(
|
|
|
134
145
|
if (failure || signal || !allowed.includes(code)) {
|
|
135
146
|
const error =
|
|
136
147
|
failure ?? new Error(`npm updater: child failed (${code ?? signal})`);
|
|
148
|
+
recordNativeFailure(
|
|
149
|
+
error,
|
|
150
|
+
category ?? (signal ? "signal" : "exit"),
|
|
151
|
+
code,
|
|
152
|
+
signal,
|
|
153
|
+
errno
|
|
154
|
+
);
|
|
137
155
|
reject(
|
|
138
156
|
Object.assign(error, {
|
|
139
157
|
stdout: Buffer.concat(chunks),
|
|
@@ -17,7 +17,6 @@ import { descriptorFor } from "./npm-update-gate.mjs";
|
|
|
17
17
|
import { assertGateTransport } from "./npm-update-gate-hooks.mjs";
|
|
18
18
|
import {
|
|
19
19
|
publicationBody,
|
|
20
|
-
assertPublicationPolicy,
|
|
21
20
|
publicationBacklink,
|
|
22
21
|
publicationProof,
|
|
23
22
|
} from "./npm-update-publication.mjs";
|
|
@@ -188,9 +187,6 @@ export async function publishProposal({
|
|
|
188
187
|
return issue;
|
|
189
188
|
};
|
|
190
189
|
await authorize();
|
|
191
|
-
assertPublicationPolicy(
|
|
192
|
-
await api.request(`repos/${policy.repository}/actions/permissions/workflow`)
|
|
193
|
-
);
|
|
194
190
|
await publicationObjects(
|
|
195
191
|
cwd,
|
|
196
192
|
proposal,
|
|
@@ -11,11 +11,11 @@ import { join, resolve } from "node:path";
|
|
|
11
11
|
import { invokedAsScript } from "./lib/invoked-as-script.mjs";
|
|
12
12
|
import {
|
|
13
13
|
required,
|
|
14
|
-
UpdaterError,
|
|
15
14
|
keys,
|
|
16
15
|
validatePolicy,
|
|
17
16
|
validateProposal,
|
|
18
17
|
} from "./lib/npm-update-contract.mjs";
|
|
18
|
+
import { publicFailure, withStage } from "./lib/npm-update-invariants.mjs";
|
|
19
19
|
import {
|
|
20
20
|
readJson,
|
|
21
21
|
writeJson,
|
|
@@ -119,6 +119,32 @@ async function publishPhase(
|
|
|
119
119
|
process.stdout.write(`${result.status} ${result.url}\n`);
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
/** Gate input/output classification preserves the original orchestration and authority order. */
|
|
123
|
+
async function gatePhase(
|
|
124
|
+
cwd,
|
|
125
|
+
output,
|
|
126
|
+
proposal,
|
|
127
|
+
policy,
|
|
128
|
+
config,
|
|
129
|
+
allocation,
|
|
130
|
+
preview
|
|
131
|
+
) {
|
|
132
|
+
const result = await gateProposal({
|
|
133
|
+
cwd,
|
|
134
|
+
proposal,
|
|
135
|
+
policy,
|
|
136
|
+
allocation,
|
|
137
|
+
preview,
|
|
138
|
+
...(await withStage("gate-input", () => readGateProof(output))),
|
|
139
|
+
token: process.env.GH_TOKEN,
|
|
140
|
+
config,
|
|
141
|
+
});
|
|
142
|
+
await withStage("gate-output", () =>
|
|
143
|
+
writeJson(join(output, "gated.json"), result)
|
|
144
|
+
);
|
|
145
|
+
process.stdout.write(`gated ${result.receipt.commit}\n`);
|
|
146
|
+
}
|
|
147
|
+
|
|
122
148
|
/** Strict phase and fixed directory arguments are data, never a caller recipe. */
|
|
123
149
|
export async function main(argv = process.argv.slice(2)) {
|
|
124
150
|
required(
|
|
@@ -131,7 +157,9 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
131
157
|
const [phase, checkout, directory] = argv;
|
|
132
158
|
const cwd = resolve(checkout);
|
|
133
159
|
const output = resolve(directory);
|
|
134
|
-
const { config, policy } = await configuration(
|
|
160
|
+
const { config, policy } = await withStage("configuration", () =>
|
|
161
|
+
configuration(cwd)
|
|
162
|
+
);
|
|
135
163
|
phaseDirectory(output);
|
|
136
164
|
if (phase === "prepare") {
|
|
137
165
|
const result = await prepareUpdate({ cwd, policy, config });
|
|
@@ -168,21 +196,16 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
168
196
|
process.stdout.write(`${result.mode} checkpoint complete\n`);
|
|
169
197
|
return;
|
|
170
198
|
}
|
|
171
|
-
if (phase === "gate")
|
|
172
|
-
|
|
199
|
+
if (phase === "gate")
|
|
200
|
+
return gatePhase(
|
|
173
201
|
cwd,
|
|
202
|
+
output,
|
|
174
203
|
proposal,
|
|
175
204
|
policy,
|
|
176
|
-
allocation,
|
|
177
|
-
preview,
|
|
178
|
-
...readGateProof(output),
|
|
179
|
-
token: process.env.GH_TOKEN,
|
|
180
205
|
config,
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
206
|
+
allocation,
|
|
207
|
+
preview
|
|
208
|
+
);
|
|
186
209
|
return publishPhase(
|
|
187
210
|
cwd,
|
|
188
211
|
output,
|
|
@@ -197,8 +220,6 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
197
220
|
if (invokedAsScript(import.meta.url))
|
|
198
221
|
main().catch(error => {
|
|
199
222
|
// Never print candidate/provider payloads or child output that may contain credentials.
|
|
200
|
-
process.stderr.write(
|
|
201
|
-
`${error instanceof UpdaterError ? error.message : "npm updater failed: required policy, authorization, installation, gate or exact publication evidence was unavailable"}\n`
|
|
202
|
-
);
|
|
223
|
+
process.stderr.write(`${publicFailure(error)}\n`);
|
|
203
224
|
process.exitCode = 1;
|
|
204
225
|
});
|