@codyswann/lisa 4.50.5 → 4.50.7
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/dist/core/nightly-e2e-guard-behavior-certificate.d.ts +5 -0
- package/dist/core/nightly-e2e-guard-behavior-certificate.d.ts.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +9 -2
- package/dist/core/nightly-e2e-guard-behavior-certificate.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +4 -3
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- 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/.codex-plugin/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +120 -14
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/toolchain.mjs +43 -3
- package/plugins/lisa/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +120 -14
- package/plugins/lisa/skills/lisa-setup-remote-env/scripts/toolchain.mjs +43 -3
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +120 -14
- package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/toolchain.mjs +43 -3
- 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/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +120 -14
- package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/toolchain.mjs +43 -3
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +120 -14
- package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/toolchain.mjs +43 -3
- 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/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +120 -14
- package/plugins/src/base/skills/lisa-setup-remote-env/scripts/toolchain.mjs +43 -3
- package/scripts/generate-nightly-e2e-guard-certificate.mjs +10 -0
|
@@ -15,9 +15,25 @@
|
|
|
15
15
|
* check that forgot `minVersion`, so a container verified clean against a node
|
|
16
16
|
* older than the manifest demanded. One function, one verdict, no drift.
|
|
17
17
|
*
|
|
18
|
-
* **
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* **Three verdicts, and the third one was learned the hard way.** The original
|
|
19
|
+
* header here claimed two sufficed: `preflight-secrets` needs `unreachable`
|
|
20
|
+
* because a vault can fail to be asked, and "probing a local binary cannot: it
|
|
21
|
+
* is present or it is not." That sentence was true of probing and false of this
|
|
22
|
+
* module, because one branch of `planToolchain` never probed at all — an
|
|
23
|
+
* install entry with no pin for the running platform short-circuited to
|
|
24
|
+
* `invalid` before the binary was looked for. So a `bws` that was installed, on
|
|
25
|
+
* PATH, and resolving secrets in the very same session was reported under
|
|
26
|
+
* "These need you", next to an instruction to route the work to blocked. Four
|
|
27
|
+
* operator sessions believed it.
|
|
28
|
+
*
|
|
29
|
+
* The third tier is `unverified`: present and usable, but with no pin for this
|
|
30
|
+
* platform, so Lisa can neither reinstall it nor say where it came from. That
|
|
31
|
+
* is a real thing to know — an unpinned binary is a supply-chain claim nobody
|
|
32
|
+
* checked — and it is emphatically not a reason to stop. Availability and
|
|
33
|
+
* provenance are two questions, and answering the first with the second is the
|
|
34
|
+
* defect this tier exists to prevent recurring.
|
|
35
|
+
*
|
|
36
|
+
* The analogous trap — a tool whose version cannot be parsed — is
|
|
21
37
|
* already handled correctly upstream, because `planRequired` compares
|
|
22
38
|
* `found.version ?? "0"` and an unknown version loses every `minVersion`
|
|
23
39
|
* comparison. It fails closed without needing a verdict of its own.
|
|
@@ -34,12 +50,44 @@ import { join } from "node:path";
|
|
|
34
50
|
import { fileURLToPath } from "node:url";
|
|
35
51
|
|
|
36
52
|
import { probe, readRemoteEnvConfig } from "./setup-remote-env.mjs";
|
|
37
|
-
import {
|
|
53
|
+
import {
|
|
54
|
+
compareVersions,
|
|
55
|
+
currentPlatform,
|
|
56
|
+
planToolchain,
|
|
57
|
+
} from "./toolchain.mjs";
|
|
38
58
|
import { toolFloor } from "./tool-floor.mjs";
|
|
39
59
|
|
|
40
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* Plan actions that mean the agent cannot use the tool right now.
|
|
62
|
+
*
|
|
63
|
+
* `invalid` is here because most of its causes really are stops — an unpinned
|
|
64
|
+
* version, a malformed `platforms` map, an unknown install method. The one
|
|
65
|
+
* cause that is not, an entry with no pin for this platform whose binary is
|
|
66
|
+
* nonetheless on PATH, is lifted out below before this set is applied.
|
|
67
|
+
*/
|
|
41
68
|
const BLOCKING = new Set(["missing", "invalid"]);
|
|
42
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Whether an unpinnable entry is nonetheless usable on this machine.
|
|
72
|
+
*
|
|
73
|
+
* Presence-only, and deliberately not a skip. Dropping the entry entirely would
|
|
74
|
+
* report clean for a tool that is neither pinned nor installed, which is the
|
|
75
|
+
* vacuous green this module refuses everywhere else. So the probe still decides
|
|
76
|
+
* — it just gets asked, which on this branch it previously never was.
|
|
77
|
+
*
|
|
78
|
+
* A declared `minVersion` still bites. The pin is unusable here (it names an
|
|
79
|
+
* artifact for another platform), but a minimum is a statement about the tool
|
|
80
|
+
* rather than about the download, and a binary below it cannot do the work.
|
|
81
|
+
* @param {object} step One `planToolchain` decision.
|
|
82
|
+
* @param {Record<string, string>} minVersions Declared minimums by tool name.
|
|
83
|
+
* @returns {boolean} Whether the tool is present and good enough to use.
|
|
84
|
+
*/
|
|
85
|
+
function usableWithoutPin(step, minVersions) {
|
|
86
|
+
if (!step.unpinnedForPlatform || !step.found?.present) return false;
|
|
87
|
+
const floor = minVersions[step.name];
|
|
88
|
+
return !floor || compareVersions(step.found.version ?? "0", floor) >= 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
43
91
|
/**
|
|
44
92
|
* Read the whole config, for the derivations that live outside `remoteEnv`.
|
|
45
93
|
*
|
|
@@ -109,7 +157,7 @@ export function mergeFloor(tools, floor) {
|
|
|
109
157
|
* @param {object} [remoteEnv] Parsed `remoteEnv` block.
|
|
110
158
|
* @param {Function} [versionProbe] Version probe, injected for tests.
|
|
111
159
|
* @param {string} [platform] Platform key, injected for tests.
|
|
112
|
-
* @returns {{verdict: string, blocked: Array<{name: string, action: string, reason: string}>, installable: Array<{name: string, action: string, reason: string}>, reasons: Record<string, string>}}
|
|
160
|
+
* @returns {{verdict: string, blocked: Array<{name: string, action: string, reason: string}>, installable: Array<{name: string, action: string, reason: string}>, unverified: Array<{name: string, action: string, reason: string}>, reasons: Record<string, string>}}
|
|
113
161
|
*/
|
|
114
162
|
export function preflightTools(
|
|
115
163
|
config = readConfigRoot(),
|
|
@@ -122,19 +170,59 @@ export function preflightTools(
|
|
|
122
170
|
toolFloor(config)
|
|
123
171
|
);
|
|
124
172
|
if (!(tools.require ?? []).length && !(tools.install ?? []).length) {
|
|
125
|
-
return {
|
|
173
|
+
return {
|
|
174
|
+
verdict: "ok",
|
|
175
|
+
blocked: [],
|
|
176
|
+
installable: [],
|
|
177
|
+
unverified: [],
|
|
178
|
+
reasons,
|
|
179
|
+
};
|
|
126
180
|
}
|
|
127
181
|
|
|
128
182
|
// "local" because this runs where the agent is, and it is the surface word
|
|
129
183
|
// `appliesToSurface` already understands — a tool narrowed to ["remote"] is
|
|
130
184
|
// correctly ignored on a laptop rather than reported as missing there.
|
|
131
185
|
const plan = planToolchain(tools, versionProbe, "local", platform);
|
|
132
|
-
const
|
|
186
|
+
const minVersions = Object.fromEntries(
|
|
187
|
+
(tools.install ?? [])
|
|
188
|
+
.filter(tool => tool.minVersion)
|
|
189
|
+
.map(tool => [tool.name, tool.minVersion])
|
|
190
|
+
);
|
|
191
|
+
const usable = new Set(
|
|
192
|
+
plan
|
|
193
|
+
.filter(step => usableWithoutPin(step, minVersions))
|
|
194
|
+
.map(step => step.name)
|
|
195
|
+
);
|
|
196
|
+
// Restated in this module's own vocabulary rather than passed through.
|
|
197
|
+
// `resolvePlatform`'s message is written for someone repairing a manifest —
|
|
198
|
+
// "add a block for darwin-arm64 with its own url and sha256" — and reads as a
|
|
199
|
+
// demand. What the reader here needs is the availability answer first and the
|
|
200
|
+
// provenance caveat second, because only one of the two affects whether they
|
|
201
|
+
// can start work.
|
|
202
|
+
const unverified = plan
|
|
203
|
+
.filter(step => usable.has(step.name))
|
|
204
|
+
.map(step => ({
|
|
205
|
+
...step,
|
|
206
|
+
action: "unverified",
|
|
207
|
+
reason:
|
|
208
|
+
`${step.name} ${step.found.version ?? "(version unknown)"} is on ` +
|
|
209
|
+
`PATH and usable, but the manifest pins no artifact for ${platform}, ` +
|
|
210
|
+
`so Lisa cannot reinstall it or say where it came from`,
|
|
211
|
+
}));
|
|
212
|
+
const blocked = plan.filter(
|
|
213
|
+
step => BLOCKING.has(step.action) && !usable.has(step.name)
|
|
214
|
+
);
|
|
133
215
|
const installable = plan.filter(step => step.action === "install");
|
|
216
|
+
if (blocked.length || installable.length)
|
|
217
|
+
return { verdict: "missing", blocked, installable, unverified, reasons };
|
|
218
|
+
// A note is not a failure and must not exit non-zero, but it does have to be
|
|
219
|
+
// said — an "ok" verdict renders nothing at all, which would drop the only
|
|
220
|
+
// signal this tier exists to carry.
|
|
134
221
|
return {
|
|
135
|
-
verdict:
|
|
222
|
+
verdict: unverified.length ? "notice" : "ok",
|
|
136
223
|
blocked,
|
|
137
224
|
installable,
|
|
225
|
+
unverified,
|
|
138
226
|
reasons,
|
|
139
227
|
};
|
|
140
228
|
}
|
|
@@ -150,11 +238,14 @@ export function reportTools(result) {
|
|
|
150
238
|
// The header tracks the exit code. Only a blocked tool is a failure — one
|
|
151
239
|
// Lisa can install is an action with a command attached, and calling that
|
|
152
240
|
// "FAILED" while exiting zero teaches readers that the word means nothing.
|
|
153
|
-
const
|
|
154
|
-
result.blocked.length
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
241
|
+
const header = () => {
|
|
242
|
+
if (result.blocked.length) return "Tooling preflight FAILED.";
|
|
243
|
+
if (result.installable.length)
|
|
244
|
+
return "Tooling preflight — action available.";
|
|
245
|
+
// Nothing to do and nothing to fix; the note below is the whole message.
|
|
246
|
+
return "Tooling preflight passed, with a note.";
|
|
247
|
+
};
|
|
248
|
+
const lines = [header()];
|
|
158
249
|
if (result.installable.length) {
|
|
159
250
|
lines.push(
|
|
160
251
|
``,
|
|
@@ -183,6 +274,21 @@ export function reportTools(result) {
|
|
|
183
274
|
`blocked with this reason rather than claiming it and stopping partway.`
|
|
184
275
|
);
|
|
185
276
|
}
|
|
277
|
+
if (result.unverified.length) {
|
|
278
|
+
lines.push(
|
|
279
|
+
``,
|
|
280
|
+
`Installed, but Lisa did not put them there and cannot vouch for them:`,
|
|
281
|
+
``
|
|
282
|
+
);
|
|
283
|
+
for (const step of result.unverified) {
|
|
284
|
+
lines.push(` ${step.name} — ${step.reason}`);
|
|
285
|
+
}
|
|
286
|
+
lines.push(
|
|
287
|
+
``,
|
|
288
|
+
`Nothing here blocks your work — these tools are usable right now. Add a`,
|
|
289
|
+
`pin for this platform when you want Lisa able to verify or replace them.`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
186
292
|
return lines.join("\n");
|
|
187
293
|
}
|
|
188
294
|
|
|
@@ -146,6 +146,18 @@ function planInstallable(tool, found, pinIsFloor = false) {
|
|
|
146
146
|
/** Surfaces a manifest entry may name. */
|
|
147
147
|
const KNOWN_SURFACES = new Set(["local", "remote"]);
|
|
148
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Marks the one resolution failure that is a question about *this machine*.
|
|
151
|
+
*
|
|
152
|
+
* A `platforms` map that is not an object is a broken declaration, and nothing
|
|
153
|
+
* on PATH redeems it. A map that simply has no block for the running platform
|
|
154
|
+
* is a different statement: the manifest cannot say what Lisa should place
|
|
155
|
+
* here, which says nothing at all about what is already here. Callers that need
|
|
156
|
+
* to tell those apart get a code rather than a regex over the message, because
|
|
157
|
+
* a message is prose written for an operator and will be rewritten.
|
|
158
|
+
*/
|
|
159
|
+
export const NO_PIN_FOR_PLATFORM = "no-pin-for-platform";
|
|
160
|
+
|
|
149
161
|
/**
|
|
150
162
|
* The platform key a manifest entry is resolved against.
|
|
151
163
|
*
|
|
@@ -197,13 +209,15 @@ export function resolvePlatform(tool, platform = currentPlatform()) {
|
|
|
197
209
|
const block = platforms[platform];
|
|
198
210
|
if (!block) {
|
|
199
211
|
const known = Object.keys(platforms).sort().join(", ");
|
|
200
|
-
|
|
212
|
+
const err = new Error(
|
|
201
213
|
`${tool.name}: no pin for ${platform}.\n` +
|
|
202
214
|
`Declared platforms: ${known || "(none)"}.\n` +
|
|
203
215
|
`Add a block for ${platform} with its own url and sha256, or drop the ` +
|
|
204
216
|
`tool from this surface. Guessing an artifact would defeat the ` +
|
|
205
217
|
`checksum.`
|
|
206
218
|
);
|
|
219
|
+
err.code = NO_PIN_FOR_PLATFORM;
|
|
220
|
+
throw err;
|
|
207
221
|
}
|
|
208
222
|
// The platform block wins over the shared fields, and `platforms` itself is
|
|
209
223
|
// dropped so a resolved entry is indistinguishable from a flat one — that is
|
|
@@ -265,11 +279,17 @@ export function appliesToSurface(tool, surface) {
|
|
|
265
279
|
* `require` entries are deliberately not resolved: they carry a name and a
|
|
266
280
|
* minimum version, nothing platform-specific, and inventing a per-platform shape
|
|
267
281
|
* for them would be ceremony with no artifact behind it.
|
|
282
|
+
*
|
|
283
|
+
* Every entry is probed, including one with no pin for this platform. That
|
|
284
|
+
* entry's verdict is still `invalid`, because provisioning genuinely cannot
|
|
285
|
+
* proceed — but `found` and `unpinnedForPlatform` travel with it so a caller
|
|
286
|
+
* asking about availability rather than provisioning has the answer instead of
|
|
287
|
+
* having to infer one from the other.
|
|
268
288
|
* @param {{require?: object[], install?: object[]}} tools Manifest.
|
|
269
289
|
* @param {(name: string) => {version: string|null, present: boolean}} probe Version probe.
|
|
270
290
|
* @param {string} [surface] Surface being provisioned.
|
|
271
291
|
* @param {string} [platform] Platform key to resolve install entries against.
|
|
272
|
-
* @returns {Array<{name: string, action: string, reason: string, tool?: object}>} Ordered decisions.
|
|
292
|
+
* @returns {Array<{name: string, action: string, reason: string, tool?: object, found?: {version: string|null, present: boolean}, unpinnedForPlatform?: boolean}>} Ordered decisions.
|
|
273
293
|
*/
|
|
274
294
|
export function planToolchain(
|
|
275
295
|
tools,
|
|
@@ -287,7 +307,27 @@ export function planToolchain(
|
|
|
287
307
|
try {
|
|
288
308
|
resolved = resolvePlatform(tool, platform);
|
|
289
309
|
} catch (err) {
|
|
290
|
-
|
|
310
|
+
// Ask the probe anyway, and carry its answer alongside the verdict.
|
|
311
|
+
//
|
|
312
|
+
// The verdict itself is right and does not change: Lisa cannot place an
|
|
313
|
+
// artifact it has no pin for. But "can Lisa provision this here?" and
|
|
314
|
+
// "can the agent use this right now?" are different questions, and
|
|
315
|
+
// skipping the probe on this branch left the second one unanswerable —
|
|
316
|
+
// so `preflight-tools` answered it with the first, and reported a `bws`
|
|
317
|
+
// that was installed, on PATH, and resolving secrets in the same session
|
|
318
|
+
// as a blocker that work had to be routed around.
|
|
319
|
+
//
|
|
320
|
+
// Only the missing-pin case gets a probe result. A malformed `platforms`
|
|
321
|
+
// map is a broken declaration rather than a fact about this machine, and
|
|
322
|
+
// a binary happening to be on PATH does not redeem it.
|
|
323
|
+
plan.push({
|
|
324
|
+
name: tool.name,
|
|
325
|
+
action: "invalid",
|
|
326
|
+
reason: err.message,
|
|
327
|
+
...(err.code === NO_PIN_FOR_PLATFORM
|
|
328
|
+
? { unpinnedForPlatform: true, found: probe(tool.name) }
|
|
329
|
+
: {}),
|
|
330
|
+
});
|
|
291
331
|
continue;
|
|
292
332
|
}
|
|
293
333
|
// The resolved entry travels with the decision so the installer never has to
|
|
@@ -51,6 +51,16 @@ export const RETAINED_RELEASES = Object.freeze([
|
|
|
51
51
|
// refusing to execute bytes it trusted yesterday. Rereading the tag is the
|
|
52
52
|
// only sanctioned way to keep it: the digest is derived, never copied.
|
|
53
53
|
"v4.26.1",
|
|
54
|
+
// The 1.7.0 guard, as the 50 releases from `4.22.13` through `4.26.0`
|
|
55
|
+
// published it. Commit 48c936bb9 moved the workspace guard and regeneration
|
|
56
|
+
// replaced the outgoing row instead of adding beside it, so the digest those
|
|
57
|
+
// 50 shipped was revoked and nothing noticed. Re-measured: 50 tags carrying
|
|
58
|
+
// it, bounded on both sides — v4.22.12 differs, v4.26.1 differs.
|
|
59
|
+
//
|
|
60
|
+
// `v4.17.16` above also certifies a 1.7.0 guard; the two are DIFFERENT bytes
|
|
61
|
+
// under the same contract version, which is why retention is a ref list and
|
|
62
|
+
// not a contract list.
|
|
63
|
+
"v4.26.0",
|
|
54
64
|
]);
|
|
55
65
|
|
|
56
66
|
const digest = bytes => createHash("sha256").update(bytes).digest("hex");
|