@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.
- package/all/copy-overwrite/scripts/lib/automation-provenance-contract.mjs +174 -0
- package/all/copy-overwrite/scripts/lib/automation-provenance-local.mjs +279 -0
- package/all/copy-overwrite/scripts/lib/gate-failure-diagnosis.mjs +17 -7
- package/all/copy-overwrite/scripts/lib/github-attestation-provider.mjs +241 -0
- package/all/copy-overwrite/scripts/lib/github-attestation-recovery.mjs +283 -0
- package/all/copy-overwrite/scripts/lib/github-attestation-verifier.mjs +244 -0
- package/all/copy-overwrite/scripts/lib/npm-update-allocate.mjs +289 -0
- package/all/copy-overwrite/scripts/lib/npm-update-authorization.mjs +235 -0
- package/all/copy-overwrite/scripts/lib/npm-update-broker-client.mjs +92 -0
- package/all/copy-overwrite/scripts/lib/npm-update-checkpoint.mjs +241 -0
- package/all/copy-overwrite/scripts/lib/npm-update-contract.mjs +278 -0
- package/all/copy-overwrite/scripts/lib/npm-update-controller-broker.mjs +234 -0
- package/all/copy-overwrite/scripts/lib/npm-update-controller-factory.mjs +278 -0
- package/all/copy-overwrite/scripts/lib/npm-update-controller-recipe.mjs +135 -0
- package/all/copy-overwrite/scripts/lib/npm-update-execution-adapter.mjs +292 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate-hooks.mjs +214 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate-install.mjs +267 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate-proof.mjs +99 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate.mjs +300 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gh-dispatch.mjs +182 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gh-grants.mjs +156 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gh-requests.mjs +221 -0
- package/all/copy-overwrite/scripts/lib/npm-update-github.mjs +214 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper-graph.mjs +238 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper-inventory.mjs +126 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper.mjs +292 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-installation.mjs +116 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-preload.mjs +153 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-provider.mjs +207 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-read-client.mjs +164 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hosted-gate.mjs +272 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hosted-hook.mjs +157 -0
- package/all/copy-overwrite/scripts/lib/npm-update-invariants.mjs +28 -0
- package/all/copy-overwrite/scripts/lib/npm-update-isolation.mjs +11 -0
- package/all/copy-overwrite/scripts/lib/npm-update-leaf-contract.mjs +171 -0
- package/all/copy-overwrite/scripts/lib/npm-update-leaf.mjs +194 -0
- package/all/copy-overwrite/scripts/lib/npm-update-native-process.mjs +155 -0
- package/all/copy-overwrite/scripts/lib/npm-update-npm.mjs +190 -0
- package/all/copy-overwrite/scripts/lib/npm-update-object.mjs +112 -0
- package/all/copy-overwrite/scripts/lib/npm-update-orchestrator.mjs +273 -0
- package/all/copy-overwrite/scripts/lib/npm-update-owner.mjs +292 -0
- package/all/copy-overwrite/scripts/lib/npm-update-prepare.mjs +263 -0
- package/all/copy-overwrite/scripts/lib/npm-update-process-core.mjs +179 -0
- package/all/copy-overwrite/scripts/lib/npm-update-process.mjs +16 -0
- package/all/copy-overwrite/scripts/lib/npm-update-publication.mjs +258 -0
- package/all/copy-overwrite/scripts/lib/npm-update-publish.mjs +258 -0
- package/all/copy-overwrite/scripts/lib/npm-update-quality.mjs +153 -0
- package/all/copy-overwrite/scripts/lib/npm-update-recovery.mjs +264 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime-archive.mjs +204 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime-graph.mjs +185 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime-transport.mjs +221 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime.mjs +284 -0
- package/all/copy-overwrite/scripts/lib/npm-update-tool-launcher.mjs +287 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-environment.mjs +161 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-inspection.mjs +142 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-lifecycle.mjs +256 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-policy.mjs +266 -0
- package/all/copy-overwrite/scripts/lib/process-tree-runner.mjs +230 -36
- package/all/copy-overwrite/scripts/lisa-automation-provenance.mjs +161 -0
- package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +89 -5
- package/all/copy-overwrite/scripts/lisa-npm-updater.mjs +204 -0
- package/all/copy-overwrite/scripts/lisa-run-gates.mjs +48 -123
- package/all/copy-overwrite/scripts/lisa-work-item.mjs +107 -41
- package/all/copy-overwrite/scripts/npm-updater-gate-runtime.json +1 -0
- package/all/copy-overwrite/scripts/npm-updater-gate-supervisor.c +121 -0
- package/all/copy-overwrite/scripts/npm-updater-gate.Dockerfile +49 -0
- package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +1085 -0
- package/dist/codex/scripts/block-no-verify.sh +89 -5
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +598 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +157 -11
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/block-no-verify.sh +89 -5
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/hooks/block-no-verify.agy.sh +89 -5
- package/plugins/lisa/hooks/block-no-verify.sh +89 -5
- package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +89 -5
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/hooks/block-no-verify.sh +89 -5
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-no-verify.sh +89 -5
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/hooks/block-no-verify.agy.sh +89 -5
- package/plugins/src/base/hooks/block-no-verify.sh +89 -5
- package/scripts/check-shipped-surface-removals.mjs +96 -24
- package/scripts/generate-npm-updater-helper-graph.mjs +184 -0
- package/scripts/two-channel-couplings.json +31 -7
- package/typescript/copy-contents/.husky/commit-msg +18 -0
- package/typescript/copy-overwrite/.prettierignore +2 -0
|
@@ -0,0 +1,273 @@
|
|
|
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 hooks own Git/control state; only qualified canonical interpreters stay in the controller. */
|
|
5
|
+
import {
|
|
6
|
+
mkdirSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
realpathSync,
|
|
9
|
+
lstatSync,
|
|
10
|
+
readdirSync,
|
|
11
|
+
unlinkSync,
|
|
12
|
+
openSync,
|
|
13
|
+
closeSync,
|
|
14
|
+
fsyncSync,
|
|
15
|
+
constants,
|
|
16
|
+
renameSync,
|
|
17
|
+
existsSync,
|
|
18
|
+
} from "node:fs";
|
|
19
|
+
import { randomBytes } from "node:crypto";
|
|
20
|
+
import { join, resolve } from "node:path";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { required, keys } from "./npm-update-contract.mjs";
|
|
23
|
+
import { readBytes, readJson, writeJson } from "./npm-update-process.mjs";
|
|
24
|
+
import { sha256 } from "./github-attestation-verifier.mjs";
|
|
25
|
+
import { canonicalJson } from "../lisa-automation-provenance.mjs";
|
|
26
|
+
import { controllerGraph, configuredRoute } from "./npm-update-gate-hooks.mjs";
|
|
27
|
+
export { controllerGraph } from "./npm-update-gate-hooks.mjs";
|
|
28
|
+
|
|
29
|
+
const INTERPRETERS = new Set(["node", "shell", "bash"]);
|
|
30
|
+
const WRAPPERS = {
|
|
31
|
+
node: "node",
|
|
32
|
+
npm: "npm",
|
|
33
|
+
bun: "bun",
|
|
34
|
+
ruby: "ruby",
|
|
35
|
+
bundle: "bundle",
|
|
36
|
+
bash: "bash",
|
|
37
|
+
sh: "shell",
|
|
38
|
+
gh: "gh",
|
|
39
|
+
gitleaks: "gitleaks",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** Registration is private and flushed before any namespace is created or started. */
|
|
43
|
+
function flushRegistration(file) {
|
|
44
|
+
const fd = openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
45
|
+
try {
|
|
46
|
+
fsyncSync(fd);
|
|
47
|
+
} finally {
|
|
48
|
+
closeSync(fd);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function registryRoot(root) {
|
|
53
|
+
const stat = lstatSync(root);
|
|
54
|
+
required(
|
|
55
|
+
stat.isDirectory() &&
|
|
56
|
+
!stat.isSymbolicLink() &&
|
|
57
|
+
realpathSync(root) === root &&
|
|
58
|
+
stat.uid === process.getuid() &&
|
|
59
|
+
(stat.mode & 0o077) === 0,
|
|
60
|
+
"namespace registry root is not private owned storage"
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Even an unstarted Docker object has an immutable owned name before creation. */
|
|
65
|
+
export function registerNamespace(boundary) {
|
|
66
|
+
registryRoot(boundary.root);
|
|
67
|
+
required(
|
|
68
|
+
/^[a-f0-9]{48}$/.test(boundary.nonce) &&
|
|
69
|
+
/^sha256:[a-f0-9]{64}$/.test(boundary.image) &&
|
|
70
|
+
["linux/arm64", "linux/amd64"].includes(boundary.platform) &&
|
|
71
|
+
Number.isSafeInteger(boundary.deadlineMs) &&
|
|
72
|
+
boundary.deadlineMs >= 1 &&
|
|
73
|
+
boundary.deadlineMs <= 1_800_000,
|
|
74
|
+
"invalid namespace registration identity/deadline"
|
|
75
|
+
);
|
|
76
|
+
const suffix = randomBytes(8).toString("hex");
|
|
77
|
+
const record = {
|
|
78
|
+
version: 1,
|
|
79
|
+
name: `lisa-npm-${boundary.nonce}-${suffix}`,
|
|
80
|
+
id: null,
|
|
81
|
+
nonce: boundary.nonce,
|
|
82
|
+
image: boundary.image,
|
|
83
|
+
platform: boundary.platform,
|
|
84
|
+
callerPid: process.pid,
|
|
85
|
+
deadlineMs: boundary.deadlineMs,
|
|
86
|
+
createdAt: Date.now(),
|
|
87
|
+
};
|
|
88
|
+
const file = writeJson(
|
|
89
|
+
join(boundary.root, `namespace-${suffix}.json`),
|
|
90
|
+
record
|
|
91
|
+
);
|
|
92
|
+
flushRegistration(file);
|
|
93
|
+
flushRegistration(boundary.root);
|
|
94
|
+
return { file, record };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function namespaceRecord(file) {
|
|
98
|
+
const record = readJson(file, 16_384);
|
|
99
|
+
keys(record, [
|
|
100
|
+
"version",
|
|
101
|
+
"name",
|
|
102
|
+
"id",
|
|
103
|
+
"nonce",
|
|
104
|
+
"image",
|
|
105
|
+
"platform",
|
|
106
|
+
"callerPid",
|
|
107
|
+
"deadlineMs",
|
|
108
|
+
"createdAt",
|
|
109
|
+
]);
|
|
110
|
+
required(
|
|
111
|
+
record.version === 1 &&
|
|
112
|
+
typeof record.nonce === "string" &&
|
|
113
|
+
/^[a-f0-9]{48}$/.test(record.nonce) &&
|
|
114
|
+
record.name ===
|
|
115
|
+
`lisa-npm-${record.nonce}-${file.match(/namespace-([a-f0-9]{16})\.json$/)?.[1]}` &&
|
|
116
|
+
/^sha256:[a-f0-9]{64}$/.test(record.image) &&
|
|
117
|
+
["linux/arm64", "linux/amd64"].includes(record.platform),
|
|
118
|
+
"namespace registry identity differs"
|
|
119
|
+
);
|
|
120
|
+
required(
|
|
121
|
+
record.id === null ||
|
|
122
|
+
(typeof record.id === "string" && /^[a-f0-9]{64}$/.test(record.id)),
|
|
123
|
+
"invalid registered namespace ID"
|
|
124
|
+
);
|
|
125
|
+
required(
|
|
126
|
+
[record.callerPid, record.deadlineMs, record.createdAt].every(
|
|
127
|
+
value => Number.isSafeInteger(value) && value > 0
|
|
128
|
+
) && record.deadlineMs <= 1_800_000,
|
|
129
|
+
"invalid namespace registry process/deadline"
|
|
130
|
+
);
|
|
131
|
+
return record;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** An actual daemon-returned ID is assigned once before start, never rebound on retry. */
|
|
135
|
+
export function assignNamespaceId(file, id) {
|
|
136
|
+
const record = namespaceRecord(file);
|
|
137
|
+
required(
|
|
138
|
+
record.id === null && typeof id === "string" && /^[a-f0-9]{64}$/.test(id),
|
|
139
|
+
"namespace identity is already assigned or invalid"
|
|
140
|
+
);
|
|
141
|
+
const temporary = writeJson(`${file}.id-${randomBytes(8).toString("hex")}`, {
|
|
142
|
+
...record,
|
|
143
|
+
id,
|
|
144
|
+
});
|
|
145
|
+
try {
|
|
146
|
+
flushRegistration(temporary);
|
|
147
|
+
required(
|
|
148
|
+
sha256(readBytes(file, 16_384)) === sha256(`${canonicalJson(record)}\n`),
|
|
149
|
+
"namespace registration changed before ID assignment"
|
|
150
|
+
);
|
|
151
|
+
renameSync(temporary, file);
|
|
152
|
+
flushRegistration(resolve(file, ".."));
|
|
153
|
+
required(
|
|
154
|
+
namespaceRecord(file).id === id,
|
|
155
|
+
"namespace ID assignment readback differs"
|
|
156
|
+
);
|
|
157
|
+
} finally {
|
|
158
|
+
if (existsSync(temporary)) {
|
|
159
|
+
readBytes(temporary, 16_384);
|
|
160
|
+
unlinkSync(temporary);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Only bounded canonical records inside this positively owned root are recovery input. */
|
|
166
|
+
export function namespaceRecords(root) {
|
|
167
|
+
registryRoot(root);
|
|
168
|
+
const names = readdirSync(root).filter(name =>
|
|
169
|
+
/^namespace-[a-f0-9]{16}\.json$/.test(name)
|
|
170
|
+
);
|
|
171
|
+
required(names.length <= 512, "namespace registry inventory exceeds bound");
|
|
172
|
+
return names.sort().map(name => {
|
|
173
|
+
const file = join(root, name);
|
|
174
|
+
return { file, record: namespaceRecord(file) };
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** The parent removes its registration only after positive daemon namespace absence. */
|
|
179
|
+
export function completeNamespace(file, absent) {
|
|
180
|
+
namespaceRecord(file);
|
|
181
|
+
required(absent === true, "namespace completion requires positive absence");
|
|
182
|
+
unlinkSync(file);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Every trusted import is rechecked before any controller interpreter is admitted. */
|
|
186
|
+
export function currentGraph(graph) {
|
|
187
|
+
const result = {};
|
|
188
|
+
for (const [path, hash] of Object.entries(graph)) {
|
|
189
|
+
required(
|
|
190
|
+
path.startsWith("/") &&
|
|
191
|
+
typeof hash === "string" &&
|
|
192
|
+
/^[a-f0-9]{64}$/.test(hash) &&
|
|
193
|
+
realpathSync(path) === path,
|
|
194
|
+
"trusted helper graph path is malformed or aliased"
|
|
195
|
+
);
|
|
196
|
+
result[path] = sha256(readBytes(path, 1_048_576, false));
|
|
197
|
+
}
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** A matching entry alone cannot bless a changed transitive import or a loader override. */
|
|
202
|
+
export function controllerRoute(
|
|
203
|
+
tool,
|
|
204
|
+
args,
|
|
205
|
+
graph,
|
|
206
|
+
observed,
|
|
207
|
+
cwd,
|
|
208
|
+
routes = [],
|
|
209
|
+
environment = {}
|
|
210
|
+
) {
|
|
211
|
+
if (!INTERPRETERS.has(tool)) return "worker";
|
|
212
|
+
required(
|
|
213
|
+
!args.some(arg =>
|
|
214
|
+
/^(?:-r|--require|--import|--(?:experimental-)?loader)(?:=|$)/.test(arg)
|
|
215
|
+
),
|
|
216
|
+
"unqualified controller loader route"
|
|
217
|
+
);
|
|
218
|
+
const entry =
|
|
219
|
+
typeof args[0] === "string" && cwd && !args[0].startsWith("-")
|
|
220
|
+
? resolve(cwd, args[0])
|
|
221
|
+
: args[0];
|
|
222
|
+
const expected = controllerGraph(graph, routes);
|
|
223
|
+
const configured = configuredRoute(tool, args, routes, cwd, environment);
|
|
224
|
+
if (
|
|
225
|
+
!configured &&
|
|
226
|
+
(typeof entry !== "string" || !Object.hasOwn(graph, entry))
|
|
227
|
+
)
|
|
228
|
+
return "worker";
|
|
229
|
+
required(
|
|
230
|
+
Object.entries(expected).every(
|
|
231
|
+
([path, hash]) => /^[a-f0-9]{64}$/.test(hash) && observed[path] === hash
|
|
232
|
+
) && Object.keys(observed).length === Object.keys(expected).length,
|
|
233
|
+
"trusted helper/configuration import graph changed"
|
|
234
|
+
);
|
|
235
|
+
return "controller";
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Single quotes preserve fixed caller paths; candidate arguments enter only through literal "$@". */
|
|
239
|
+
function quote(value) {
|
|
240
|
+
required(
|
|
241
|
+
typeof value === "string" && !value.includes("\0") && !value.includes("\n"),
|
|
242
|
+
"invalid launcher path"
|
|
243
|
+
);
|
|
244
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** These executables dispatch real tools; no version, hook or verdict is synthesized. */
|
|
248
|
+
export function installLaunchers(
|
|
249
|
+
root,
|
|
250
|
+
context,
|
|
251
|
+
interpreter = process.execPath
|
|
252
|
+
) {
|
|
253
|
+
const stat = lstatSync(root);
|
|
254
|
+
required(
|
|
255
|
+
stat.isDirectory() &&
|
|
256
|
+
!stat.isSymbolicLink() &&
|
|
257
|
+
stat.uid === process.getuid() &&
|
|
258
|
+
(stat.mode & 0o077) === 0 &&
|
|
259
|
+
context.root === root,
|
|
260
|
+
"launcher root is not private controller storage"
|
|
261
|
+
);
|
|
262
|
+
const bin = join(root, "bin");
|
|
263
|
+
mkdirSync(bin, { mode: 0o700 });
|
|
264
|
+
const config = writeJson(join(root, "launcher.json"), context);
|
|
265
|
+
const entry = fileURLToPath(
|
|
266
|
+
new URL("./npm-update-tool-launcher.mjs", import.meta.url)
|
|
267
|
+
);
|
|
268
|
+
for (const [name, tool] of Object.entries(WRAPPERS)) {
|
|
269
|
+
const script = `#!/bin/sh\nexec ${quote(resolve(interpreter))} ${quote(entry)} --context ${quote(config)} --tool ${quote(tool)} -- "$@"\n`;
|
|
270
|
+
writeFileSync(join(bin, name), script, { mode: 0o700, flag: "wx" });
|
|
271
|
+
}
|
|
272
|
+
return bin;
|
|
273
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
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
|
+
/** Released lifecycle and current native registration qualify a local Lisa owner. */
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { existsSync, lstatSync } from "node:fs";
|
|
7
|
+
import { runNpm, effectiveEngineStrict } from "./npm-update-npm.mjs";
|
|
8
|
+
import { sha256 } from "./github-attestation-verifier.mjs";
|
|
9
|
+
import { qualifiedHelperFiles, helperOwner } from "./npm-update-helper.mjs";
|
|
10
|
+
import { SECTIONS, required, UpdaterError } from "./npm-update-contract.mjs";
|
|
11
|
+
import {
|
|
12
|
+
runProcess,
|
|
13
|
+
withPrivateRoot,
|
|
14
|
+
readBytes,
|
|
15
|
+
} from "./npm-update-process-core.mjs";
|
|
16
|
+
|
|
17
|
+
/** A genuine second clone keeps released lifecycle effects out of the source checkout. */
|
|
18
|
+
const PLUGIN_HOOKS = "plugins/lisa/.codex-plugin/hooks.json";
|
|
19
|
+
|
|
20
|
+
/** Native registration is data; no candidate session hook is executed. */
|
|
21
|
+
export function nativeHooksEnabled(native) {
|
|
22
|
+
// This bounded native registration shape refuses multiline TOML strings.
|
|
23
|
+
if (native.includes('"""') || native.includes("'''")) return false;
|
|
24
|
+
const lines = native.split("\n").map(line => line.split("#", 1)[0].trim());
|
|
25
|
+
const begin = lines.indexOf("[features]");
|
|
26
|
+
if (begin < 0 || begin !== lines.lastIndexOf("[features]")) return false;
|
|
27
|
+
const tail = lines.slice(begin + 1);
|
|
28
|
+
const end = tail.findIndex(line => line.startsWith("["));
|
|
29
|
+
const hooks = tail
|
|
30
|
+
.slice(0, end < 0 ? tail.length : end)
|
|
31
|
+
.filter(line => /^hooks\b/.test(line));
|
|
32
|
+
return hooks.length === 1 && /^hooks[ \t]*=[ \t]*true$/.test(hooks[0]);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function ownerCheckout(cwd, app, policy, env) {
|
|
36
|
+
const child = (command, args, options = {}) =>
|
|
37
|
+
command === "npm"
|
|
38
|
+
? runNpm(args, { cwd: app, env, timeout: 1_800_000, ...options })
|
|
39
|
+
: runProcess(command, args, {
|
|
40
|
+
cwd: app,
|
|
41
|
+
env,
|
|
42
|
+
timeout: 1_800_000,
|
|
43
|
+
...options,
|
|
44
|
+
});
|
|
45
|
+
await runProcess("git", ["clone", "--no-local", cwd, app], {
|
|
46
|
+
env,
|
|
47
|
+
timeout: 120_000,
|
|
48
|
+
});
|
|
49
|
+
await child("git", [
|
|
50
|
+
"checkout",
|
|
51
|
+
"--detach",
|
|
52
|
+
(await runProcess("git", ["rev-parse", "HEAD"], { cwd, env })).stdout
|
|
53
|
+
.toString()
|
|
54
|
+
.trim(),
|
|
55
|
+
]);
|
|
56
|
+
await child("git", [
|
|
57
|
+
"remote",
|
|
58
|
+
"set-url",
|
|
59
|
+
"origin",
|
|
60
|
+
`https://github.com/${policy.repository}.git`,
|
|
61
|
+
]);
|
|
62
|
+
return child;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Released Codex registration is checked as data; no session hook is invoked. */
|
|
66
|
+
async function localSessionOwner(app, installed, config) {
|
|
67
|
+
const read = file => JSON.parse(readBytes(file, 1_048_576, false));
|
|
68
|
+
const adopted = read(join(app, ".lisa.config.json"));
|
|
69
|
+
required(
|
|
70
|
+
["codex", "fleet"].includes(adopted.harness) &&
|
|
71
|
+
config.harness === adopted.harness &&
|
|
72
|
+
adopted.autoUpdate !== false,
|
|
73
|
+
"local Lisa session owner requires a qualified configured runtime"
|
|
74
|
+
);
|
|
75
|
+
const marketplace = read(join(app, ".agents/plugins/marketplace.json"));
|
|
76
|
+
const entries = marketplace.plugins?.filter(plugin => plugin.name === "lisa");
|
|
77
|
+
required(
|
|
78
|
+
entries?.length === 1 &&
|
|
79
|
+
entries[0].source?.source === "local" &&
|
|
80
|
+
entries[0].source?.path ===
|
|
81
|
+
"./node_modules/@codyswann/lisa/plugins/lisa" &&
|
|
82
|
+
entries[0].policy?.installation === "INSTALLED_BY_DEFAULT",
|
|
83
|
+
"current installed Lisa plugin registration differs"
|
|
84
|
+
);
|
|
85
|
+
const manifest = read(
|
|
86
|
+
join(installed, "plugins/lisa/.codex-plugin/plugin.json")
|
|
87
|
+
);
|
|
88
|
+
required(
|
|
89
|
+
manifest.hooks === "./.codex-plugin/hooks.json",
|
|
90
|
+
"released plugin hook surface differs"
|
|
91
|
+
);
|
|
92
|
+
const catalog = join(installed, PLUGIN_HOOKS);
|
|
93
|
+
const helper = await qualifiedHelperFiles(config, [
|
|
94
|
+
PLUGIN_HOOKS,
|
|
95
|
+
"plugins/lisa/hooks/auto-update.sh",
|
|
96
|
+
]);
|
|
97
|
+
required(
|
|
98
|
+
readBytes(catalog, 1_048_576, false).equals(helper.bytes.get(PLUGIN_HOOKS)),
|
|
99
|
+
"released session catalog differs from immutable helper"
|
|
100
|
+
);
|
|
101
|
+
const hooks = read(catalog).hooks?.SessionStart;
|
|
102
|
+
required(
|
|
103
|
+
Array.isArray(hooks) &&
|
|
104
|
+
hooks.some(
|
|
105
|
+
group =>
|
|
106
|
+
group.matcher === "startup" &&
|
|
107
|
+
group.hooks?.some(
|
|
108
|
+
hook =>
|
|
109
|
+
hook.type === "command" &&
|
|
110
|
+
hook.command === "${PLUGIN_ROOT}/hooks/auto-update.sh"
|
|
111
|
+
)
|
|
112
|
+
),
|
|
113
|
+
"actual released auto-update SessionStart is absent"
|
|
114
|
+
);
|
|
115
|
+
const script = "plugins/lisa/hooks/auto-update.sh";
|
|
116
|
+
required(
|
|
117
|
+
readBytes(join(installed, script), 1_048_576, false).equals(
|
|
118
|
+
helper.bytes.get(script)
|
|
119
|
+
),
|
|
120
|
+
"released local owner hook differs"
|
|
121
|
+
);
|
|
122
|
+
const native = readBytes(
|
|
123
|
+
join(app, ".codex/config.toml"),
|
|
124
|
+
65_536,
|
|
125
|
+
false
|
|
126
|
+
).toString("utf8");
|
|
127
|
+
required(nativeHooksEnabled(native), "current native hooks are not enabled");
|
|
128
|
+
return "codex-plugin-session-start";
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Genuine apply diagnostics require committed reconciliation rather than receipt filtering. */
|
|
132
|
+
export function validateOwnerReceipt(receipt, version, status, harness) {
|
|
133
|
+
required(
|
|
134
|
+
receipt.schema_version === 1,
|
|
135
|
+
"genuine full-apply receipt schema differs"
|
|
136
|
+
);
|
|
137
|
+
required(
|
|
138
|
+
receipt.apply_mode === "full",
|
|
139
|
+
"genuine full-apply receipt mode differs"
|
|
140
|
+
);
|
|
141
|
+
required(
|
|
142
|
+
receipt.lisa_version === version,
|
|
143
|
+
"genuine full-apply receipt version differs"
|
|
144
|
+
);
|
|
145
|
+
required(
|
|
146
|
+
["codex", "fleet"].includes(harness) && receipt.harness === harness,
|
|
147
|
+
"genuine full-apply receipt harness differs from configured Codex channel"
|
|
148
|
+
);
|
|
149
|
+
if (
|
|
150
|
+
!Array.isArray(receipt.stale_paths) ||
|
|
151
|
+
receipt.stale_paths.length ||
|
|
152
|
+
status
|
|
153
|
+
) {
|
|
154
|
+
const error = new UpdaterError(
|
|
155
|
+
!Array.isArray(receipt.stale_paths) || receipt.stale_paths.length
|
|
156
|
+
? "genuine full-apply receipt has stale paths"
|
|
157
|
+
: "full apply requires committed host reconciliation first"
|
|
158
|
+
);
|
|
159
|
+
error.qualification = {
|
|
160
|
+
applyExit: 0,
|
|
161
|
+
schema: receipt.schema_version,
|
|
162
|
+
mode: receipt.apply_mode,
|
|
163
|
+
version: receipt.lisa_version,
|
|
164
|
+
stalePaths: receipt.stale_paths,
|
|
165
|
+
...(status ? { status } : {}),
|
|
166
|
+
};
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
return receipt;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** A fresh host proves the released local owner; an ignored foreign receipt never grants authority. */
|
|
173
|
+
export async function qualifyLisaOwner(
|
|
174
|
+
cwd,
|
|
175
|
+
before,
|
|
176
|
+
policy,
|
|
177
|
+
config,
|
|
178
|
+
suppliedEngineStrict
|
|
179
|
+
) {
|
|
180
|
+
const engineStrict =
|
|
181
|
+
suppliedEngineStrict ?? (await effectiveEngineStrict(cwd));
|
|
182
|
+
const declares = SECTIONS.some(
|
|
183
|
+
section => before[section]?.["@codyswann/lisa"]
|
|
184
|
+
);
|
|
185
|
+
if (!declares) {
|
|
186
|
+
required(policy.lisaOwner === "absent", "Lisa owner contradicts manifest");
|
|
187
|
+
return { status: "absent" };
|
|
188
|
+
}
|
|
189
|
+
required(
|
|
190
|
+
policy.lisaOwner === "verified-local-full-apply" &&
|
|
191
|
+
config.autoUpdate !== false &&
|
|
192
|
+
["codex", "fleet"].includes(config.harness),
|
|
193
|
+
"adopt the enabled local Lisa owner before generic updates"
|
|
194
|
+
);
|
|
195
|
+
return withPrivateRoot(async (root, env) => {
|
|
196
|
+
const app = join(root, "qualification");
|
|
197
|
+
const child = await ownerCheckout(cwd, app, policy, env);
|
|
198
|
+
await child("npm", [
|
|
199
|
+
"ci",
|
|
200
|
+
"--ignore-scripts",
|
|
201
|
+
"--no-audit",
|
|
202
|
+
"--no-fund",
|
|
203
|
+
...(engineStrict ? ["--engine-strict"] : []),
|
|
204
|
+
]);
|
|
205
|
+
const installed = join(app, "node_modules/@codyswann/lisa");
|
|
206
|
+
const metadata = JSON.parse(
|
|
207
|
+
readBytes(join(installed, "package.json"), 1_048_576, false)
|
|
208
|
+
);
|
|
209
|
+
await releasedLisaIdentity(child, installed, metadata, config);
|
|
210
|
+
await child(
|
|
211
|
+
process.execPath,
|
|
212
|
+
[join(installed, "dist/index.js"), "apply", ".", "--yes", "--full-apply"],
|
|
213
|
+
{ env: { ...env, CI: "true", LISA_BOOTSTRAP: "1" } }
|
|
214
|
+
);
|
|
215
|
+
const receipt = JSON.parse(
|
|
216
|
+
readBytes(join(app, ".lisa/apply-receipt.json"), 1_048_576, false)
|
|
217
|
+
);
|
|
218
|
+
validateOwnerReceipt(receipt, metadata.version, "", config.harness);
|
|
219
|
+
const status = (
|
|
220
|
+
await child("git", ["status", "--porcelain", "--untracked-files=all"])
|
|
221
|
+
).stdout
|
|
222
|
+
.toString()
|
|
223
|
+
.trim();
|
|
224
|
+
validateOwnerReceipt(receipt, metadata.version, status, config.harness);
|
|
225
|
+
return {
|
|
226
|
+
status: "qualified-local-owner",
|
|
227
|
+
version: metadata.version,
|
|
228
|
+
gitHead: config.automationProvenance.signerDigest,
|
|
229
|
+
applyMode: receipt.apply_mode,
|
|
230
|
+
baselineUnchanged: true,
|
|
231
|
+
sessionHook: await localSessionOwner(app, installed, config),
|
|
232
|
+
};
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Verify public release and the actual installed session hook before executing package code. */
|
|
237
|
+
export async function releasedLisaIdentity(child, installed, metadata, config) {
|
|
238
|
+
required(
|
|
239
|
+
metadata.name === "@codyswann/lisa" &&
|
|
240
|
+
metadata.bin?.lisa === "dist/index.js",
|
|
241
|
+
"released Lisa executable identity differs"
|
|
242
|
+
);
|
|
243
|
+
const result = await child("npm", [
|
|
244
|
+
"view",
|
|
245
|
+
`@codyswann/lisa@${metadata.version}`,
|
|
246
|
+
"--json",
|
|
247
|
+
]);
|
|
248
|
+
const registry = JSON.parse(result.stdout.toString());
|
|
249
|
+
required(
|
|
250
|
+
registry.name === metadata.name &&
|
|
251
|
+
registry.version === metadata.version &&
|
|
252
|
+
registry.gitHead === config.automationProvenance?.signerDigest &&
|
|
253
|
+
typeof registry.dist?.integrity === "string",
|
|
254
|
+
"approved released helper identity is unavailable"
|
|
255
|
+
);
|
|
256
|
+
const lock = JSON.parse(
|
|
257
|
+
readBytes(join(installed, "../../../package-lock.json"), 1_048_576, false)
|
|
258
|
+
);
|
|
259
|
+
required(
|
|
260
|
+
lock.packages["node_modules/@codyswann/lisa"]?.integrity ===
|
|
261
|
+
registry.dist.integrity,
|
|
262
|
+
"installed Lisa registry integrity differs"
|
|
263
|
+
);
|
|
264
|
+
const hook = join(installed, "plugins/lisa/hooks/auto-update.mjs");
|
|
265
|
+
const hold = join(
|
|
266
|
+
installed,
|
|
267
|
+
"plugins/lisa/scripts/intake-blocker-reprobe.mjs"
|
|
268
|
+
);
|
|
269
|
+
required(
|
|
270
|
+
[hook, hold].every(
|
|
271
|
+
file => existsSync(file) && !lstatSync(file).isSymbolicLink()
|
|
272
|
+
),
|
|
273
|
+
"released local updater or hold classifier is missing"
|
|
274
|
+
);
|
|
275
|
+
const sourcePackage = helperOwner().metadata;
|
|
276
|
+
required(
|
|
277
|
+
sourcePackage.version === metadata.version,
|
|
278
|
+
"installed Lisa differs from approved helper version"
|
|
279
|
+
);
|
|
280
|
+
const members = [
|
|
281
|
+
"plugins/lisa/hooks/auto-update.mjs",
|
|
282
|
+
"plugins/lisa/scripts/intake-blocker-reprobe.mjs",
|
|
283
|
+
"plugins/lisa/scripts/intake-prework-denominator.mjs",
|
|
284
|
+
];
|
|
285
|
+
const trusted = await qualifiedHelperFiles(config, members);
|
|
286
|
+
for (const relative of members)
|
|
287
|
+
required(
|
|
288
|
+
sha256(readBytes(join(installed, relative), 1_048_576, false)) ===
|
|
289
|
+
sha256(trusted.bytes.get(relative)),
|
|
290
|
+
"packaged helper import differs from immutable source"
|
|
291
|
+
);
|
|
292
|
+
}
|