@codyswann/lisa 4.72.6 → 4.73.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.
Files changed (93) hide show
  1. package/all/copy-overwrite/scripts/lib/automation-provenance-contract.mjs +19 -0
  2. package/all/copy-overwrite/scripts/lib/automation-provenance-local.mjs +4 -2
  3. package/all/copy-overwrite/scripts/lib/history-secret-evidence-shape.mjs +398 -6
  4. package/all/copy-overwrite/scripts/lib/history-secret-evidence.mjs +175 -7
  5. package/all/copy-overwrite/scripts/lib/npm-update-cancel-origin.mjs +7 -1
  6. package/all/copy-overwrite/scripts/lib/npm-update-contract.mjs +11 -66
  7. package/all/copy-overwrite/scripts/lib/npm-update-gate.mjs +3 -0
  8. package/all/copy-overwrite/scripts/lib/npm-update-helper-inventory.mjs +10 -0
  9. package/all/copy-overwrite/scripts/lib/npm-update-hosted-gate.mjs +162 -121
  10. package/all/copy-overwrite/scripts/lib/npm-update-hosted-hook.mjs +8 -0
  11. package/all/copy-overwrite/scripts/lib/npm-update-hosted-scope.mjs +177 -0
  12. package/all/copy-overwrite/scripts/lib/npm-update-policy.mjs +73 -0
  13. package/all/copy-overwrite/scripts/lib/npm-update-publish.mjs +1 -0
  14. package/all/copy-overwrite/scripts/lib/npm-update-rails-mysql-daemon.mjs +227 -0
  15. package/all/copy-overwrite/scripts/lib/npm-update-rails-mysql-resource.mjs +176 -0
  16. package/all/copy-overwrite/scripts/lib/npm-update-rails-mysql-storage.mjs +201 -0
  17. package/all/copy-overwrite/scripts/lib/npm-update-rails-mysql.mjs +220 -0
  18. package/all/copy-overwrite/scripts/lib/npm-update-rails-runtime-contract.mjs +75 -0
  19. package/all/copy-overwrite/scripts/lib/npm-update-rails-tool-downloads.mjs +187 -0
  20. package/all/copy-overwrite/scripts/lib/npm-update-rails-tool-identity.mjs +288 -0
  21. package/all/copy-overwrite/scripts/lib/npm-update-rails-tools.mjs +292 -0
  22. package/all/copy-overwrite/scripts/lisa-automation-provenance.mjs +3 -0
  23. package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +1 -1
  24. package/all/copy-overwrite/scripts/lisa-scratch-run.sh +7 -4
  25. package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +148 -20
  26. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  27. package/dist/core/lisa-owned-hash-ledger.js +126 -0
  28. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  29. package/dist/core/lisa.d.ts.map +1 -1
  30. package/dist/core/lisa.js +2 -1
  31. package/dist/core/lisa.js.map +1 -1
  32. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  33. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  34. package/dist/core/upstream-evidence-manifest.js +79 -17
  35. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  36. package/dist/opencode/plugin-templates/parity-safety-net.sh +1 -1
  37. package/package.json +4 -4
  38. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  40. package/plugins/lisa/hooks/parity-safety-net.sh +1 -1
  41. package/plugins/lisa-agy/hooks/parity-safety-net.sh +1 -1
  42. package/plugins/lisa-agy/plugin.json +1 -1
  43. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  45. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  46. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-copilot/hooks/parity-safety-net.sh +1 -1
  50. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +1 -1
  52. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  54. package/plugins/lisa-expo-agy/plugin.json +1 -1
  55. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  59. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  60. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  64. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  65. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  69. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  70. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  74. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  75. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  79. package/plugins/lisa-rails-agy/plugin.json +1 -1
  80. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  81. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  83. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  84. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  85. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  86. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  89. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  90. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  91. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  92. package/plugins/src/base/hooks/parity-safety-net.sh +1 -1
  93. package/scripts/two-channel-couplings.json +1 -1
@@ -2,24 +2,30 @@
2
2
  // Do not edit directly — durable changes belong upstream in Lisa.
3
3
 
4
4
  /** Supported disposable Actions Linux gates use original native tools; publisher authority stays in another job. */
5
- import { existsSync, readFileSync, mkdirSync, writeFileSync } from "node:fs";
6
- import { join, resolve } from "node:path";
7
- import { fileURLToPath } from "node:url";
5
+ import { existsSync, readFileSync } from "node:fs";
6
+ import { join } from "node:path";
8
7
  import { required, proposalFileNames } from "./npm-update-contract.mjs";
9
8
  import { qualifiedBun, frozenBun } from "./npm-update-bun.mjs";
10
- import { runProcess, writeJson } from "./npm-update-process.mjs";
9
+ import { runProcess } from "./npm-update-process.mjs";
11
10
  import { qualifiedControllerGraph } from "./npm-update-helper.mjs";
11
+ import { controllerTools } from "./npm-update-controller-factory.mjs";
12
12
  import {
13
- controllerTools,
14
- controllerSubject,
15
- } from "./npm-update-controller-factory.mjs";
16
- import { startHookReadBroker } from "./npm-update-hook-provider.mjs";
13
+ openHostedReadScope,
14
+ authenticateHostedRuntime,
15
+ } from "./npm-update-hosted-scope.mjs";
16
+ import { affirmRuntimeProfile } from "./npm-update-rails-runtime-contract.mjs";
17
+ import { prepareRailsTools } from "./npm-update-rails-tools.mjs";
18
+ import { openRailsMysqlRuntime } from "./npm-update-rails-mysql.mjs";
17
19
  import { installOriginalManager } from "./npm-update-hook-installation.mjs";
18
20
  import { withStage } from "./npm-update-invariants.mjs";
19
- import {
20
- PROPOSAL_PREDICATE,
21
- RECOVERY_PREDICATE,
22
- } from "./github-attestation-verifier.mjs";
21
+
22
+ const INSTALL_STAGE = "gate-install";
23
+ const VALIDATE_STAGE = "gate-validate";
24
+
25
+ /** Only prepared browser hooks use a short supervisor base; candidate HOME and TMPDIR stay private. */
26
+ export function originalHookEnvironment(env, runtime) {
27
+ return runtime?.browser ? { ...env, LISA_SCRATCH_BASE: "/tmp" } : env;
28
+ }
23
29
 
24
30
  /** This diagnoses the supported caller; attestation/provider validation independently establishes actual authority. */
25
31
  export function assertHostedGate(
@@ -111,88 +117,113 @@ async function installHostedDependencies(context, root, env) {
111
117
  return installed;
112
118
  }
113
119
 
114
- /** Proof paths are the actual fixed slots installed for the unchanged canonical verifier. */
115
- async function proofScope(context, env) {
116
- const result = await nativeStep(context, env, "/usr/bin/git", [
117
- "rev-parse",
118
- "--git-common-dir",
119
- ]);
120
- const text = result.stdout.toString();
121
- required(
122
- text.endsWith("\n") && !/[\n\r\0]/.test(text.slice(0, -1)),
123
- "original Git control path is unsupported"
120
+ /** Authenticate before tools; keep frozen application setup separate from hook/tool authority. */
121
+ export async function prepareRailsApplication(
122
+ context,
123
+ root,
124
+ env,
125
+ scope,
126
+ profile
127
+ ) {
128
+ await withStage(VALIDATE_STAGE, () =>
129
+ authenticateHostedRuntime(context, root, scope, nativeStep)
124
130
  );
125
- const control = resolve(
126
- context.cwd,
127
- text.slice(0, -1),
128
- "lisa/automation-provenance"
131
+ const tools = await withStage("gate-tools", () =>
132
+ prepareRailsTools(context, root, env, profile)
129
133
  );
130
- const policy = context.config.automationProvenance;
131
- const record = (file, bundle, predicate) => ({
132
- file: join(control, file),
133
- bundle: join(control, bundle),
134
- predicate,
135
- signerWorkflow: policy.signerWorkflow,
136
- signerDigest: policy.signerDigest,
137
- });
138
- const proofs = [record("descriptor.json", "bundle.json", PROPOSAL_PREDICATE)];
139
- if (context.recovery !== undefined)
140
- proofs.push(
141
- record("recovery.json", "recovery-bundle.json", RECOVERY_PREDICATE)
142
- );
143
- const recoveryDescriptor =
144
- context.recovery === undefined
145
- ? undefined
146
- : JSON.parse(context.recovery.toString());
147
- return { proofs, recoveryDescriptor };
148
- }
149
-
150
- /** PATH preserves original native commands; the sole Node gateway carries no provider token. */
151
- function nodeGateway(root, context) {
152
- const bin = join(root, "hook-bin");
153
- mkdirSync(bin, { mode: 0o700 });
154
- const entry = fileURLToPath(
155
- new URL("./npm-update-hosted-hook.mjs", import.meta.url)
134
+ const installed = await withStage(INSTALL_STAGE, () =>
135
+ installHostedDependencies(context, root, tools.env)
156
136
  );
157
- const quote = text => {
158
- required(!/[\0\n\r]/.test(text), "unsupported hosted gateway path");
159
- return `'${text.replaceAll("'", "'\\''")}'`;
160
- };
161
- const file = join(root, "hosted-hooks.json");
162
- writeJson(file, context);
163
- writeFileSync(
164
- join(bin, "node"),
165
- `#!/bin/sh\nexec ${quote(context.native.node.path)} ${quote(entry)} --context ${quote(file)} -- "$@"\n`,
166
- { flag: "wx", mode: 0o700 }
137
+ const application = Object.fromEntries(
138
+ [
139
+ "PATH",
140
+ "HOME",
141
+ "LANG",
142
+ "LC_ALL",
143
+ "TZ",
144
+ "TMPDIR",
145
+ "BUNDLE_PATH",
146
+ "BUNDLE_APP_CONFIG",
147
+ "BUNDLE_FROZEN",
148
+ "BUNDLE_SILENCE_ROOT_WARNING",
149
+ "BUNDLER_VERSION",
150
+ ]
151
+ .filter(name => installed[name] !== undefined)
152
+ .map(name => [name, installed[name]])
167
153
  );
168
- return bin;
154
+ const runtime = await withStage(INSTALL_STAGE, () =>
155
+ openRailsMysqlRuntime(
156
+ {
157
+ cwd: context.cwd,
158
+ root,
159
+ deadline: context.deadline,
160
+ profile,
161
+ docker: tools.docker,
162
+ },
163
+ application
164
+ )
165
+ );
166
+ try {
167
+ await withStage(INSTALL_STAGE, () => runtime.prepareSchemas());
168
+ const databases = Object.fromEntries(
169
+ [
170
+ "DATABASE_NAME",
171
+ "DATABASE_USER",
172
+ "DATABASE_PASSWORD",
173
+ "DATABASE_PORT",
174
+ "PRIMARY_DB_HOST",
175
+ "DATABASE_REPLICA_HOST",
176
+ "DATABASE_SSL",
177
+ "DATABASE_IAM_AUTH",
178
+ "RAILS_ENV",
179
+ "RACK_ENV",
180
+ ].map(name => [name, runtime.env[name]])
181
+ );
182
+ required(
183
+ Object.values(databases).every(value => typeof value === "string"),
184
+ "prepared database environment differs"
185
+ );
186
+ return { env: { ...installed, ...databases }, close: runtime.close };
187
+ } catch (error) {
188
+ try {
189
+ await runtime.close();
190
+ } catch (cleanup) {
191
+ throw new AggregateError(
192
+ [error, cleanup],
193
+ "hosted schema preparation and cleanup failed",
194
+ { cause: error }
195
+ );
196
+ }
197
+ throw error;
198
+ }
169
199
  }
170
200
 
171
- /** The unchanged read-only subject is constructed separately so diagnostic wrapping stays within function budgets. */
172
- function hookProfile(context, root, native, scope) {
173
- return {
174
- version: 1,
175
- deadline: context.deadline,
176
- cwd: context.cwd,
177
- home: root,
178
- nativeGh: native.gh,
179
- invocation: {
180
- entry: join(context.cwd, "scripts/lisa-npm-updater.mjs"),
181
- args: [join(context.cwd, "scripts/lisa-npm-updater.mjs"), "gate"],
182
- cwd: context.cwd,
183
- },
184
- subject: controllerSubject(
185
- { ...context, recoveryDescriptor: scope.recoveryDescriptor },
186
- "hook-read",
187
- null,
188
- scope.proofs
189
- ),
190
- };
201
+ /** Attempt every owned close within its original deadline, preserving all actual failures. */
202
+ async function closeHostedResources(...resources) {
203
+ const errors = [];
204
+ for (const resource of resources) {
205
+ if (!resource) continue;
206
+ try {
207
+ await resource.close();
208
+ } catch (error) {
209
+ errors.push(error);
210
+ }
211
+ }
212
+ if (errors.length === 1) throw errors[0];
213
+ if (errors.length)
214
+ throw new AggregateError(errors, "hosted resources cleanup failed");
191
215
  }
192
216
 
193
217
  /** A separate read-only capability is available during original gates; it cannot acquire publisher/issuer permissions. */
194
218
  export async function createHostedGate(context, root, env) {
195
- await withStage("gate-validate", () => assertHostedGate(context));
219
+ await withStage(VALIDATE_STAGE, () => assertHostedGate(context));
220
+ const runtime = await withStage(VALIDATE_STAGE, () =>
221
+ affirmRuntimeProfile(
222
+ context.proposal,
223
+ context.policy,
224
+ process.env.LISA_NPM_RUNTIME_PROFILE ?? "none"
225
+ )
226
+ );
196
227
  const native = await withStage("gate-tools", () =>
197
228
  controllerTools(context.config.automationProvenance)
198
229
  );
@@ -205,51 +236,61 @@ export async function createHostedGate(context, root, env) {
205
236
  "lib/npm-update-hook-preload.mjs",
206
237
  ])
207
238
  );
208
- const installed = await withStage("gate-install", () =>
209
- installHostedDependencies(context, root, env)
210
- );
211
- const installation = await withStage("gate-hooks", () =>
212
- installOriginalManager(context, installed, nativeStep)
213
- );
214
- const scope = await withStage("gate-scope", () =>
215
- proofScope(context, installed)
216
- );
217
- const profile = await withStage("gate-scope", () => {
218
- mkdirSync(join(root, "gh-config"), { mode: 0o700 });
219
- return hookProfile(context, root, native, scope);
220
- });
221
- const broker = await withStage("gate-broker", () =>
222
- startHookReadBroker(profile, context.token)
223
- );
239
+ let scope;
240
+ let application;
224
241
  try {
225
- const bin = await withStage("gate-gateway", () =>
226
- nodeGateway(root, {
227
- version: 1,
242
+ if (runtime) {
243
+ scope = await openHostedReadScope(
244
+ context,
228
245
  root,
229
- cwd: context.cwd,
230
- graph,
246
+ env,
231
247
  native,
232
- reader: { root, path: broker.path, deadline: context.deadline },
233
- })
248
+ graph,
249
+ nativeStep
250
+ );
251
+ application = await prepareRailsApplication(
252
+ context,
253
+ root,
254
+ env,
255
+ scope,
256
+ runtime
257
+ );
258
+ }
259
+ const installed =
260
+ application?.env ??
261
+ (await withStage(INSTALL_STAGE, () =>
262
+ installHostedDependencies(context, root, env)
263
+ ));
264
+ const installation = await withStage("gate-hooks", () =>
265
+ installOriginalManager(context, installed, nativeStep)
266
+ );
267
+ scope ??= await openHostedReadScope(
268
+ context,
269
+ root,
270
+ installed,
271
+ native,
272
+ graph,
273
+ nativeStep
234
274
  );
235
275
  return {
236
276
  installation,
237
- env: {
238
- ...installed,
239
- PATH: `${bin}:${installed.PATH}`,
240
- CI: "true",
241
- GITHUB_ACTIONS: "true",
242
- GITHUB_REPOSITORY: context.policy.repository,
243
- GITHUB_RUN_ID: process.env.GITHUB_RUN_ID,
244
- GITHUB_RUN_ATTEMPT: process.env.GITHUB_RUN_ATTEMPT,
245
- GITHUB_SHA: context.proposal.parent,
246
- GITHUB_REF: "refs/heads/main",
247
- GITHUB_EVENT_NAME: process.env.GITHUB_EVENT_NAME,
248
- },
249
- close: broker.close,
277
+ env: originalHookEnvironment(scope.environment(installed), runtime),
278
+ close: () => closeHostedResources(application, scope),
250
279
  };
251
280
  } catch (error) {
252
- await withStage("gate-close", () => broker.close());
281
+ if (scope) {
282
+ try {
283
+ await withStage("gate-close", () =>
284
+ closeHostedResources(application, scope)
285
+ );
286
+ } catch (cleanup) {
287
+ throw new AggregateError(
288
+ [error, cleanup],
289
+ "hosted preparation and scope cleanup failed",
290
+ { cause: error }
291
+ );
292
+ }
293
+ }
253
294
  throw error;
254
295
  }
255
296
  }
@@ -83,6 +83,14 @@ export function hookReadSlot(entry, args, role) {
83
83
  return `${role}-work-item`;
84
84
  }
85
85
  if (name === "lisa-automation-provenance.mjs") {
86
+ if (
87
+ role === "commit" &&
88
+ args.length === 2 &&
89
+ typeof args[1] === "string" &&
90
+ args[1].length > 0 &&
91
+ !args[1].startsWith("-")
92
+ )
93
+ return "commit-provenance";
86
94
  if (args[1] === "check-commit") return "commit-provenance";
87
95
  if (args[1] === "check-push" && ["audit", "destination"].includes(role))
88
96
  return `${role}-provenance`;
@@ -0,0 +1,177 @@
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
+ /** Early signed verification uses the original read-only broker, never application credentials. */
5
+ import { mkdirSync, writeFileSync } from "node:fs";
6
+ import { join, resolve } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { required } from "./npm-update-contract.mjs";
9
+ import { withStage } from "./npm-update-invariants.mjs";
10
+ import { writeJson } from "./npm-update-process.mjs";
11
+ import { controllerSubject } from "./npm-update-controller-factory.mjs";
12
+ import { startHookReadBroker } from "./npm-update-hook-provider.mjs";
13
+ import {
14
+ PROPOSAL_PREDICATE,
15
+ RECOVERY_PREDICATE,
16
+ } from "./github-attestation-verifier.mjs";
17
+
18
+ /** Proof paths remain the fixed slots installed for the unchanged canonical verifier. */
19
+ async function proofScope(context, env, step) {
20
+ const result = await step(context, env, "/usr/bin/git", [
21
+ "rev-parse",
22
+ "--git-common-dir",
23
+ ]);
24
+ const text = result.stdout.toString();
25
+ required(
26
+ text.endsWith("\n") && !/[\n\r\0]/.test(text.slice(0, -1)),
27
+ "original Git control path is unsupported"
28
+ );
29
+ const control = resolve(
30
+ context.cwd,
31
+ text.slice(0, -1),
32
+ "lisa/automation-provenance"
33
+ );
34
+ const policy = context.config.automationProvenance;
35
+ const record = (file, bundle, predicate) => ({
36
+ file: join(control, file),
37
+ bundle: join(control, bundle),
38
+ predicate,
39
+ signerWorkflow: policy.signerWorkflow,
40
+ signerDigest: policy.signerDigest,
41
+ });
42
+ const proofs = [record("descriptor.json", "bundle.json", PROPOSAL_PREDICATE)];
43
+ if (context.recovery !== undefined)
44
+ proofs.push(
45
+ record("recovery.json", "recovery-bundle.json", RECOVERY_PREDICATE)
46
+ );
47
+ const recoveryDescriptor =
48
+ context.recovery === undefined
49
+ ? undefined
50
+ : JSON.parse(context.recovery.toString());
51
+ return { proofs, recoveryDescriptor };
52
+ }
53
+
54
+ /** PATH preserves original commands; the fixed Node gateway carries no provider token. */
55
+ function nodeGateway(root, context) {
56
+ const bin = join(root, "hook-bin");
57
+ mkdirSync(bin, { mode: 0o700 });
58
+ const entry = fileURLToPath(
59
+ new URL("./npm-update-hosted-hook.mjs", import.meta.url)
60
+ );
61
+ const quote = text => {
62
+ required(!/[\0\n\r]/.test(text), "unsupported hosted gateway path");
63
+ return `'${text.replaceAll("'", "'\\''")}'`;
64
+ };
65
+ const file = join(root, "hosted-hooks.json");
66
+ writeJson(file, context);
67
+ writeFileSync(
68
+ join(bin, "node"),
69
+ `#!/bin/sh\nexec ${quote(context.native.node.path)} ${quote(entry)} --context ${quote(file)} -- "$@"\n`,
70
+ { flag: "wx", mode: 0o700 }
71
+ );
72
+ return bin;
73
+ }
74
+
75
+ /** Scope construction uses the same original allocation and proof subject as ordinary hooks. */
76
+ function hookProfile(context, root, native, scope) {
77
+ return {
78
+ version: 1,
79
+ deadline: context.deadline,
80
+ cwd: context.cwd,
81
+ home: root,
82
+ nativeGh: native.gh,
83
+ invocation: {
84
+ entry: join(context.cwd, "scripts/lisa-npm-updater.mjs"),
85
+ args: [join(context.cwd, "scripts/lisa-npm-updater.mjs"), "gate"],
86
+ cwd: context.cwd,
87
+ },
88
+ subject: controllerSubject(
89
+ { ...context, recoveryDescriptor: scope.recoveryDescriptor },
90
+ "hook-read",
91
+ null,
92
+ scope.proofs
93
+ ),
94
+ };
95
+ }
96
+
97
+ /** Exit ten means no proof; only the real signed verifier's zero permits runtime side effects. */
98
+ export async function authenticateHostedRuntime(context, root, scope, step) {
99
+ const result = await step(
100
+ context,
101
+ { ...scope.env, LISA_NPM_HOOK_ROLE: "commit" },
102
+ "node",
103
+ ["scripts/lisa-automation-provenance.mjs", join(root, "message")]
104
+ );
105
+ required(result.code === 0, "canonical runtime proof was not authenticated");
106
+ }
107
+
108
+ /** Retain qualified install paths while adding only the existing gateway and hosted identity. */
109
+ function hostedEnvironment(context, root, bin, installed) {
110
+ required(
111
+ installed.HOME === root && typeof installed.PATH === "string",
112
+ "installed hosted environment differs"
113
+ );
114
+ return {
115
+ ...installed,
116
+ PATH: `${bin}:${installed.PATH}`,
117
+ CI: "true",
118
+ GITHUB_ACTIONS: "true",
119
+ GITHUB_REPOSITORY: context.policy.repository,
120
+ GITHUB_RUN_ID: process.env.GITHUB_RUN_ID,
121
+ GITHUB_RUN_ATTEMPT: process.env.GITHUB_RUN_ATTEMPT,
122
+ GITHUB_SHA: context.proposal.parent,
123
+ GITHUB_REF: "refs/heads/main",
124
+ GITHUB_EVENT_NAME: process.env.GITHUB_EVENT_NAME,
125
+ };
126
+ }
127
+
128
+ /** Gateway publication failures positively close an already allocated original listener. */
129
+ export async function openHostedReadScope(
130
+ context,
131
+ root,
132
+ env,
133
+ native,
134
+ graph,
135
+ step
136
+ ) {
137
+ const scope = await withStage("gate-scope", () =>
138
+ proofScope(context, env, step)
139
+ );
140
+ const profile = await withStage("gate-scope", () => {
141
+ mkdirSync(join(root, "gh-config"), { mode: 0o700 });
142
+ return hookProfile(context, root, native, scope);
143
+ });
144
+ const broker = await withStage("gate-broker", () =>
145
+ startHookReadBroker(profile, context.token)
146
+ );
147
+ try {
148
+ const bin = await withStage("gate-gateway", () =>
149
+ nodeGateway(root, {
150
+ version: 1,
151
+ root,
152
+ cwd: context.cwd,
153
+ graph,
154
+ native,
155
+ reader: { root, path: broker.path, deadline: context.deadline },
156
+ })
157
+ );
158
+ const environment = installed =>
159
+ hostedEnvironment(context, root, bin, installed);
160
+ return {
161
+ env: environment(env),
162
+ environment,
163
+ close: broker.close,
164
+ };
165
+ } catch (error) {
166
+ try {
167
+ await withStage("gate-close", () => broker.close());
168
+ } catch (cleanup) {
169
+ throw new AggregateError(
170
+ [error, cleanup],
171
+ "hosted gateway publication and scope cleanup failed",
172
+ { cause: error }
173
+ );
174
+ }
175
+ throw error;
176
+ }
177
+ }
@@ -0,0 +1,73 @@
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
+ /** Fixed npm policy validation shares the optional signed runtime contract without cycles. */
5
+ import { keys, required } from "./npm-update-invariants.mjs";
6
+ import { railsRuntime } from "./npm-update-rails-runtime-contract.mjs";
7
+
8
+ export const NAME = /^(?:@[a-z0-9][a-z0-9_.-]*\/)?[a-z0-9][a-z0-9_.-]*$/;
9
+ export const VERSION =
10
+ /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[A-Za-z0-9.-]+)?$/;
11
+
12
+ /** Configured tracking and an explicit owner are prerequisites, never inferred. */
13
+ export function validatePolicy(value, config) {
14
+ keys(value, [
15
+ "version",
16
+ "repository",
17
+ "directory",
18
+ "target",
19
+ "maintainer",
20
+ "packages",
21
+ "lisaOwner",
22
+ ...(Object.hasOwn(value ?? {}, "runtime") ? ["runtime"] : []),
23
+ ]);
24
+ railsRuntime(value);
25
+ required(
26
+ value.version === 1 && value.directory === "." && value.target === "main",
27
+ "only root npm on main is supported"
28
+ );
29
+ required(
30
+ config.tracker === "github" &&
31
+ value.repository === `${config.github?.org}/${config.github?.repo}`,
32
+ "configured repository/tracker differs"
33
+ );
34
+ required(
35
+ /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(value.repository),
36
+ "invalid repository"
37
+ );
38
+ required(
39
+ typeof value.maintainer === "string" &&
40
+ /^[A-Za-z0-9][A-Za-z0-9-]{0,38}$/.test(value.maintainer),
41
+ "explicit assignable maintainer required"
42
+ );
43
+ required(
44
+ ["absent", "verified-local-full-apply"].includes(value.lisaOwner),
45
+ "Lisa requires a verified local full-apply owner"
46
+ );
47
+ required(
48
+ Array.isArray(value.packages) &&
49
+ value.packages.length > 0 &&
50
+ value.packages.length <= 64,
51
+ "invalid package selection"
52
+ );
53
+ const names = new Set();
54
+ for (const selection of value.packages) {
55
+ keys(selection, ["name", "version"]);
56
+ required(
57
+ typeof selection.name === "string" &&
58
+ selection.name.length <= 214 &&
59
+ NAME.test(selection.name),
60
+ "invalid npm name"
61
+ );
62
+ required(
63
+ typeof selection.version === "string" && VERSION.test(selection.version),
64
+ "exact registry version required"
65
+ );
66
+ required(
67
+ !names.has(selection.name) && selection.name !== "@codyswann/lisa",
68
+ "duplicate selection or Lisa version-only update"
69
+ );
70
+ names.add(selection.name);
71
+ }
72
+ return structuredClone(value);
73
+ }
@@ -146,6 +146,7 @@ export async function publishProposal({
146
146
  required(
147
147
  descriptor.parent === proposal.parent &&
148
148
  descriptor.proposalKey === proposal.bindingKey &&
149
+ descriptor.runtimeSha256 === proposal.runtimeSha256 &&
149
150
  canonicalJson(descriptor.files) === canonicalJson(proposal.hashes) &&
150
151
  canonicalJson(descriptor.updates) === canonicalJson(proposal.updates) &&
151
152
  descriptor.workItem === allocation.workItem &&