@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,157 @@
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
+ /** Original Node hook calls preserve argv/stdin while only authenticated helpers gain a read-only IPC route. */
5
+ import { readJson, runProcess } from "./npm-update-process.mjs";
6
+ import { required, keys } from "./npm-update-contract.mjs";
7
+ import { currentGraph } from "./npm-update-orchestrator.mjs";
8
+ import { realpathSync, lstatSync } from "node:fs";
9
+ import { resolve, join, basename } from "node:path";
10
+ import { fileURLToPath, pathToFileURL } from "node:url";
11
+ import { binaryDigest } from "./npm-update-isolation.mjs";
12
+
13
+ const ENTRY = fileURLToPath(import.meta.url);
14
+ const PRELOAD = fileURLToPath(
15
+ new URL("./npm-update-hook-preload.mjs", import.meta.url)
16
+ );
17
+ const RAILS_INLINE =
18
+ 'try { const { main } = await import("./scripts/lisa-rails-prepush.mjs"); process.exitCode = await main(process.argv.slice(1)); } catch { console.error("Managed Rails push tooling is unavailable or damaged. Run full Lisa apply and retry; raw bootstrap errors are withheld."); process.exitCode = 1; }';
19
+
20
+ /** Context carries no token; the broker independently retains its immutable read subject in parent memory. */
21
+ export function hostedHookContext(file) {
22
+ const context = readJson(file, 1_048_576);
23
+ keys(context, ["version", "root", "cwd", "graph", "native", "reader"]);
24
+ keys(context.reader, ["root", "path", "deadline"]);
25
+ keys(context.native, ["node", "git", "gh"]);
26
+ keys(context.native.node, ["path", "version", "sha256"]);
27
+ const root = lstatSync(context.root);
28
+ required(
29
+ context.version === 1 &&
30
+ file === join(context.root, "hosted-hooks.json") &&
31
+ realpathSync(context.root) === context.root &&
32
+ realpathSync(context.cwd) === context.cwd &&
33
+ root.isDirectory() &&
34
+ !root.isSymbolicLink() &&
35
+ root.uid === process.getuid() &&
36
+ (root.mode & 0o077) === 0 &&
37
+ context.reader.root === context.root &&
38
+ context.reader.path === join(context.root, "hook-reader.sock") &&
39
+ Number.isSafeInteger(context.reader.deadline) &&
40
+ context.reader.deadline > Date.now() &&
41
+ context.reader.deadline <= Date.now() + 1_800_000,
42
+ "invalid hosted hook context"
43
+ );
44
+ const observed = currentGraph(context.graph);
45
+ required(
46
+ Object.keys(observed).length <= 128 &&
47
+ Object.entries(context.graph).every(
48
+ ([path, hash]) => observed[path] === hash
49
+ ),
50
+ "authenticated hook graph changed"
51
+ );
52
+ required(
53
+ context.native.node.version === "22.23.3" &&
54
+ context.native.node.path === realpathSync(context.native.node.path) &&
55
+ binaryDigest(context.native.node.path) === context.native.node.sha256,
56
+ "hosted hook Node changed"
57
+ );
58
+ return { ...context, file, deadline: context.reader.deadline };
59
+ }
60
+
61
+ /** Inline qualification is one exact managed Rails bootstrap; arbitrary eval stays ordinary token-free application code. */
62
+ export function hookEntry(context, args, cwd) {
63
+ if (
64
+ args[0] === "--input-type=module" &&
65
+ args[1] === "-e" &&
66
+ args[2] === RAILS_INLINE
67
+ ) {
68
+ const entry = join(cwd, "scripts/lisa-rails-prepush.mjs");
69
+ return Object.hasOwn(context.graph, entry) ? entry : null;
70
+ }
71
+ if (typeof args[0] !== "string" || args[0].startsWith("-")) return null;
72
+ const entry = resolve(cwd, args[0]);
73
+ return Object.hasOwn(context.graph, entry) ? entry : null;
74
+ }
75
+
76
+ /** Slots are limited read-state labels; they cannot grant a writer or widen the parent's subject. */
77
+ export function hookReadSlot(entry, args, role) {
78
+ const name = basename(entry);
79
+ if (name === "lisa-work-item.mjs") {
80
+ if (args[1] === "prepare-commit-msg") return "commit-prepare";
81
+ if (args[1] === "validate-commit") return "commit-work-item";
82
+ if (args[1] === "validate-push" && ["audit", "destination"].includes(role))
83
+ return `${role}-work-item`;
84
+ }
85
+ if (name === "lisa-automation-provenance.mjs") {
86
+ if (args[1] === "check-commit") return "commit-provenance";
87
+ if (args[1] === "check-push" && ["audit", "destination"].includes(role))
88
+ return `${role}-provenance`;
89
+ }
90
+ return null;
91
+ }
92
+
93
+ /** Provider tokens, issuer secrets and ambient preload authority are absent from both helper and application processes. */
94
+ export function tokenFreeHookEnvironment(source) {
95
+ const env = { ...source };
96
+ for (const name of Object.keys(env))
97
+ if (
98
+ /^(?:GH_TOKEN|GITHUB_TOKEN|ACTIONS_ID_TOKEN_|ACTIONS_RUNTIME_|LISA_NPM_DISPATCH_CONTEXT|NODE_OPTIONS|NODE_PATH)/.test(
99
+ name
100
+ )
101
+ )
102
+ delete env[name];
103
+ return env;
104
+ }
105
+
106
+ async function main() {
107
+ const args = process.argv.slice(2);
108
+ required(
109
+ args.length >= 3 && args[0] === "--context" && args[2] === "--",
110
+ "invalid hosted Node gateway"
111
+ );
112
+ const context = hostedHookContext(args[1]);
113
+ const original = args.slice(3);
114
+ const entry = hookEntry(context, original, realpathSync(process.cwd()));
115
+ if (entry)
116
+ required(
117
+ !original.some(arg =>
118
+ /^(?:-r|--require|--import|--(?:experimental-)?loader)(?:=|$)/.test(arg)
119
+ ) &&
120
+ !process.env.NODE_OPTIONS &&
121
+ !process.env.NODE_PATH,
122
+ "unqualified original canonical hook loader"
123
+ );
124
+ const env = tokenFreeHookEnvironment(process.env);
125
+ if (entry) env.LISA_NPM_HOSTED_HOOK_CONTEXT = context.file;
126
+ else delete env.LISA_NPM_HOSTED_HOOK_CONTEXT;
127
+ const chunks = [];
128
+ let size = 0;
129
+ for await (const chunk of process.stdin) {
130
+ size += chunk.length;
131
+ required(size <= 4_194_304, "original hook stdin exceeded");
132
+ chunks.push(chunk);
133
+ }
134
+ const timeout = Math.min(1_800_000, context.deadline - Date.now());
135
+ required(timeout > 0, "original hook phase expired");
136
+ const result = await runProcess(
137
+ context.native.node.path,
138
+ entry ? ["--import", pathToFileURL(PRELOAD).href, ...original] : original,
139
+ {
140
+ cwd: process.cwd(),
141
+ env,
142
+ input: Buffer.concat(chunks),
143
+ timeout,
144
+ maximum: 8_388_608,
145
+ allowed: Array.from({ length: 256 }, (_, code) => code),
146
+ }
147
+ );
148
+ process.stdout.write(result.stdout);
149
+ process.stderr.write(result.stderr);
150
+ process.exitCode = result.code;
151
+ }
152
+
153
+ if (process.argv[1] && resolve(process.argv[1]) === ENTRY)
154
+ main().catch(() => {
155
+ process.stderr.write("original hosted hook command failed\n");
156
+ process.exitCode = 1;
157
+ });
@@ -0,0 +1,28 @@
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
+ /** Pure shared diagnostics retain one error identity without importing producer orchestration. */
5
+
6
+ /** Only trusted static boundary reasons may leave a phase as a public diagnostic. */
7
+ export class UpdaterError extends Error {
8
+ constructor(reason) {
9
+ super(`npm updater: ${reason}`);
10
+ }
11
+ }
12
+
13
+ /** Reject a boundary explicitly without printing untrusted candidate values. */
14
+ export function required(ok, reason) {
15
+ if (!ok) throw new UpdaterError(reason);
16
+ }
17
+
18
+ /** Unknown keys are never an executable extension point. */
19
+ export function keys(value, expected) {
20
+ required(
21
+ value && typeof value === "object" && !Array.isArray(value),
22
+ "expected object"
23
+ );
24
+ required(
25
+ Object.keys(value).sort().join("\n") === [...expected].sort().join("\n"),
26
+ "unknown or missing fields"
27
+ );
28
+ }
@@ -0,0 +1,11 @@
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
+ /** Public worker facade preserves the managed command import surface without initialization. @module npm-updater */
5
+ export { workerArguments } from "./npm-update-worker-policy.mjs";
6
+ export { assertWorkerInspection } from "./npm-update-worker-inspection.mjs";
7
+ export {
8
+ executeWorker,
9
+ recoverWorkers,
10
+ binaryDigest,
11
+ } from "./npm-update-worker-lifecycle.mjs";
@@ -0,0 +1,171 @@
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
+ /** Pure environment, lifecycle and fixed Task data cannot import live claim or quality orchestration. */
5
+ import { canonicalJson } from "../lisa-automation-provenance.mjs";
6
+ import { required } from "./npm-update-contract.mjs";
7
+ import { trackerContract } from "../lisa-work-item.mjs";
8
+
9
+ /** Immutable host config, not candidate data or local aliases, chooses lifecycle. */
10
+ export function leafRoles(config, repository) {
11
+ required(
12
+ config.tracker === "github" &&
13
+ repository === `${config.github?.org}/${config.github?.repo}`,
14
+ "configured tracker scope differs"
15
+ );
16
+ required(
17
+ !config.github.queueRepo || config.github.queueRepo === repository,
18
+ "separate umbrella queues are not supported by this producer"
19
+ );
20
+ required(
21
+ !config.github.project &&
22
+ !config.github.projectV2 &&
23
+ !config.github.projects,
24
+ "configured ProjectV2 coordination requires the ordinary writer"
25
+ );
26
+ const build = config.github.labels?.build ?? {};
27
+ const contract = trackerContract(config);
28
+ const targetEnvironment = environment(config);
29
+ const terminal =
30
+ contract.lifecycle.done.find(([name]) => name === targetEnvironment)?.[1] ??
31
+ contract.lifecycle.terminalName;
32
+ required(
33
+ typeof terminal === "string" && terminal.trim().length > 0,
34
+ "configured main environment terminal label is missing"
35
+ );
36
+ const terminals = contract.lifecycle.done.map(([, role]) => role);
37
+ required(
38
+ terminals.every(
39
+ label => typeof label === "string" && label.trim().length > 0
40
+ ),
41
+ "configured terminal labels are invalid"
42
+ );
43
+ const later = contract.lifecycle.roles.filter(
44
+ label =>
45
+ label !== contract.lifecycle.ready &&
46
+ label !== (build.blocked ?? "status:blocked") &&
47
+ !terminals.includes(label)
48
+ );
49
+ return {
50
+ ready: contract.lifecycle.ready,
51
+ claimed: contract.lifecycle.claimed,
52
+ blocked: build.blocked ?? "status:blocked",
53
+ human: build.human_needed ?? "human-needed",
54
+ terminal,
55
+ terminals,
56
+ later,
57
+ };
58
+ }
59
+
60
+ /** Reuse the canonical default while refusing an explicitly ambiguous main mapping. */
61
+ export function environment(config) {
62
+ const matches = Object.entries(config.deploy?.branches ?? {}).filter(
63
+ ([, branch]) => branch === "main"
64
+ );
65
+ required(
66
+ matches.length <= 1,
67
+ "main requires one unambiguous configured runtime environment"
68
+ );
69
+ const target = trackerContract(config).deployBranches.get("main");
70
+ required(target, "main has no configured runtime environment");
71
+ return target;
72
+ }
73
+
74
+ /** A standalone, build-ready Task has complete implementation and terminal evidence. */
75
+ export function buildDraft(proposal, policy, config, evidence) {
76
+ const roles = leafRoles(config, policy.repository);
77
+ const env = environment(config);
78
+ const versions = proposal.updates
79
+ .map(update => `${update.name}: ${update.from} -> ${update.to}`)
80
+ .join("\n");
81
+ const body = `## Context / Business Value
82
+
83
+ Keep the selected dependencies current while preserving ordinary review, required checks and runtime verification. This Task owns one exact dependency proposal; its implementation ticket is never reused for another update.
84
+
85
+ ## Technical Approach
86
+
87
+ Base parent: ${proposal.parent}
88
+ Proposal key: ${proposal.key}
89
+ Selection key: ${proposal.selectionKey}
90
+ Update exactly package.json and package-lock.json:
91
+ ${versions}
92
+ Use the trusted four-job npm updater. Preparation performs an installed-tree npm outdated check and npm ci with lifecycle scripts disabled. Allocation precedes the first attributed commit. Ordinary commit and original pre-push gates remain mandatory. Publish only the exact gated unsigned raw Git object with unchanged parent, tree, identities, times and message. Preserve every unrelated dependency, script and existing assignee. Stop on stale base, unavailable authorization, hold, failed gate or mismatching bytes. Lisa is excluded only with verified local full-apply ownership.
93
+
94
+ ## Acceptance Criteria
95
+
96
+ \`\`\`gherkin
97
+ Scenario: publish one coherent dependency proposal
98
+ Given parent ${proposal.parent} and the selected versions above
99
+ When the trusted updater claims this leaf and completes ordinary gates
100
+ Then the manifest and lock agree and npm ci preserves both file hashes
101
+ And the pull request into main contains exactly the gated commit and two files
102
+ Scenario: refuse unauthorized or stale publication
103
+ Given a hold, changed parent, failed gate or mismatching proposal
104
+ When publication is attempted
105
+ Then no branch or pull request is written and a diagnostic reports failure
106
+ Scenario: complete ordinary review and runtime verification
107
+ Given the pull request at its current head
108
+ When a trusted human approves and every configured required check succeeds
109
+ Then runtime verification in ${env} provides the declared evidence before closure
110
+ \`\`\`
111
+
112
+ ## Out of Scope
113
+
114
+ No repository scripts, lifecycle execution, package-manager migration, workspaces, merge, deployment, review approval, protection changes or automatic issue closure. No generic Lisa version-only update.
115
+
116
+ ## Repository
117
+
118
+ ${policy.repository}
119
+
120
+ ## Target Backend Environment
121
+
122
+ Assumption: ${env} — remote default branch main
123
+
124
+ ## Branch Plan
125
+
126
+ Branch from: main
127
+ PR into: main
128
+ Derived from: Target Backend Environment ${env} via .lisa.config.json deploy.branches
129
+
130
+ ## Sign-in Required
131
+
132
+ GitHub Actions runtime token with explicitly reduced job permissions. Trusted maintainer: ${policy.maintainer}. No maintainer credential is used. Allocation alone may sign. Preparation and gates have no provider write or issuance authority.
133
+
134
+ ## Source Precedence
135
+
136
+ Business rules: this exact proposal and ordinary configured policy. Visual: existing application. Flow: existing application. API/data: committed npm manifest, coherent registry lock and installed-tree checks. Stop and report cross-axis conflicts.
137
+
138
+ ## Relationship Search
139
+
140
+ Historical filing evidence, preserved from initial allocation. Current relationships and eligibility are checked again before each recovery.
141
+ Git history command: ${evidence.history.command}
142
+ Git history outcome: ${evidence.history.result}
143
+ All-state GitHub query: ${evidence.search.query}
144
+ Outcome: every matching record was loaded completely; unrelated work was preserved and exact proposal recovery was checked separately.
145
+ Matches are read completely before allocating this deterministic proposal. No parent or PRD lineage is declared; this build-ready standalone Task is one leaf.
146
+
147
+ ## Validation Journey
148
+
149
+ 1. Prepare the selected update from the exact parent with installed npm outdated; record actual exit and versions. [EVIDENCE: cli-output: npm-outdated]
150
+ 2. Run script-disabled npm ci against the proposed files and record unchanged hashes. [EVIDENCE: cli-output: npm-ci]
151
+ 3. Record normal commit/pre-push hook exits, all-ref stdin/ranges and the exact raw commit identity. [EVIDENCE: test-run-log: ordinary-gates]
152
+ 4. Read the current-head pull request, required checks and genuine human approval. [EVIDENCE: state-dump: current-head-review]
153
+ 5. Verify ordinary application behavior in ${env}; record actual environment and deployed identity. [EVIDENCE: test-run-log: runtime-verification]
154
+ 6. Reach a stale-base or hold rejection before any publication write. [EVIDENCE: cli-output: rejected-publication]
155
+
156
+ ## Proposal Bytes
157
+
158
+ package.json SHA256: ${proposal.hashes["package.json"]}
159
+ package-lock.json SHA256: ${proposal.hashes["package-lock.json"]}
160
+
161
+ <!-- [lisa-npm-filing] ${Buffer.from(canonicalJson(evidence)).toString("base64")} -->
162
+ <!-- [lisa-npm-proposal] ${proposal.key} -->
163
+ `;
164
+ return {
165
+ title: "Update selected npm dependencies",
166
+ body,
167
+ labels: ["type:Task", "priority:medium", roles.ready],
168
+ assignees: [policy.maintainer],
169
+ build_ready: true,
170
+ };
171
+ }
@@ -0,0 +1,194 @@
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-leaf.mjs
6
+ * @description A fixed dependency Task still owes all writer gates and current human authorization.
7
+ * @module npm-updater
8
+ */
9
+ import { canonicalClassifier } from "./npm-update-helper.mjs";
10
+ import { canonicalJson } from "../lisa-automation-provenance.mjs";
11
+ import { required, CLAIM, keys } from "./npm-update-contract.mjs";
12
+ import { qualityGates } from "./npm-update-quality.mjs";
13
+ import { leafRoles } from "./npm-update-leaf-contract.mjs";
14
+ export {
15
+ leafRoles,
16
+ environment,
17
+ buildDraft,
18
+ } from "./npm-update-leaf-contract.mjs";
19
+ import { sha256 } from "./github-attestation-verifier.mjs";
20
+
21
+ /** Each transition rereads holds, ownership and the actual managed claim. */
22
+ export async function claimLeaf(
23
+ api,
24
+ suppliedIssue,
25
+ draft,
26
+ proposal,
27
+ policy,
28
+ config,
29
+ evidence,
30
+ roles
31
+ ) {
32
+ let issue = await api.issue(suppliedIssue.number);
33
+ let state = await inspectLeaf(issue, draft, policy, config);
34
+ qualityGates(
35
+ { ...draft, title: issue.title, body: issue.body },
36
+ proposal,
37
+ policy,
38
+ config,
39
+ evidence
40
+ );
41
+ if (state.unassigned) {
42
+ await api.assignable(policy.maintainer);
43
+ await api.request(
44
+ `repos/${policy.repository}/issues/${issue.number}/assignees`,
45
+ "POST",
46
+ { assignees: [policy.maintainer] }
47
+ );
48
+ }
49
+ issue = await api.issue(issue.number);
50
+ state = await inspectLeaf(issue, draft, policy, config);
51
+ required(!state.unassigned, "allocation lacks trusted maintainer ownership");
52
+ if (!state.claimed) {
53
+ const labels = issue.labels
54
+ .map(label => label.name)
55
+ .filter(name => name !== roles.ready);
56
+ labels.push(roles.claimed);
57
+ await api.request(
58
+ `repos/${policy.repository}/issues/${issue.number}`,
59
+ "PATCH",
60
+ { labels }
61
+ );
62
+ }
63
+ issue = await api.issue(issue.number);
64
+ state = await inspectLeaf(issue, draft, policy, config);
65
+ if (!state.claims.length)
66
+ await api.request(
67
+ `repos/${policy.repository}/issues/${issue.number}/comments`,
68
+ "POST",
69
+ { body: CLAIM }
70
+ );
71
+ issue = await api.issue(issue.number);
72
+ state = await inspectLeaf(issue, draft, policy, config);
73
+ required(
74
+ state.claimed &&
75
+ state.claims.length === 1 &&
76
+ String(state.claims[0].user?.id) === api.policy.claimActorId &&
77
+ state.claims[0].user?.type === "Bot",
78
+ "actual managed claim identity differs"
79
+ );
80
+ return { issue, state };
81
+ }
82
+
83
+ /** Complete provider history and immutable human policy govern every mutation. */
84
+ export async function inspectLeaf(issue, draft, policy, config) {
85
+ const roles = leafRoles(config, policy.repository);
86
+ const labels = issue.labels.map(label => label.name);
87
+ required(
88
+ issue.state === "open" && !issue.pull_request && issue.number > 0,
89
+ "closed or nonissue leaf"
90
+ );
91
+ required(
92
+ issue.children.length === 0 &&
93
+ issue.blockers.every(item => item.state === "closed"),
94
+ "child work or active blocker"
95
+ );
96
+ required(
97
+ !/- \[[ x]\]/i.test(issue.body) && !labels.includes("type:Epic"),
98
+ "container is not claimable"
99
+ );
100
+ required(
101
+ !roles.terminals.some(label => labels.includes(label)) &&
102
+ !labels.includes(roles.blocked),
103
+ "terminal or blocked leaf"
104
+ );
105
+ const hold = (await canonicalClassifier(config))({
106
+ body: issue.body,
107
+ labels,
108
+ comments: issue.comments,
109
+ trustedHumanActorIds: config.github.trustedHumanActorIds ?? [],
110
+ humanNeededLabel: roles.human,
111
+ });
112
+ required(!hold.held, "current human hold is outstanding");
113
+ required(
114
+ issue.body === draft.body && issue.title === draft.title,
115
+ "live proposal specification differs"
116
+ );
117
+ required(
118
+ issue.assignees.every(
119
+ actor => actor.login === policy.maintainer && actor.type === "User"
120
+ ),
121
+ "existing assignee is foreign; preserve ownership"
122
+ );
123
+ required(
124
+ ["type:Task", "priority:medium"].every(label => labels.includes(label)),
125
+ "required live labels differ"
126
+ );
127
+ const claims = issue.comments.filter(comment => comment.body === CLAIM);
128
+ required(claims.length <= 1, "ambiguous managed claims");
129
+ return {
130
+ claimed: labels.some(label => roles.later.includes(label)),
131
+ unassigned: issue.assignees.length === 0,
132
+ claims,
133
+ roles,
134
+ };
135
+ }
136
+
137
+ /** Initial public filing evidence is immutable specification, not live authorization. */
138
+ export function historicalFiling(issue) {
139
+ const matches = [
140
+ ...issue.body.matchAll(/<!-- \[lisa-npm-filing\] ([A-Za-z0-9+/]+=*) -->/g),
141
+ ];
142
+ required(
143
+ matches.length === 1 && matches[0][1].length <= 65_536,
144
+ "original filing evidence is missing or ambiguous"
145
+ );
146
+ const bytes = Buffer.from(matches[0][1], "base64");
147
+ required(
148
+ bytes.toString("base64") === matches[0][1],
149
+ "original filing encoding differs"
150
+ );
151
+ const value = JSON.parse(
152
+ new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes)
153
+ );
154
+ keys(value, ["history", "search", "registry", "labels", "main"]);
155
+ required(
156
+ canonicalJson(value) === bytes.toString("utf8"),
157
+ "original filing fields are noncanonical"
158
+ );
159
+ return value;
160
+ }
161
+
162
+ export { qualityGates } from "./npm-update-quality.mjs";
163
+
164
+ /** Every privileged transition rechecks current policy, claim, owner and holds. */
165
+ export async function authorizeAllocation(
166
+ api,
167
+ proposal,
168
+ allocation,
169
+ policy,
170
+ config
171
+ ) {
172
+ await api.main(proposal.parent);
173
+ await api.assignable(policy.maintainer);
174
+ const issue = await api.issue(allocation.number);
175
+ const state = await inspectLeaf(issue, allocation.draft, policy, config);
176
+ required(
177
+ state.claimed &&
178
+ !state.unassigned &&
179
+ state.claims.length === 1 &&
180
+ String(state.claims[0].id) === allocation.claimCommentId &&
181
+ String(state.claims[0].user?.id) === api.policy.claimActorId &&
182
+ state.claims[0].user?.type === "Bot" &&
183
+ sha256(state.claims[0].body) === allocation.claimSha256,
184
+ "current proposal claim differs"
185
+ );
186
+ qualityGates(
187
+ { ...allocation.draft, title: issue.title, body: issue.body },
188
+ proposal,
189
+ policy,
190
+ config,
191
+ allocation.evidence
192
+ );
193
+ return issue;
194
+ }