@codyswann/lisa 4.71.1 → 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.
Files changed (66) hide show
  1. package/all/copy-overwrite/scripts/lib/history-secret-scanner.mjs +46 -37
  2. package/all/copy-overwrite/scripts/lib/npm-update-gate.mjs +52 -35
  3. package/all/copy-overwrite/scripts/lib/npm-update-hosted-gate.mjs +48 -26
  4. package/all/copy-overwrite/scripts/lib/npm-update-invariants.mjs +97 -0
  5. package/all/copy-overwrite/scripts/lib/npm-update-native-process.mjs +24 -6
  6. package/all/copy-overwrite/scripts/lisa-npm-updater.mjs +37 -16
  7. package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +10 -6
  8. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  9. package/dist/core/lisa-owned-hash-ledger.js +28 -0
  10. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  11. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  12. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  13. package/dist/core/upstream-evidence-manifest.js +11 -8
  14. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  15. package/package.json +4 -4
  16. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  17. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  18. package/plugins/lisa-agy/plugin.json +1 -1
  19. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  20. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  21. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  22. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  26. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  28. package/plugins/lisa-expo-agy/plugin.json +1 -1
  29. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  33. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  34. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  38. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  39. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  43. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  44. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  48. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  49. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  53. package/plugins/lisa-rails-agy/plugin.json +1 -1
  54. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  58. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  59. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  63. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  64. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  66. 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 truncates this existing file: its report keeps private mode even
248
- // when the caller's umask would otherwise create world-readable metadata.
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 result = spawnSync(
276
- scanner,
277
- [
278
- "git",
279
- source,
280
- "--config",
281
- config,
282
- "--gitleaks-ignore-path",
283
- ignore,
284
- "--ignore-gitleaks-allow",
285
- "--redact=100",
286
- "--no-banner",
287
- "--no-color",
288
- "--log-level",
289
- "error",
290
- "--platform",
291
- "none",
292
- "--exit-code",
293
- "42",
294
- "--timeout",
295
- "120",
296
- "--report-format",
297
- "json",
298
- "--report-path",
299
- report,
300
- "--log-opts",
301
- `--no-walk=unsorted --root --diff-merges=separate --no-ext-diff --no-textconv --no-renames ${group.join(" ")}`,
302
- ],
303
- {
304
- cwd: scratch,
305
- env: audit.environment,
306
- timeout: 130000,
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 executeCanonicalHelper(context, "stage-read", "link");
158
- await executeCanonicalHelper(context, "stage-read", "attach-branch");
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
- 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)
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
  }
@@ -21,6 +21,7 @@ import {
21
21
  } from "./npm-update-controller-factory.mjs";
22
22
  import { startHookReadBroker } from "./npm-update-hook-provider.mjs";
23
23
  import { originalHookInstallation } from "./npm-update-hook-installation.mjs";
24
+ import { withStage } from "./npm-update-invariants.mjs";
24
25
  import {
25
26
  sha256,
26
27
  PROPOSAL_PREDICATE,
@@ -234,22 +235,9 @@ function nodeGateway(root, context) {
234
235
  return bin;
235
236
  }
236
237
 
237
- /** A separate read-only capability is available during original gates; it cannot acquire publisher/issuer permissions. */
238
- export async function createHostedGate(context, root, env) {
239
- assertHostedGate(context);
240
- const native = controllerTools(context.config.automationProvenance);
241
- const graph = await qualifiedControllerGraph(context.cwd, context.config, [
242
- "lisa-work-item.mjs",
243
- "lisa-automation-provenance.mjs",
244
- "lisa-rails-prepush.mjs",
245
- "lib/npm-update-hosted-hook.mjs",
246
- "lib/npm-update-hook-preload.mjs",
247
- ]);
248
- const installed = await installHostedDependencies(context, root, env);
249
- const installation = await installOriginalManager(context, installed);
250
- const scope = await proofScope(context, installed);
251
- mkdirSync(join(root, "gh-config"), { mode: 0o700 });
252
- 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 {
253
241
  version: 1,
254
242
  deadline: context.deadline,
255
243
  cwd: context.cwd,
@@ -267,16 +255,50 @@ export async function createHostedGate(context, root, env) {
267
255
  scope.proofs
268
256
  ),
269
257
  };
270
- const broker = await startHookReadBroker(profile, context.token);
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
+ );
271
291
  try {
272
- const bin = nodeGateway(root, {
273
- version: 1,
274
- root,
275
- cwd: context.cwd,
276
- graph,
277
- native,
278
- reader: { root, path: broker.path, deadline: context.deadline },
279
- });
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
+ );
280
302
  return {
281
303
  installation,
282
304
  env: {
@@ -294,7 +316,7 @@ export async function createHostedGate(context, root, env) {
294
316
  close: broker.close,
295
317
  };
296
318
  } catch (error) {
297
- await broker.close();
319
+ await withStage("gate-close", () => broker.close());
298
320
  throw error;
299
321
  }
300
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
- failure ??= new Error(`npm updater: ${reason}`);
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(() => stop("child exceeded deadline"), timeout);
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),
@@ -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(cwd);
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
- const result = await gateProposal({
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
- writeJson(join(output, "gated.json"), result);
183
- process.stdout.write(`gated ${result.receipt.commit}\n`);
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
  });
@@ -142,7 +142,7 @@
142
142
  "children": []
143
143
  },
144
144
  "lib/history-secret-scanner.mjs": {
145
- "sha256": "d77fc82db53ffbfef6bd714b600ed9f8b711cf52bbf630020efa4841ae5f083c",
145
+ "sha256": "07b639188c44ed5db817fc47bcdedfdd58e700024af19d464d78497b1ee236b3",
146
146
  "staticImports": [
147
147
  "lib/history-secret-evidence.mjs",
148
148
  "lib/history-secret-git.mjs",
@@ -353,7 +353,7 @@
353
353
  "children": []
354
354
  },
355
355
  "lib/npm-update-gate.mjs": {
356
- "sha256": "86b3b29736b5abee8d3f53844084805f4e5d85ad6537488f7d2da284d2146207",
356
+ "sha256": "bb6b6b8ea1e3620ee0b1b620e50331788bc99b44f431057e12a60cd138e56bc4",
357
357
  "staticImports": [
358
358
  "lib/github-attestation-verifier.mjs",
359
359
  "lib/npm-update-contract.mjs",
@@ -362,6 +362,7 @@
362
362
  "lib/npm-update-gate-proof.mjs",
363
363
  "lib/npm-update-github.mjs",
364
364
  "lib/npm-update-hosted-gate.mjs",
365
+ "lib/npm-update-invariants.mjs",
365
366
  "lib/npm-update-prepare.mjs",
366
367
  "lib/npm-update-process.mjs",
367
368
  "lib/npm-update-publication.mjs",
@@ -499,7 +500,7 @@
499
500
  "children": []
500
501
  },
501
502
  "lib/npm-update-hosted-gate.mjs": {
502
- "sha256": "787e9424114d52f6a6c8a09a67033d69b20d66ee22e1735f856f2c2d9f20aa69",
503
+ "sha256": "b6adf8af72067f883c83e11b7465a3f4c219a6e78d98b029b1af489967ccbaa8",
503
504
  "staticImports": [
504
505
  "lib/github-attestation-verifier.mjs",
505
506
  "lib/npm-update-contract.mjs",
@@ -507,6 +508,7 @@
507
508
  "lib/npm-update-helper.mjs",
508
509
  "lib/npm-update-hook-installation.mjs",
509
510
  "lib/npm-update-hook-provider.mjs",
511
+ "lib/npm-update-invariants.mjs",
510
512
  "lib/npm-update-process.mjs",
511
513
  "node:fs",
512
514
  "node:path",
@@ -534,7 +536,7 @@
534
536
  ]
535
537
  },
536
538
  "lib/npm-update-invariants.mjs": {
537
- "sha256": "866525d6b23523dc5d45cd1ed7794e31f84be5cc636288cc05d0e54d0cee63a6",
539
+ "sha256": "e7a093ceeaca34b6e0cf2ea2eccd3b5e1882e295ec2baeeaec9da0373e669687",
538
540
  "staticImports": [],
539
541
  "dynamicImports": [],
540
542
  "children": []
@@ -573,9 +575,10 @@
573
575
  "children": []
574
576
  },
575
577
  "lib/npm-update-native-process.mjs": {
576
- "sha256": "29dc444ecb1736be991faffc7439865bcecc2cf19a81f8cefd574f9bb5a40917",
578
+ "sha256": "295d69d90f32aad9d757060ea6d3f66787a00e24c19e01e5beaf2ba752fec8da",
577
579
  "staticImports": [
578
580
  "lib/npm-update-contract.mjs",
581
+ "lib/npm-update-invariants.mjs",
579
582
  "node:child_process",
580
583
  "node:url"
581
584
  ],
@@ -950,7 +953,7 @@
950
953
  "children": []
951
954
  },
952
955
  "lisa-npm-updater.mjs": {
953
- "sha256": "43b4eb085bede37441c36fa30af3c65f397bd5862a15d4bce6d05068b7497bac",
956
+ "sha256": "0bfa51b90df7ef2e076d35312ece4b6b550e586f2f343a85d94e7dca52156809",
954
957
  "staticImports": [
955
958
  "lib/github-attestation-verifier.mjs",
956
959
  "lib/invoked-as-script.mjs",
@@ -960,6 +963,7 @@
960
963
  "lib/npm-update-gate-proof.mjs",
961
964
  "lib/npm-update-gate.mjs",
962
965
  "lib/npm-update-github.mjs",
966
+ "lib/npm-update-invariants.mjs",
963
967
  "lib/npm-update-prepare.mjs",
964
968
  "lib/npm-update-process.mjs",
965
969
  "lib/npm-update-publish.mjs",
@@ -1 +1 @@
1
- {"version":3,"file":"lisa-owned-hash-ledger.d.ts","sourceRoot":"","sources":["../../src/core/lisa-owned-hash-ledger.ts"],"names":[],"mappings":"AACA,6EAA6E;AAE7E;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAC3C,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAkvDjC,CAAC;AAEH;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,+BAA+B,EAAE,QAAQ,CACpD,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAqpDjC,CAAC"}
1
+ {"version":3,"file":"lisa-owned-hash-ledger.d.ts","sourceRoot":"","sources":["../../src/core/lisa-owned-hash-ledger.ts"],"names":[],"mappings":"AACA,6EAA6E;AAE7E;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAC3C,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAgwDjC,CAAC;AAEH;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,+BAA+B,EAAE,QAAQ,CACpD,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAmqDjC,CAAC"}