@codyswann/lisa 4.29.4 → 4.29.6
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/check-release-package-identity.mjs +128 -5
- package/dist/cli/version.d.ts +13 -5
- package/dist/cli/version.d.ts.map +1 -1
- package/dist/cli/version.js +22 -14
- package/dist/cli/version.js.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +4 -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 +3 -2
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/migrations/ensure-nightly-e2e-workflow-pins.d.ts +27 -8
- package/dist/migrations/ensure-nightly-e2e-workflow-pins.d.ts.map +1 -1
- package/dist/migrations/ensure-nightly-e2e-workflow-pins.js +49 -11
- package/dist/migrations/ensure-nightly-e2e-workflow-pins.js.map +1 -1
- package/package.json +4 -3
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- 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-cursor/.claude-plugin/plugin.json +1 -1
- 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/scripts/check-release-package-identity.mjs +128 -5
|
@@ -17,6 +17,13 @@
|
|
|
17
17
|
* Projects without Lisa's nightly certificate still receive the generic
|
|
18
18
|
* version/commit/tarball checks. When the source certificate exists, its built
|
|
19
19
|
* and packed counterpart becomes mandatory.
|
|
20
|
+
*
|
|
21
|
+
* `checkout` additionally proves the release commit is reachable from a ref a
|
|
22
|
+
* consumer can resolve. A commit that exists but is referenced by nothing is
|
|
23
|
+
* the one failure with no red signal anywhere: tooling stamps it into consumer
|
|
24
|
+
* workflow pins, and a pinned workflow that cannot be resolved never loads —
|
|
25
|
+
* so it runs zero jobs, fails zero jobs, and its run history is
|
|
26
|
+
* indistinguishable from a healthy one.
|
|
20
27
|
* @module scripts/check-release-package-identity
|
|
21
28
|
*/
|
|
22
29
|
import { createHash } from "node:crypto";
|
|
@@ -30,10 +37,35 @@ import {
|
|
|
30
37
|
import path from "node:path";
|
|
31
38
|
import process from "node:process";
|
|
32
39
|
|
|
33
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
boundedExecFileSync,
|
|
42
|
+
rethrowIfChildTimeout,
|
|
43
|
+
} from "./lib/bounded-spawn.mjs";
|
|
34
44
|
import { invokedAsScript } from "./lib/invoked-as-script.mjs";
|
|
35
45
|
|
|
36
46
|
const RELEASE_COMMIT_PATTERN = /^[a-f0-9]{40}$/u;
|
|
47
|
+
const RELEASE_TAG_PATTERN = /^[!-~]+$/u;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Ref namespaces a consumer can actually resolve.
|
|
51
|
+
*
|
|
52
|
+
* Local branches are excluded on purpose. Measuring reachability over all
|
|
53
|
+
* local refs answers "does this commit exist in my clone", not "can a consumer
|
|
54
|
+
* resolve it", and that difference is what made a 235-version outage read as
|
|
55
|
+
* four versions on the first pass.
|
|
56
|
+
*/
|
|
57
|
+
const DURABLE_REF_NAMESPACES = Object.freeze(["refs/tags", "refs/remotes"]);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Ref prefix that does not count as durable reachability.
|
|
61
|
+
*
|
|
62
|
+
* A `refs/tags/backup/*` tag is a byproduct of a history rewrite, not a
|
|
63
|
+
* release artifact. Measured on this repository, a run of 70 published
|
|
64
|
+
* versions was reachable through exactly one such tag and nothing else —
|
|
65
|
+
* alive only until somebody prunes the backup. A publish whose release commit
|
|
66
|
+
* has no anchor but a backup tag is already the failure, one deletion early.
|
|
67
|
+
*/
|
|
68
|
+
const BACKUP_TAG_PREFIX = "refs/tags/backup/";
|
|
37
69
|
const SOURCE_CERTIFICATE = "src/core/nightly-e2e-guard-behavior-certificate.ts";
|
|
38
70
|
const PACKED_CERTIFICATE =
|
|
39
71
|
"package/dist/core/nightly-e2e-guard-behavior-certificate.js";
|
|
@@ -79,6 +111,43 @@ function sha256(bytes) {
|
|
|
79
111
|
return createHash("sha256").update(bytes).digest("hex");
|
|
80
112
|
}
|
|
81
113
|
|
|
114
|
+
/**
|
|
115
|
+
* List the durable refs from which one commit is reachable.
|
|
116
|
+
*
|
|
117
|
+
* Fails closed rather than answering "none" when git cannot be asked: an
|
|
118
|
+
* enumeration that did not run is not evidence of an unreachable commit, and
|
|
119
|
+
* conflating the two would turn a busy machine into a failed release.
|
|
120
|
+
*/
|
|
121
|
+
function durableRefsContaining(root, commit) {
|
|
122
|
+
let output;
|
|
123
|
+
try {
|
|
124
|
+
output = String(
|
|
125
|
+
boundedExecFileSync(
|
|
126
|
+
"git",
|
|
127
|
+
[
|
|
128
|
+
"for-each-ref",
|
|
129
|
+
"--format=%(refname)",
|
|
130
|
+
`--contains=${commit}`,
|
|
131
|
+
...DURABLE_REF_NAMESPACES,
|
|
132
|
+
],
|
|
133
|
+
{ cwd: root, encoding: "utf8", timeout: 120_000 }
|
|
134
|
+
)
|
|
135
|
+
);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
rethrowIfChildTimeout(error);
|
|
138
|
+
mismatch(
|
|
139
|
+
`could not enumerate durable refs containing ${commit} (${error instanceof Error ? error.message : String(error)})`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return Object.freeze(
|
|
143
|
+
output
|
|
144
|
+
.split(/\r?\n/u)
|
|
145
|
+
.map(line => line.trim())
|
|
146
|
+
.filter(Boolean)
|
|
147
|
+
.filter(ref => !ref.startsWith(BACKUP_TAG_PREFIX))
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
82
151
|
/** Read one tar member without extracting paths into the checkout. */
|
|
83
152
|
function tarMember(tarballPath, member) {
|
|
84
153
|
try {
|
|
@@ -137,9 +206,40 @@ export function assertReleaseCommit(releaseCommit) {
|
|
|
137
206
|
}
|
|
138
207
|
|
|
139
208
|
/**
|
|
140
|
-
*
|
|
209
|
+
* Require a release tag that a consumer can be pinned at.
|
|
210
|
+
*
|
|
211
|
+
* The one spelling refused outright is a bare commit SHA. Stamping a commit is
|
|
212
|
+
* the defect this check exists to close: the object survives a history rewrite
|
|
213
|
+
* but its reachability does not, and a caller pinned at an orphaned SHA never
|
|
214
|
+
* loads — zero jobs, so zero failures, so nothing red anywhere. Tag NAMING is
|
|
215
|
+
* otherwise left to the calling project, which is free to use its own prefix.
|
|
216
|
+
* @param {string} tag Release tag supplied by the publishing workflow
|
|
217
|
+
* @returns {string} The validated tag
|
|
218
|
+
*/
|
|
219
|
+
export function assertReleaseTag(tag) {
|
|
220
|
+
if (typeof tag !== "string" || tag === "") {
|
|
221
|
+
mismatch(
|
|
222
|
+
`release tag must be a non-empty string; got ${JSON.stringify(tag)}`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
if (!RELEASE_TAG_PATTERN.test(tag)) {
|
|
226
|
+
mismatch(
|
|
227
|
+
`release tag must be printable non-whitespace characters; got ${JSON.stringify(tag)}`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
if (RELEASE_COMMIT_PATTERN.test(tag.toLowerCase())) {
|
|
231
|
+
mismatch(
|
|
232
|
+
`release tag ${JSON.stringify(tag)} is a bare commit SHA; a consumer pinned at a commit stops loading the workflow the moment history moves, so publish must stamp a tag ref`
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
return tag;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Prove that the checked-out tag and HEAD name the supplied release commit,
|
|
240
|
+
* and that the commit is reachable from a ref a consumer can resolve.
|
|
141
241
|
* @param {{root: string, releaseCommit: string, tag: string}} input Identity input
|
|
142
|
-
* @returns {{headCommit: string, tagCommit: string, releaseCommit: string}}
|
|
242
|
+
* @returns {{headCommit: string, tagCommit: string, releaseCommit: string, durableRefs: readonly string[]}}
|
|
143
243
|
*/
|
|
144
244
|
export function assertCheckoutIdentity({ root, releaseCommit, tag }) {
|
|
145
245
|
const validatedReleaseCommit = assertReleaseCommit(releaseCommit);
|
|
@@ -155,25 +255,36 @@ export function assertCheckoutIdentity({ root, releaseCommit, tag }) {
|
|
|
155
255
|
`tag ${tag} resolves ${tagCommit}, expected ${validatedReleaseCommit}`
|
|
156
256
|
);
|
|
157
257
|
}
|
|
258
|
+
const durableRefs = durableRefsContaining(root, validatedReleaseCommit);
|
|
259
|
+
if (durableRefs.length === 0) {
|
|
260
|
+
mismatch(
|
|
261
|
+
`release commit ${validatedReleaseCommit} is reachable from no durable ref (${DURABLE_REF_NAMESPACES.join(", ")}, excluding ${BACKUP_TAG_PREFIX}*). ` +
|
|
262
|
+
"Publishing it would stamp a pin that resolves to nothing in every consumer that installs this version, and a caller whose pin does not resolve reports no failure at all. " +
|
|
263
|
+
`Push a release tag at ${validatedReleaseCommit} and re-run, or re-cut the release from a commit that is on the published branch.`
|
|
264
|
+
);
|
|
265
|
+
}
|
|
158
266
|
return Object.freeze({
|
|
159
267
|
headCommit,
|
|
160
268
|
tagCommit,
|
|
161
269
|
releaseCommit: validatedReleaseCommit,
|
|
270
|
+
durableRefs,
|
|
162
271
|
});
|
|
163
272
|
}
|
|
164
273
|
|
|
165
274
|
/**
|
|
166
275
|
* Pack and validate the exact candidate later passed to npm publish.
|
|
167
|
-
* @param {{root: string, version: string, releaseCommit: string, packDestination: string}} input Candidate input
|
|
168
|
-
* @returns {{version: string, releaseCommit: string, certificateVersion: string|null, tarballPath: string, tarballSha256: string, certificateMemberSha256: string|null}}
|
|
276
|
+
* @param {{root: string, version: string, releaseCommit: string, tag: string, packDestination: string}} input Candidate input
|
|
277
|
+
* @returns {{version: string, releaseCommit: string, releaseTag: string, certificateVersion: string|null, tarballPath: string, tarballSha256: string, certificateMemberSha256: string|null}}
|
|
169
278
|
*/
|
|
170
279
|
export function packAndValidateReleaseCandidate({
|
|
171
280
|
root,
|
|
172
281
|
version,
|
|
173
282
|
releaseCommit,
|
|
283
|
+
tag,
|
|
174
284
|
packDestination,
|
|
175
285
|
}) {
|
|
176
286
|
const validatedReleaseCommit = assertReleaseCommit(releaseCommit);
|
|
287
|
+
const validatedTag = assertReleaseTag(tag);
|
|
177
288
|
const packageJson = readJson(path.join(root, "package.json"), "package.json");
|
|
178
289
|
if (packageJson.version !== version) {
|
|
179
290
|
mismatch(
|
|
@@ -190,6 +301,11 @@ export function packAndValidateReleaseCandidate({
|
|
|
190
301
|
`package.json gitHead ${JSON.stringify(packageJson.gitHead)} expected ${validatedReleaseCommit}`
|
|
191
302
|
);
|
|
192
303
|
}
|
|
304
|
+
if (packageJson.lisaReleaseTag !== validatedTag) {
|
|
305
|
+
mismatch(
|
|
306
|
+
`package.json lisaReleaseTag ${JSON.stringify(packageJson.lisaReleaseTag)} expected ${validatedTag}`
|
|
307
|
+
);
|
|
308
|
+
}
|
|
193
309
|
|
|
194
310
|
const sourceCertificatePath = path.join(root, SOURCE_CERTIFICATE);
|
|
195
311
|
const hasCertificate = existsSync(sourceCertificatePath);
|
|
@@ -243,6 +359,11 @@ export function packAndValidateReleaseCandidate({
|
|
|
243
359
|
`packed gitHead ${JSON.stringify(packedPackage.gitHead)} expected ${validatedReleaseCommit}`
|
|
244
360
|
);
|
|
245
361
|
}
|
|
362
|
+
if (packedPackage.lisaReleaseTag !== validatedTag) {
|
|
363
|
+
mismatch(
|
|
364
|
+
`packed lisaReleaseTag ${JSON.stringify(packedPackage.lisaReleaseTag)} expected ${validatedTag}`
|
|
365
|
+
);
|
|
366
|
+
}
|
|
246
367
|
|
|
247
368
|
let packedCertificateVersion = null;
|
|
248
369
|
let certificateMemberSha256 = null;
|
|
@@ -260,6 +381,7 @@ export function packAndValidateReleaseCandidate({
|
|
|
260
381
|
return Object.freeze({
|
|
261
382
|
version,
|
|
262
383
|
releaseCommit: validatedReleaseCommit,
|
|
384
|
+
releaseTag: validatedTag,
|
|
263
385
|
certificateVersion: packedCertificateVersion,
|
|
264
386
|
tarballPath,
|
|
265
387
|
tarballSha256: sha256(tarballBytes),
|
|
@@ -306,6 +428,7 @@ export function runReleasePackageIdentityCli(
|
|
|
306
428
|
root,
|
|
307
429
|
version: requiredOption(options, "--version"),
|
|
308
430
|
releaseCommit: requiredOption(options, "--release-commit"),
|
|
431
|
+
tag: requiredOption(options, "--tag"),
|
|
309
432
|
packDestination: requiredOption(options, "--pack-destination"),
|
|
310
433
|
});
|
|
311
434
|
writeGithubOutput(result, githubOutput);
|