@codyswann/lisa 4.70.4 → 4.71.1

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 (139) hide show
  1. package/all/copy-overwrite/scripts/lib/automation-provenance-contract.mjs +174 -0
  2. package/all/copy-overwrite/scripts/lib/automation-provenance-local.mjs +279 -0
  3. package/all/copy-overwrite/scripts/lib/gate-failure-diagnosis.mjs +17 -7
  4. package/all/copy-overwrite/scripts/lib/github-attestation-provider.mjs +241 -0
  5. package/all/copy-overwrite/scripts/lib/github-attestation-recovery.mjs +283 -0
  6. package/all/copy-overwrite/scripts/lib/github-attestation-verifier.mjs +244 -0
  7. package/all/copy-overwrite/scripts/lib/npm-update-allocate.mjs +289 -0
  8. package/all/copy-overwrite/scripts/lib/npm-update-authorization.mjs +235 -0
  9. package/all/copy-overwrite/scripts/lib/npm-update-broker-client.mjs +92 -0
  10. package/all/copy-overwrite/scripts/lib/npm-update-checkpoint.mjs +241 -0
  11. package/all/copy-overwrite/scripts/lib/npm-update-contract.mjs +278 -0
  12. package/all/copy-overwrite/scripts/lib/npm-update-controller-broker.mjs +234 -0
  13. package/all/copy-overwrite/scripts/lib/npm-update-controller-factory.mjs +278 -0
  14. package/all/copy-overwrite/scripts/lib/npm-update-controller-recipe.mjs +135 -0
  15. package/all/copy-overwrite/scripts/lib/npm-update-execution-adapter.mjs +292 -0
  16. package/all/copy-overwrite/scripts/lib/npm-update-gate-hooks.mjs +214 -0
  17. package/all/copy-overwrite/scripts/lib/npm-update-gate-install.mjs +267 -0
  18. package/all/copy-overwrite/scripts/lib/npm-update-gate-proof.mjs +99 -0
  19. package/all/copy-overwrite/scripts/lib/npm-update-gate.mjs +300 -0
  20. package/all/copy-overwrite/scripts/lib/npm-update-gh-dispatch.mjs +182 -0
  21. package/all/copy-overwrite/scripts/lib/npm-update-gh-grants.mjs +156 -0
  22. package/all/copy-overwrite/scripts/lib/npm-update-gh-requests.mjs +221 -0
  23. package/all/copy-overwrite/scripts/lib/npm-update-github.mjs +214 -0
  24. package/all/copy-overwrite/scripts/lib/npm-update-helper-graph.mjs +238 -0
  25. package/all/copy-overwrite/scripts/lib/npm-update-helper-inventory.mjs +126 -0
  26. package/all/copy-overwrite/scripts/lib/npm-update-helper.mjs +292 -0
  27. package/all/copy-overwrite/scripts/lib/npm-update-hook-installation.mjs +116 -0
  28. package/all/copy-overwrite/scripts/lib/npm-update-hook-preload.mjs +153 -0
  29. package/all/copy-overwrite/scripts/lib/npm-update-hook-provider.mjs +207 -0
  30. package/all/copy-overwrite/scripts/lib/npm-update-hook-read-client.mjs +164 -0
  31. package/all/copy-overwrite/scripts/lib/npm-update-hosted-gate.mjs +300 -0
  32. package/all/copy-overwrite/scripts/lib/npm-update-hosted-hook.mjs +157 -0
  33. package/all/copy-overwrite/scripts/lib/npm-update-invariants.mjs +28 -0
  34. package/all/copy-overwrite/scripts/lib/npm-update-isolation.mjs +11 -0
  35. package/all/copy-overwrite/scripts/lib/npm-update-leaf-contract.mjs +171 -0
  36. package/all/copy-overwrite/scripts/lib/npm-update-leaf.mjs +194 -0
  37. package/all/copy-overwrite/scripts/lib/npm-update-native-process.mjs +155 -0
  38. package/all/copy-overwrite/scripts/lib/npm-update-npm.mjs +190 -0
  39. package/all/copy-overwrite/scripts/lib/npm-update-object.mjs +112 -0
  40. package/all/copy-overwrite/scripts/lib/npm-update-orchestrator.mjs +273 -0
  41. package/all/copy-overwrite/scripts/lib/npm-update-owner.mjs +292 -0
  42. package/all/copy-overwrite/scripts/lib/npm-update-prepare.mjs +263 -0
  43. package/all/copy-overwrite/scripts/lib/npm-update-process-core.mjs +179 -0
  44. package/all/copy-overwrite/scripts/lib/npm-update-process.mjs +16 -0
  45. package/all/copy-overwrite/scripts/lib/npm-update-publication.mjs +258 -0
  46. package/all/copy-overwrite/scripts/lib/npm-update-publish.mjs +254 -0
  47. package/all/copy-overwrite/scripts/lib/npm-update-quality.mjs +153 -0
  48. package/all/copy-overwrite/scripts/lib/npm-update-recovery.mjs +264 -0
  49. package/all/copy-overwrite/scripts/lib/npm-update-runtime-archive.mjs +204 -0
  50. package/all/copy-overwrite/scripts/lib/npm-update-runtime-graph.mjs +185 -0
  51. package/all/copy-overwrite/scripts/lib/npm-update-runtime-transport.mjs +221 -0
  52. package/all/copy-overwrite/scripts/lib/npm-update-runtime.mjs +284 -0
  53. package/all/copy-overwrite/scripts/lib/npm-update-tool-launcher.mjs +287 -0
  54. package/all/copy-overwrite/scripts/lib/npm-update-worker-environment.mjs +161 -0
  55. package/all/copy-overwrite/scripts/lib/npm-update-worker-inspection.mjs +142 -0
  56. package/all/copy-overwrite/scripts/lib/npm-update-worker-lifecycle.mjs +256 -0
  57. package/all/copy-overwrite/scripts/lib/npm-update-worker-policy.mjs +266 -0
  58. package/all/copy-overwrite/scripts/lib/process-tree-runner.mjs +230 -36
  59. package/all/copy-overwrite/scripts/lisa-automation-provenance.mjs +161 -0
  60. package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +89 -5
  61. package/all/copy-overwrite/scripts/lisa-npm-updater.mjs +204 -0
  62. package/all/copy-overwrite/scripts/lisa-run-gates.mjs +48 -123
  63. package/all/copy-overwrite/scripts/lisa-work-item.mjs +107 -41
  64. package/all/copy-overwrite/scripts/npm-updater-gate-runtime.json +1 -0
  65. package/all/copy-overwrite/scripts/npm-updater-gate-supervisor.c +121 -0
  66. package/all/copy-overwrite/scripts/npm-updater-gate.Dockerfile +49 -0
  67. package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +1085 -0
  68. package/dist/codex/scripts/block-no-verify.sh +89 -5
  69. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  70. package/dist/core/lisa-owned-hash-ledger.js +604 -0
  71. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  72. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  73. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  74. package/dist/core/upstream-evidence-manifest.js +159 -11
  75. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  76. package/dist/opencode/plugin-templates/block-no-verify.sh +89 -5
  77. package/package.json +4 -4
  78. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  80. package/plugins/lisa/hooks/block-no-verify.agy.sh +89 -5
  81. package/plugins/lisa/hooks/block-no-verify.sh +89 -5
  82. package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +89 -5
  83. package/plugins/lisa-agy/plugin.json +1 -1
  84. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  85. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  86. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  87. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  89. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  90. package/plugins/lisa-copilot/hooks/block-no-verify.sh +89 -5
  91. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-cursor/hooks/block-no-verify.sh +89 -5
  93. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  94. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  95. package/plugins/lisa-expo-agy/plugin.json +1 -1
  96. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  97. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  98. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  99. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  100. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  101. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  102. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  103. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  104. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  105. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  106. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  108. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  109. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  110. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  111. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  112. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  113. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  114. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  115. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  116. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  117. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  118. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  119. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  120. package/plugins/lisa-rails-agy/plugin.json +1 -1
  121. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  122. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  123. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  124. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  125. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  126. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  127. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  128. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  129. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  130. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  131. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  132. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  133. package/plugins/src/base/hooks/block-no-verify.agy.sh +89 -5
  134. package/plugins/src/base/hooks/block-no-verify.sh +89 -5
  135. package/scripts/check-shipped-surface-removals.mjs +96 -24
  136. package/scripts/generate-npm-updater-helper-graph.mjs +184 -0
  137. package/scripts/two-channel-couplings.json +31 -7
  138. package/typescript/copy-contents/.husky/commit-msg +18 -0
  139. package/typescript/copy-overwrite/.prettierignore +2 -0
@@ -0,0 +1,287 @@
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
+ /** Literal original tool invocations enter a qualified worker before application code loads. */
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
+ import { resolve, sep } from "node:path";
7
+ import { invokedAsScript } from "./invoked-as-script.mjs";
8
+ import { lstatSync, realpathSync } from "node:fs";
9
+ import { required, keys } from "./npm-update-contract.mjs";
10
+ import { readJson, runProcess } from "./npm-update-process.mjs";
11
+ import {
12
+ controllerRoute,
13
+ currentGraph,
14
+ controllerGraph,
15
+ } from "./npm-update-orchestrator.mjs";
16
+ import { executeWorker, binaryDigest } from "./npm-update-isolation.mjs";
17
+ import { canonicalJson } from "../lisa-automation-provenance.mjs";
18
+ import { validateGhProfile } from "./npm-update-gh-dispatch.mjs";
19
+
20
+ const TOOLS = new Set([
21
+ "node",
22
+ "npm",
23
+ "bun",
24
+ "ruby",
25
+ "bundle",
26
+ "shell",
27
+ "bash",
28
+ "gh",
29
+ "gitleaks",
30
+ ]);
31
+
32
+ /** Loading flags introduce a second interpreter authority and require separate qualification. */
33
+ export function literalArguments(args) {
34
+ required(
35
+ Array.isArray(args) &&
36
+ args.length <= 512 &&
37
+ args.every(
38
+ arg =>
39
+ typeof arg === "string" &&
40
+ !arg.includes("\0") &&
41
+ Buffer.byteLength(arg) <= 65_536
42
+ ) &&
43
+ Buffer.byteLength(JSON.stringify(args)) <= 262_144,
44
+ "invalid or unbounded tool argument"
45
+ );
46
+ }
47
+
48
+ /** npm's qualified CLI is launched by the qualified Node interpreter, never a host npm alias. */
49
+ export function toolInvocation(runtime, tool, args) {
50
+ required(TOOLS.has(tool), "unsupported tool dispatch");
51
+ literalArguments(args);
52
+ if (tool === "node")
53
+ required(
54
+ !args.some(arg =>
55
+ /^(?:-r|--require|--import|--(?:experimental-)?loader)(?:=|$)/.test(arg)
56
+ ),
57
+ "unqualified interpreter loader route"
58
+ );
59
+ const selected = runtime.tools[tool];
60
+ required(
61
+ selected &&
62
+ typeof selected.path === "string" &&
63
+ selected.path.startsWith("/"),
64
+ "qualified tool is unavailable"
65
+ );
66
+ return tool === "npm"
67
+ ? { command: runtime.tools.node.path, args: [selected.path, ...args] }
68
+ : { command: selected.path, args: [...args] };
69
+ }
70
+
71
+ /** Caller-only private context is never mounted into candidate namespaces. */
72
+ export function launcherContext(file) {
73
+ const value = readJson(file, 1_048_576);
74
+ keys(value, [
75
+ "version",
76
+ "root",
77
+ "cwd",
78
+ "runtime",
79
+ "boundary",
80
+ "graph",
81
+ "controller",
82
+ ...([2, 3].includes(value.version) ? ["routes"] : []),
83
+ ...(value.version === 3 ? ["provider"] : []),
84
+ ]);
85
+ required(
86
+ [1, 2, 3].includes(value.version) &&
87
+ typeof value.root === "string" &&
88
+ resolve(file) === resolve(value.root, "launcher.json") &&
89
+ typeof value.cwd === "string" &&
90
+ value.cwd.startsWith("/"),
91
+ "invalid controller launcher context"
92
+ );
93
+ if (value.version === 3 && value.provider !== null)
94
+ validateGhProfile(value.provider);
95
+ const root = lstatSync(value.root);
96
+ required(
97
+ root.isDirectory() &&
98
+ !root.isSymbolicLink() &&
99
+ realpathSync(value.root) === value.root &&
100
+ root.uid === process.getuid() &&
101
+ (root.mode & 0o077) === 0,
102
+ "launcher context parent is not private controller storage"
103
+ );
104
+ required(
105
+ value.graph &&
106
+ typeof value.graph === "object" &&
107
+ !Array.isArray(value.graph) &&
108
+ Object.keys(value.graph).length <= 128,
109
+ "unbounded trusted import graph"
110
+ );
111
+ controllerGraph(value.graph, value.routes);
112
+ return value;
113
+ }
114
+
115
+ /** The first trusted Node entry needs the same qualified instrumentation as nested entries. */
116
+ function controllerInvocation(context, tool, args, env) {
117
+ if (tool !== "node") return { args, env };
118
+ const file = resolve(context.root, "launcher.json");
119
+ required(
120
+ canonicalJson(launcherContext(file)) === canonicalJson(context),
121
+ "controller context bytes differ"
122
+ );
123
+ const adapter = fileURLToPath(
124
+ new URL("./npm-update-execution-adapter.mjs", import.meta.url)
125
+ );
126
+ required(
127
+ Object.hasOwn(context.graph, adapter),
128
+ "trusted graph is missing the execution adapter"
129
+ );
130
+ required(
131
+ !env.NODE_OPTIONS && !env.NODE_PATH,
132
+ "unqualified controller environment loader"
133
+ );
134
+ return {
135
+ args: ["--import", pathToFileURL(adapter).href, ...args],
136
+ env: { ...env, LISA_NPM_DISPATCH_CONTEXT: file },
137
+ };
138
+ }
139
+
140
+ /** Caller constraints narrow execution only; copied exit statuses cannot change after dispatch starts. */
141
+ function executionContract(value) {
142
+ if (value === undefined)
143
+ return {
144
+ allowed: Array.from({ length: 256 }, (_, code) => code),
145
+ timeout: 1_800_000,
146
+ maximum: 8_388_608,
147
+ };
148
+ required(
149
+ value && typeof value === "object" && !Array.isArray(value),
150
+ "invalid execution contract object"
151
+ );
152
+ keys(value, [
153
+ "allowed",
154
+ "timeout",
155
+ "maximum",
156
+ ...(value.signal !== undefined ? ["signal"] : []),
157
+ ]);
158
+ required(
159
+ value.signal === undefined || value.signal instanceof AbortSignal,
160
+ "invalid controller cancellation signal"
161
+ );
162
+ const allowed = Array.isArray(value.allowed) ? [...value.allowed] : [];
163
+ required(
164
+ allowed.length > 0 &&
165
+ allowed.length <= 256 &&
166
+ new Set(allowed).size === allowed.length &&
167
+ allowed.every(
168
+ code => Number.isSafeInteger(code) && code >= 0 && code <= 255
169
+ ),
170
+ "invalid execution contract exit statuses"
171
+ );
172
+ required(
173
+ Number.isSafeInteger(value.timeout) &&
174
+ value.timeout > 0 &&
175
+ value.timeout <= 1_800_000 &&
176
+ Number.isSafeInteger(value.maximum) &&
177
+ value.maximum > 0 &&
178
+ value.maximum <= 8_388_608,
179
+ "invalid execution contract deadline or output bound"
180
+ );
181
+ return {
182
+ allowed,
183
+ timeout: value.timeout,
184
+ maximum: value.maximum,
185
+ ...(value.signal !== undefined ? { signal: value.signal } : {}),
186
+ };
187
+ }
188
+
189
+ /**
190
+ * Full argv/stdin cross only the chosen boundary; worker output is captured privately by the caller.
191
+ * @param {{allowed: number[], timeout: number, maximum: number}} [execution] Optional controller-only constraints.
192
+ */
193
+ export async function dispatchTool(
194
+ context,
195
+ tool,
196
+ args,
197
+ input,
198
+ env,
199
+ cwd = context.cwd,
200
+ execution = undefined
201
+ ) {
202
+ const contract = executionContract(execution);
203
+ const actualCwd = realpathSync(cwd);
204
+ const workspace = realpathSync(context.cwd);
205
+ required(
206
+ actualCwd === workspace || actualCwd.startsWith(`${workspace}${sep}`),
207
+ "tool working directory escapes its qualified workspace"
208
+ );
209
+ const invocation = toolInvocation(context.runtime, tool, args);
210
+ const route = controllerRoute(
211
+ tool,
212
+ args,
213
+ context.graph,
214
+ currentGraph(controllerGraph(context.graph, context.routes)),
215
+ actualCwd,
216
+ context.routes,
217
+ env
218
+ );
219
+ if (route === "controller") {
220
+ const identity = context.controller[tool];
221
+ keys(identity, ["path", "version", "sha256"]);
222
+ required(
223
+ typeof identity.path === "string" &&
224
+ identity.path.startsWith("/") &&
225
+ typeof identity.sha256 === "string" &&
226
+ /^[a-f0-9]{64}$/.test(identity.sha256) &&
227
+ binaryDigest(identity.path) === identity.sha256,
228
+ "qualified controller interpreter is unavailable or changed"
229
+ );
230
+ const original = controllerInvocation(context, tool, args, env);
231
+ return runProcess(identity.path, original.args, {
232
+ cwd: actualCwd,
233
+ env: original.env,
234
+ input,
235
+ ...contract,
236
+ });
237
+ }
238
+ required(
239
+ execution === undefined,
240
+ "scoped execution contract is unsupported on worker routes"
241
+ );
242
+ required(
243
+ context.boundary.workspace === workspace,
244
+ "unmatched candidate workspace mapping"
245
+ );
246
+ return executeWorker({ ...context.boundary, cwd: actualCwd }, invocation, {
247
+ input,
248
+ env,
249
+ });
250
+ }
251
+
252
+ /** No diagnostic or container plumbing status substitutes for the actual dispatched tool exit. */
253
+ async function main() {
254
+ const args = process.argv.slice(2);
255
+ required(
256
+ args.length >= 5 &&
257
+ args[0] === "--context" &&
258
+ args[2] === "--tool" &&
259
+ args[4] === "--",
260
+ "usage: launcher --context FILE --tool TOOL -- ARGV"
261
+ );
262
+ const context = launcherContext(args[1]);
263
+ const chunks = [];
264
+ let size = 0;
265
+ for await (const chunk of process.stdin) {
266
+ size += chunk.length;
267
+ required(size <= 4_194_304, "tool stdin exceeds bound");
268
+ chunks.push(chunk);
269
+ }
270
+ const result = await dispatchTool(
271
+ context,
272
+ args[3],
273
+ args.slice(5),
274
+ Buffer.concat(chunks),
275
+ process.env,
276
+ process.cwd()
277
+ );
278
+ process.stdout.write(result.stdout);
279
+ process.stderr.write(result.stderr);
280
+ process.exitCode = result.code;
281
+ }
282
+
283
+ if (invokedAsScript(import.meta.url))
284
+ main().catch(error => {
285
+ process.stderr.write(`npm tool dispatch: ${error.message}\n`);
286
+ process.exitCode = 1;
287
+ });
@@ -0,0 +1,161 @@
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
+ /** Closed guest environment carries only fixed runtime and narrowly scoped workload data. @module npm-updater */
5
+ import { required } from "./npm-update-contract.mjs";
6
+
7
+ const RUBY_INSTALL = "ruby-install";
8
+ const PROVIDER_TRANSPORT = new Set([
9
+ "HTTP_PROXY",
10
+ "HTTPS_PROXY",
11
+ "http_proxy",
12
+ "https_proxy",
13
+ "ALL_PROXY",
14
+ "all_proxy",
15
+ "NO_PROXY",
16
+ "no_proxy",
17
+ "SSL_CERT_FILE",
18
+ "SSL_CERT_DIR",
19
+ "CURL_CA_BUNDLE",
20
+ "SSLKEYLOGFILE",
21
+ "GH_DEBUG",
22
+ "GH_HTTP_UNIX_SOCKET",
23
+ "GH_CONFIG_DIR",
24
+ "GH_FORCE_TTY",
25
+ "GIT_SSL_CAINFO",
26
+ "GIT_PROXY_COMMAND",
27
+ "LD_PRELOAD",
28
+ "LD_LIBRARY_PATH",
29
+ ]);
30
+
31
+ /** Both credentialed and token-free callers reject the same native transport overrides. */
32
+ export function isProviderTransport(name) {
33
+ return PROVIDER_TRANSPORT.has(name) || name.startsWith("DYLD_");
34
+ }
35
+
36
+ const ENVIRONMENT = new Set([
37
+ "PATH",
38
+ "HOME",
39
+ "LANG",
40
+ "LC_ALL",
41
+ "TZ",
42
+ "TMPDIR",
43
+ "TMP",
44
+ "TEMP",
45
+ "NODE_VERSION",
46
+ "NPM_CONFIG_USERCONFIG",
47
+ "NPM_CONFIG_GLOBALCONFIG",
48
+ "NPM_CONFIG_CACHE",
49
+ "NPM_CONFIG_REGISTRY",
50
+ "NPM_CONFIG_IGNORE_SCRIPTS",
51
+ "NPM_CONFIG_AUDIT",
52
+ "NPM_CONFIG_FUND",
53
+ "NPM_CONFIG_UPDATE_NOTIFIER",
54
+ "GIT_TERMINAL_PROMPT",
55
+ "RAILS_ENV",
56
+ "DATABASE_USER",
57
+ "DATABASE_PASSWORD",
58
+ "DATABASE_NAME",
59
+ "DATABASE_PORT",
60
+ "PRIMARY_DB_HOST",
61
+ "DATABASE_REPLICA_HOST",
62
+ "HOSTNAME",
63
+ "RUBY_VERSION",
64
+ "RUBY_DOWNLOAD_URL",
65
+ "RUBY_DOWNLOAD_SHA256",
66
+ "GEM_HOME",
67
+ "BUNDLE_SILENCE_ROOT_WARNING",
68
+ "BUNDLE_APP_CONFIG",
69
+ "BUNDLE_PATH",
70
+ "BUNDLE_FROZEN",
71
+ ]);
72
+
73
+ /** Installer identity is confined to an isolated dependency-only namespace. */
74
+ export function isInstallerRole(role) {
75
+ return role === "install" || role === RUBY_INSTALL;
76
+ }
77
+
78
+ /** Candidate inspection rejects provider credentials and controller identity channels. */
79
+ export function inspectedEnvironment(values) {
80
+ required(
81
+ Array.isArray(values) && values.length <= ENVIRONMENT.size,
82
+ "worker environment is unbounded"
83
+ );
84
+ const names = values.map(value => {
85
+ required(
86
+ typeof value === "string" && value.includes("="),
87
+ "invalid worker environment value"
88
+ );
89
+ return value.slice(0, value.indexOf("="));
90
+ });
91
+ required(
92
+ names.every(name => ENVIRONMENT.has(name)) &&
93
+ new Set(names).size === names.length,
94
+ "worker contains ambient or duplicated authority environment"
95
+ );
96
+ }
97
+
98
+ /** Sealed and installer Ruby projections use the same fixed frozen dependency path. */
99
+ export function assertRubyEnvironment(environment, boundary) {
100
+ const ruby =
101
+ boundary.role === RUBY_INSTALL ||
102
+ boundary.mounts?.some(
103
+ mount =>
104
+ mount.source === `${boundary.root}/ruby-dependencies` &&
105
+ mount.target === `${boundary.workspace}/vendor/bundle` &&
106
+ mount.readOnly === true
107
+ );
108
+ if (ruby)
109
+ required(
110
+ environment.BUNDLE_PATH === `${boundary.workspace}/vendor/bundle` &&
111
+ environment.BUNDLE_FROZEN === "true" &&
112
+ environment.BUNDLE_APP_CONFIG === "/home/candidate/bundle",
113
+ "Ruby dependency environment requires its fixed path and frozen config"
114
+ );
115
+ }
116
+
117
+ /** Private runtime settings never inherit Actions, controller, issuer or registry channels. */
118
+ export function workerEnvironment(source, boundary) {
119
+ const value = {
120
+ PATH: "/opt/node/bin:/usr/local/bin:/usr/bin:/bin",
121
+ HOME: "/home/candidate",
122
+ LANG: "C.UTF-8",
123
+ LC_ALL: "C.UTF-8",
124
+ TZ: "UTC",
125
+ TMPDIR: "/tmp",
126
+ NPM_CONFIG_USERCONFIG: "/home/candidate/npmrc",
127
+ NPM_CONFIG_GLOBALCONFIG: "/home/candidate/global-npmrc",
128
+ NPM_CONFIG_CACHE: "/home/candidate/npm-cache",
129
+ NPM_CONFIG_REGISTRY: "https://registry.npmjs.org/",
130
+ NPM_CONFIG_AUDIT: "false",
131
+ NPM_CONFIG_FUND: "false",
132
+ NPM_CONFIG_UPDATE_NOTIFIER: "false",
133
+ GIT_TERMINAL_PROMPT: "0",
134
+ };
135
+ if (!isInstallerRole(boundary?.role)) {
136
+ for (const name of [
137
+ "RAILS_ENV",
138
+ "DATABASE_USER",
139
+ "DATABASE_PASSWORD",
140
+ "DATABASE_NAME",
141
+ "DATABASE_PORT",
142
+ "PRIMARY_DB_HOST",
143
+ "DATABASE_REPLICA_HOST",
144
+ ])
145
+ if (typeof source?.[name] === "string") value[name] = source[name];
146
+ }
147
+ if (
148
+ boundary?.role === RUBY_INSTALL ||
149
+ boundary?.mounts?.some(
150
+ mount =>
151
+ mount.source === `${boundary.root}/ruby-dependencies` &&
152
+ mount.target === `${boundary.workspace}/vendor/bundle` &&
153
+ mount.readOnly === true
154
+ )
155
+ ) {
156
+ value.BUNDLE_PATH = `${boundary.workspace}/vendor/bundle`;
157
+ value.BUNDLE_FROZEN = "true";
158
+ value.BUNDLE_APP_CONFIG = "/home/candidate/bundle";
159
+ }
160
+ return value;
161
+ }
@@ -0,0 +1,142 @@
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
+ /** Effective daemon observations must match all parent-selected namespace controls. @module npm-updater */
5
+ import { required } from "./npm-update-contract.mjs";
6
+ import { canonicalJson } from "../lisa-automation-provenance.mjs";
7
+ import { sha256 } from "./github-attestation-verifier.mjs";
8
+ import { workerScratch } from "./npm-update-worker-policy.mjs";
9
+ import {
10
+ inspectedEnvironment,
11
+ isInstallerRole,
12
+ assertRubyEnvironment,
13
+ } from "./npm-update-worker-environment.mjs";
14
+
15
+ /** Daemon inspection records the effective profile JSON rather than its caller filename. */
16
+ function inspectedSecurity(host, expected) {
17
+ required(
18
+ Array.isArray(host.SecurityOpt) &&
19
+ host.SecurityOpt.length === 2 &&
20
+ host.SecurityOpt[0] === "no-new-privileges" &&
21
+ host.SecurityOpt[1].startsWith("seccomp="),
22
+ "worker security options differ"
23
+ );
24
+ const profile = JSON.parse(host.SecurityOpt[1].slice(8));
25
+ required(
26
+ sha256(`${canonicalJson(profile)}\n`) === expected.seccompSha256,
27
+ "worker effective kernel policy bytes differ"
28
+ );
29
+ required(
30
+ host.PidsLimit === 512 &&
31
+ host.Memory === 4_294_967_296 &&
32
+ host.NanoCpus === 4_000_000_000,
33
+ "worker resource limits differ"
34
+ );
35
+ required(
36
+ [host.GroupAdd, host.ExtraHosts, host.Links, host.VolumesFrom].every(
37
+ value => !value?.length
38
+ ),
39
+ "worker has supplementary or linked authority"
40
+ );
41
+ required(
42
+ Array.isArray(host.ReadonlyPaths) &&
43
+ [
44
+ "/proc/bus",
45
+ "/proc/fs",
46
+ "/proc/irq",
47
+ "/proc/sys",
48
+ "/proc/sysrq-trigger",
49
+ ].every(path => host.ReadonlyPaths.includes(path)),
50
+ "worker proc policy is not read-only"
51
+ );
52
+ required(
53
+ canonicalJson(host.Tmpfs) === canonicalJson(workerScratch(expected)),
54
+ "worker scratch mounts differ"
55
+ );
56
+ }
57
+
58
+ /** Every observed bind belongs to the exact parent-selected projection. */
59
+ function inspectedMounts(inspected, expected) {
60
+ required(
61
+ Array.isArray(inspected.Mounts) &&
62
+ inspected.Mounts.length === expected.mounts.length,
63
+ "worker has missing or extra mounts"
64
+ );
65
+ for (const mount of expected.mounts) {
66
+ const matches = inspected.Mounts.filter(
67
+ actual => actual.Destination === mount.target
68
+ );
69
+ required(
70
+ matches.length === 1 &&
71
+ matches[0].Type === "bind" &&
72
+ matches[0].Source === mount.source &&
73
+ matches[0].RW === !mount.readOnly,
74
+ "worker input/control mount differs"
75
+ );
76
+ }
77
+ }
78
+
79
+ /** An actual inspected namespace must match every parent-selected mount and kernel flag. */
80
+ export function assertWorkerInspection(inspected, expected, invocation) {
81
+ const user = isInstallerRole(expected.role)
82
+ ? `${expected.uid}:${expected.gid}`
83
+ : "2001:2001";
84
+ required(
85
+ /^[a-f0-9]{64}$/.test(inspected.Id) &&
86
+ inspected.Image === expected.image &&
87
+ inspected.Config?.User === user &&
88
+ inspected.Config?.Labels?.["dev.lisa.npm.owner"] === expected.nonce,
89
+ "worker ownership/image/user differs"
90
+ );
91
+ const host = inspected.HostConfig;
92
+ required(
93
+ host &&
94
+ host.ReadonlyRootfs === true &&
95
+ host.Privileged === false &&
96
+ host.PidMode === "" &&
97
+ host.IpcMode === "private" &&
98
+ canonicalJson(host.CapDrop) === '["ALL"]' &&
99
+ !host.CapAdd?.length,
100
+ "worker namespace or capabilities differ"
101
+ );
102
+ inspectedSecurity(host, expected);
103
+ required(
104
+ canonicalJson(inspected.Config.Entrypoint) ===
105
+ canonicalJson([expected.supervisor.path]) &&
106
+ inspected.Config.Cmd?.[0] === String(expected.deadlineMs) &&
107
+ inspected.Config.Cmd?.[1] === "--",
108
+ "worker protected supervisor invocation differs"
109
+ );
110
+ if (invocation)
111
+ required(
112
+ canonicalJson(inspected.Config.Cmd.slice(2)) ===
113
+ canonicalJson([invocation.command, ...invocation.args]),
114
+ "worker original argv differs"
115
+ );
116
+ required(
117
+ host.NetworkMode === expected.network &&
118
+ expected.network !== "host" &&
119
+ !host.Binds?.length &&
120
+ !host.Devices?.length &&
121
+ !host.DeviceRequests?.length,
122
+ "worker has foreign network/device/bind authority"
123
+ );
124
+ inspectedEnvironment(inspected.Config.Env);
125
+ assertRubyEnvironment(
126
+ Object.fromEntries(
127
+ inspected.Config.Env.map(value => [
128
+ value.slice(0, value.indexOf("=")),
129
+ value.slice(value.indexOf("=") + 1),
130
+ ])
131
+ ),
132
+ expected
133
+ );
134
+ if (isInstallerRole(expected.role))
135
+ required(
136
+ inspected.Config.Env.every(
137
+ value => !/^(?:GH_TOKEN|GITHUB_|DATABASE_)/.test(value)
138
+ ),
139
+ "installer contains provider or application credentials"
140
+ );
141
+ inspectedMounts(inspected, expected);
142
+ }