@codyswann/lisa 4.70.4 → 4.71.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +272 -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 +258 -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 +598 -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 +157 -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,263 @@
1
+ // This file is managed by Lisa and IS replaced on each `lisa` run.
2
+ // Do not edit directly — durable changes belong upstream in Lisa.
3
+
4
+ /**
5
+ * @file npm-update-prepare.mjs
6
+ * @description Real script-free npm installations distinguish current packages from missing trees.
7
+ * @module npm-updater
8
+ */
9
+ import {
10
+ mkdirSync,
11
+ writeFileSync,
12
+ readFileSync,
13
+ readdirSync,
14
+ existsSync,
15
+ } from "node:fs";
16
+ import { join } from "node:path";
17
+ import {
18
+ FILES,
19
+ SECTIONS,
20
+ required,
21
+ validatePolicy,
22
+ validateHost,
23
+ validateLock,
24
+ proposalFrom,
25
+ } from "./npm-update-contract.mjs";
26
+ import {
27
+ runProcess,
28
+ runNpm,
29
+ withPrivateRoot,
30
+ effectiveEngineStrict,
31
+ } from "./npm-update-process.mjs";
32
+ import { canonicalJson } from "../lisa-automation-provenance.mjs";
33
+ import { qualifyLisaOwner } from "./npm-update-owner.mjs";
34
+ export { qualifyLisaOwner } from "./npm-update-owner.mjs";
35
+ import { sha256 } from "./github-attestation-verifier.mjs";
36
+
37
+ const NPM_FLAGS = ["--ignore-scripts", "--no-audit", "--no-fund"];
38
+
39
+ /** Actual installed versions, not only lock entries, reach npm's outdated boundary. */
40
+ function installedVersions(directory, selections) {
41
+ return Object.fromEntries(
42
+ selections.map(({ name }) => [
43
+ name,
44
+ JSON.parse(
45
+ readFileSync(
46
+ join(directory, "node_modules", name, "package.json"),
47
+ "utf8"
48
+ )
49
+ ).version,
50
+ ])
51
+ );
52
+ }
53
+
54
+ /** Re-read the committed source; an unstaged manifest cannot become the baseline. */
55
+ export async function baseline(cwd, env, proposal) {
56
+ const command = args =>
57
+ runProcess("git", args, { cwd, env, maximum: 1_048_576 });
58
+ const parent = (await command(["rev-parse", "HEAD"])).stdout
59
+ .toString()
60
+ .trim();
61
+ const files = {};
62
+ for (const file of FILES) {
63
+ const entry = (
64
+ await command(["ls-tree", "HEAD", "--", file])
65
+ ).stdout.toString();
66
+ required(
67
+ entry.startsWith("100644 blob ") && entry.trim().split("\n").length === 1,
68
+ "regular committed npm files required"
69
+ );
70
+ files[file] = (await command(["show", `HEAD:${file}`])).stdout.toString(
71
+ "utf8"
72
+ );
73
+ }
74
+ if (proposal)
75
+ required(
76
+ parent === proposal.parent &&
77
+ canonicalJson(JSON.parse(files["package.json"])) ===
78
+ canonicalJson(proposal.before),
79
+ "committed proposal baseline differs"
80
+ );
81
+ return { parent, files };
82
+ }
83
+
84
+ /** Installed evidence and genuine selected registry metadata define the update set. */
85
+ async function findUpdates(
86
+ before,
87
+ policy,
88
+ installedBefore,
89
+ detected,
90
+ outdated,
91
+ npm
92
+ ) {
93
+ const updates = [];
94
+ for (const selection of policy.packages) {
95
+ const sections = SECTIONS.filter(
96
+ section => before[section]?.[selection.name]
97
+ );
98
+ required(
99
+ sections.length === 1,
100
+ "selected package must be one unambiguous direct dependency"
101
+ );
102
+ if (installedBefore[selection.name] === selection.version) continue;
103
+ required(
104
+ outdated.code === 1 &&
105
+ detected[selection.name]?.current === installedBefore[selection.name],
106
+ "outdated did not reach installed selected package"
107
+ );
108
+ const metadata = await npm([
109
+ "view",
110
+ `${selection.name}@${selection.version}`,
111
+ "--json",
112
+ ]);
113
+ const registry = JSON.parse(metadata.stdout.toString());
114
+ required(
115
+ registry.name === selection.name &&
116
+ registry.version === selection.version &&
117
+ registry.dist?.integrity,
118
+ "selected registry metadata differs"
119
+ );
120
+ updates.push({
121
+ name: selection.name,
122
+ section: sections[0],
123
+ from: before[sections[0]][selection.name],
124
+ to: selection.version,
125
+ });
126
+ }
127
+ return updates;
128
+ }
129
+
130
+ /** npm writes only the explicitly selected ordinary dependency sections. */
131
+ async function installUpdates(updates, npm, npmFlags) {
132
+ for (const update of updates) {
133
+ const sectionFlag = {
134
+ dependencies: "--save-prod",
135
+ devDependencies: "--save-dev",
136
+ optionalDependencies: "--save-optional",
137
+ }[update.section];
138
+ await npm([
139
+ "install",
140
+ `${update.name}@${update.to}`,
141
+ "--save-exact",
142
+ "--package-lock-only",
143
+ sectionFlag,
144
+ ...npmFlags,
145
+ ]);
146
+ }
147
+ }
148
+
149
+ /** Build only a two-file proposal in a private HOME, never executing repository scripts. */
150
+ async function preparedProposal({
151
+ app,
152
+ npm,
153
+ policy,
154
+ original,
155
+ before,
156
+ updates,
157
+ npmFlags,
158
+ version,
159
+ outdated,
160
+ installedBefore,
161
+ }) {
162
+ await installUpdates(updates, npm, npmFlags);
163
+ const files = Object.fromEntries(
164
+ FILES.map(file => [file, readFileSync(join(app, file), "utf8")])
165
+ );
166
+ const proposal = proposalFrom(
167
+ policy,
168
+ original.parent,
169
+ before,
170
+ files,
171
+ updates
172
+ );
173
+ const hashes = Object.fromEntries(
174
+ FILES.map(file => [file, sha256(readFileSync(join(app, file)))])
175
+ );
176
+ await npm(["ci", ...npmFlags]);
177
+ required(
178
+ FILES.every(file => hashes[file] === sha256(readFileSync(join(app, file)))),
179
+ "npm ci rewrote manifest or lock"
180
+ );
181
+ required(
182
+ readdirSync(app).sort().join("\n") ===
183
+ ["node_modules", ...FILES].sort().join("\n"),
184
+ "unexpected npm output path or lifecycle effect"
185
+ );
186
+ return {
187
+ status: "prepared",
188
+ proposal,
189
+ npm: {
190
+ version,
191
+ outdatedExit: outdated.code,
192
+ installedBefore,
193
+ installedAfter: installedVersions(app, policy.packages),
194
+ ciPreserved: true,
195
+ },
196
+ };
197
+ }
198
+
199
+ /** The private resource lifecycle remains outside the prepared proposal's npm checks. */
200
+ export async function prepareUpdate({ cwd, policy: input, config }) {
201
+ const engineStrict = await effectiveEngineStrict(cwd);
202
+ const npmFlags = [...NPM_FLAGS, ...(engineStrict ? ["--engine-strict"] : [])];
203
+ const policy = validatePolicy(input, config);
204
+ let privatePath;
205
+ const result = await withPrivateRoot(async (root, env) => {
206
+ privatePath = root;
207
+ const app = join(root, "app");
208
+ mkdirSync(app, { mode: 0o700 });
209
+ required(process.versions.node === "22.23.3", "Node22.23.3 is required");
210
+ const version = (await runNpm(["--version"], { env })).stdout
211
+ .toString()
212
+ .trim();
213
+ required(
214
+ ["10.9.9", "11.21.0"].includes(version),
215
+ "supported npm10.9.9 or11.21.0 is required"
216
+ );
217
+ const original = await baseline(cwd, env);
218
+ const before = JSON.parse(original.files["package.json"]);
219
+ validateHost(before);
220
+ required(
221
+ !before.workspaces &&
222
+ (!before.packageManager || before.packageManager === `npm@${version}`),
223
+ "unsupported workspace or npm declaration"
224
+ );
225
+ await qualifyLisaOwner(cwd, before, policy, config, engineStrict);
226
+ validateLock(JSON.parse(original.files["package-lock.json"]), before, []);
227
+ for (const file of FILES)
228
+ writeFileSync(join(app, file), original.files[file], { mode: 0o600 });
229
+ const npm = (args, allowed = [0]) =>
230
+ runNpm(args, { cwd: app, env, timeout: 120_000, allowed });
231
+ await npm(["ci", ...npmFlags]);
232
+ const installedBefore = installedVersions(app, policy.packages);
233
+ const outdated = await npm(["outdated", "--json", "--long"], [0, 1]);
234
+ const detected = JSON.parse(outdated.stdout.toString() || "{}");
235
+ const updates = await findUpdates(
236
+ before,
237
+ policy,
238
+ installedBefore,
239
+ detected,
240
+ outdated,
241
+ npm
242
+ );
243
+ if (updates.length === 0)
244
+ return {
245
+ status: "no-selected-update",
246
+ npm: { version, outdatedExit: outdated.code, installedBefore },
247
+ };
248
+ return preparedProposal({
249
+ app,
250
+ npm,
251
+ policy,
252
+ original,
253
+ before,
254
+ updates,
255
+ npmFlags,
256
+ version,
257
+ outdated,
258
+ installedBefore,
259
+ });
260
+ });
261
+ required(!existsSync(privatePath), "private npm resources remain");
262
+ return { ...result, cleanup: { absent: true } };
263
+ }
@@ -0,0 +1,179 @@
1
+ // This file is managed by Lisa and IS replaced on each `lisa` run.
2
+ // Do not edit directly — durable changes belong upstream in Lisa.
3
+
4
+ /**
5
+ * @file npm-update-process-core.mjs
6
+ * @description Private bounded process and file boundaries keep credentials out of candidates.
7
+ * @module npm-updater
8
+ */
9
+ import {
10
+ constants,
11
+ openSync,
12
+ closeSync,
13
+ fstatSync,
14
+ ftruncateSync,
15
+ readSync,
16
+ readFileSync,
17
+ writeFileSync,
18
+ mkdirSync,
19
+ mkdtempSync,
20
+ rmSync,
21
+ lstatSync,
22
+ existsSync,
23
+ realpathSync,
24
+ readdirSync,
25
+ } from "node:fs";
26
+ import { join, resolve, dirname } from "node:path";
27
+ import { tmpdir } from "node:os";
28
+ import { randomBytes } from "node:crypto";
29
+ import { canonicalJson } from "../lisa-automation-provenance.mjs";
30
+ import { required } from "./npm-update-contract.mjs";
31
+
32
+ /** A fresh HOME and allowlist remove ambient Git/npm/issuer/write authority. */
33
+ export function candidateEnvironment(home, source = process.env) {
34
+ return {
35
+ PATH: source.PATH ?? "/usr/bin:/bin",
36
+ HOME: home,
37
+ LANG: "C.UTF-8",
38
+ LC_ALL: "C.UTF-8",
39
+ TZ: "UTC",
40
+ TMPDIR: join(home, "tmp"),
41
+ NPM_CONFIG_USERCONFIG: join(home, "npmrc"),
42
+ NPM_CONFIG_GLOBALCONFIG: join(home, "global-npmrc"),
43
+ NPM_CONFIG_CACHE: join(home, "npm-cache"),
44
+ NPM_CONFIG_REGISTRY: "https://registry.npmjs.org/",
45
+ NPM_CONFIG_AUDIT: "false",
46
+ NPM_CONFIG_FUND: "false",
47
+ NPM_CONFIG_UPDATE_NOTIFIER: "false",
48
+ GIT_TERMINAL_PROMPT: "0",
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Single owned root includes every cache and temp path; cleanup is a checked result.
54
+ * @template T
55
+ * @param {(root: string, env: NodeJS.ProcessEnv) => T | Promise<T>} operation Owned work.
56
+ * @returns {Promise<T>} The operation result after checked cleanup.
57
+ */
58
+ export async function withPrivateRoot(operation) {
59
+ const root = realpathSync(mkdtempSync(join(tmpdir(), "lisa-npm-update-")));
60
+ const nonce = randomBytes(24).toString("hex");
61
+ writeFileSync(join(root, "owner"), nonce, { mode: 0o600 });
62
+ mkdirSync(join(root, "tmp"), { mode: 0o700 });
63
+ try {
64
+ return await operation(root, candidateEnvironment(root));
65
+ } finally {
66
+ required(
67
+ lstatSync(root).isDirectory() &&
68
+ !lstatSync(root).isSymbolicLink() &&
69
+ readFileSync(join(root, "owner"), "utf8") === nonce,
70
+ "cleanup ownership changed"
71
+ );
72
+ required(
73
+ !readdirSync(root).some(name =>
74
+ /^namespace-[a-f0-9]{16}\.json$/.test(name)
75
+ ),
76
+ "owned namespace registration cleanup outstanding"
77
+ );
78
+ rmSync(root, { recursive: true });
79
+ required(!existsSync(root), "owned root cleanup failed");
80
+ }
81
+ }
82
+
83
+ /** Read through a no-follow descriptor so aliases and growth cannot bypass bounds. */
84
+ export function readBytes(file, maximum = 3_145_728, privateFile = true) {
85
+ required(
86
+ Number.isSafeInteger(maximum) && maximum > 0 && maximum <= 4_194_304,
87
+ "invalid file bound"
88
+ );
89
+ const fd = openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW);
90
+ try {
91
+ const st = fstatSync(fd);
92
+ required(
93
+ st.isFile() &&
94
+ st.size <= maximum &&
95
+ (!privateFile ||
96
+ ((st.mode & 0o077) === 0 &&
97
+ st.nlink === 1 &&
98
+ st.uid === process.getuid())),
99
+ "invalid bounded private file"
100
+ );
101
+ const bytes = Buffer.alloc(maximum + 1);
102
+ let length = 0;
103
+ let count;
104
+ do {
105
+ count = readSync(fd, bytes, length, bytes.length - length, null);
106
+ length += count;
107
+ required(length <= maximum, "file grew beyond bound");
108
+ } while (count > 0);
109
+ return bytes.subarray(0, length);
110
+ } finally {
111
+ closeSync(fd);
112
+ }
113
+ }
114
+
115
+ /** Canonical serialization rejects duplicate keys instead of discarding ambiguity. */
116
+ export function readJson(file, maximum) {
117
+ const bytes = readBytes(file, maximum);
118
+ const text = new TextDecoder("utf8", { fatal: true, ignoreBOM: true }).decode(
119
+ bytes
120
+ );
121
+ const value = JSON.parse(text);
122
+ required(
123
+ text === `${canonicalJson(value)}\n`,
124
+ "JSON must be canonical without duplicate fields"
125
+ );
126
+ return value;
127
+ }
128
+
129
+ /** Fresh outputs never follow symlinks or overwrite recovery evidence. */
130
+ export function writeJson(file, value) {
131
+ const absolute = resolve(file);
132
+ writeFileSync(absolute, `${canonicalJson(value)}\n`, {
133
+ mode: 0o600,
134
+ flag: "wx",
135
+ });
136
+ return absolute;
137
+ }
138
+
139
+ /** Replace only an already private, singly linked owned phase file. */
140
+ export function replaceJson(file, value) {
141
+ const bytes = Buffer.from(`${canonicalJson(value)}\n`);
142
+ required(bytes.length <= 3_145_728, "replacement phase data exceeds bound");
143
+ const fd = openSync(file, constants.O_WRONLY | constants.O_NOFOLLOW);
144
+ try {
145
+ const stat = fstatSync(fd);
146
+ required(
147
+ stat.isFile() &&
148
+ stat.nlink === 1 &&
149
+ stat.uid === process.getuid() &&
150
+ (stat.mode & 0o077) === 0,
151
+ "phase replacement is not owned private data"
152
+ );
153
+ ftruncateSync(fd, 0);
154
+ writeFileSync(fd, bytes);
155
+ } finally {
156
+ closeSync(fd);
157
+ }
158
+ required(readBytes(file).equals(bytes), "phase replacement readback differs");
159
+ }
160
+
161
+ /** A fixed phase directory must be private and genuinely owned before any writes. */
162
+ export function phaseDirectory(directory) {
163
+ const parent = lstatSync(dirname(directory));
164
+ required(
165
+ parent.isDirectory() && !parent.isSymbolicLink(),
166
+ "phase parent is aliased"
167
+ );
168
+ if (!existsSync(directory)) mkdirSync(directory, { mode: 0o700 });
169
+ const stat = lstatSync(directory);
170
+ required(
171
+ stat.isDirectory() &&
172
+ !stat.isSymbolicLink() &&
173
+ stat.uid === process.getuid() &&
174
+ (stat.mode & 0o077) === 0,
175
+ "phase directory is not owned private storage"
176
+ );
177
+ }
178
+
179
+ export { runProcess } from "./npm-update-native-process.mjs";
@@ -0,0 +1,16 @@
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
+ /** Compatibility exports retain bounded IO and native npm/owner APIs through one-way implementation dependencies. */
5
+ export {
6
+ candidateEnvironment,
7
+ withPrivateRoot,
8
+ readBytes,
9
+ readJson,
10
+ writeJson,
11
+ replaceJson,
12
+ phaseDirectory,
13
+ runProcess,
14
+ } from "./npm-update-process-core.mjs";
15
+ export { runNpm, effectiveEngineStrict } from "./npm-update-npm.mjs";
16
+ export { releasedLisaIdentity } from "./npm-update-owner.mjs";