@codyswann/lisa 4.21.4 → 4.21.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/dist/cli/doctor-nightly-e2e-guard-command-files.d.ts +19 -0
- package/dist/cli/doctor-nightly-e2e-guard-command-files.d.ts.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-command-files.js +221 -0
- package/dist/cli/doctor-nightly-e2e-guard-command-files.js.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-contract.d.ts +113 -0
- package/dist/cli/doctor-nightly-e2e-guard-contract.d.ts.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-contract.js +87 -0
- package/dist/cli/doctor-nightly-e2e-guard-contract.js.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-deadline.d.ts +61 -0
- package/dist/cli/doctor-nightly-e2e-guard-deadline.d.ts.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-deadline.js +97 -0
- package/dist/cli/doctor-nightly-e2e-guard-deadline.js.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-evidence.d.ts +52 -0
- package/dist/cli/doctor-nightly-e2e-guard-evidence.d.ts.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-evidence.js +68 -0
- package/dist/cli/doctor-nightly-e2e-guard-evidence.js.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-graph.d.ts +14 -0
- package/dist/cli/doctor-nightly-e2e-guard-graph.d.ts.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-graph.js +130 -0
- package/dist/cli/doctor-nightly-e2e-guard-graph.js.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-io.d.ts +41 -0
- package/dist/cli/doctor-nightly-e2e-guard-io.d.ts.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-io.js +239 -0
- package/dist/cli/doctor-nightly-e2e-guard-io.js.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-job.d.ts +31 -0
- package/dist/cli/doctor-nightly-e2e-guard-job.d.ts.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-job.js +156 -0
- package/dist/cli/doctor-nightly-e2e-guard-job.js.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-proof.d.ts +42 -0
- package/dist/cli/doctor-nightly-e2e-guard-proof.d.ts.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-proof.js +57 -0
- package/dist/cli/doctor-nightly-e2e-guard-proof.js.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-remediation.d.ts +32 -0
- package/dist/cli/doctor-nightly-e2e-guard-remediation.d.ts.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-remediation.js +55 -0
- package/dist/cli/doctor-nightly-e2e-guard-remediation.js.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-runtime.d.ts +34 -0
- package/dist/cli/doctor-nightly-e2e-guard-runtime.d.ts.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-runtime.js +93 -0
- package/dist/cli/doctor-nightly-e2e-guard-runtime.js.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-scan.d.ts +17 -0
- package/dist/cli/doctor-nightly-e2e-guard-scan.d.ts.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-scan.js +86 -0
- package/dist/cli/doctor-nightly-e2e-guard-scan.js.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-shell-lexer.d.ts +38 -0
- package/dist/cli/doctor-nightly-e2e-guard-shell-lexer.d.ts.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-shell-lexer.js +160 -0
- package/dist/cli/doctor-nightly-e2e-guard-shell-lexer.js.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-shell.d.ts +40 -0
- package/dist/cli/doctor-nightly-e2e-guard-shell.d.ts.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard-shell.js +201 -0
- package/dist/cli/doctor-nightly-e2e-guard-shell.js.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard.d.ts +36 -0
- package/dist/cli/doctor-nightly-e2e-guard.d.ts.map +1 -0
- package/dist/cli/doctor-nightly-e2e-guard.js +186 -0
- package/dist/cli/doctor-nightly-e2e-guard.js.map +1 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +7 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.d.ts +25 -0
- package/dist/core/nightly-e2e-guard-behavior-certificate.d.ts.map +1 -0
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +30 -0
- package/dist/core/nightly-e2e-guard-behavior-certificate.js.map +1 -0
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +30 -3
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +5 -3
- 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-secrets-access/scripts/publish-aws-bootstrap.mjs +17 -2
- package/plugins/lisa/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +17 -2
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +17 -2
- 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-secrets-access/scripts/publish-aws-bootstrap.mjs +17 -2
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +17 -2
- 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-secrets-access/scripts/publish-aws-bootstrap.mjs +17 -2
- package/scripts/check-derived-artifacts.mjs +32 -5
- package/scripts/generate-nightly-e2e-guard-certificate.mjs +370 -0
- package/scripts/two-channel-couplings.json +16 -4
|
@@ -126,6 +126,10 @@ export function candidateAwsEnvironment(bundle) {
|
|
|
126
126
|
env.AWS_SECRET_ACCESS_KEY = String(
|
|
127
127
|
bundle.secretAccessKey ?? bundle.aws_secret_access_key
|
|
128
128
|
);
|
|
129
|
+
// STS verification must reach AWS, not an endpoint inherited from the host.
|
|
130
|
+
// A redirected endpoint could otherwise answer the probe and turn a green
|
|
131
|
+
// result into evidence about a local emulator or an attacker-controlled URL.
|
|
132
|
+
env.AWS_IGNORE_CONFIGURED_ENDPOINT_URLS = "true";
|
|
129
133
|
env.AWS_PAGER = "";
|
|
130
134
|
return env;
|
|
131
135
|
}
|
|
@@ -228,12 +232,17 @@ export function preflightAwsBootstrap(cfg, operations = {}) {
|
|
|
228
232
|
* Restore the previous provider value and prove the restoration.
|
|
229
233
|
* @param {object} cfg Resolved configuration.
|
|
230
234
|
* @param {{id: string, value: string}} previous Previous entry.
|
|
235
|
+
* @param {string} candidateRaw Exact value written by this publication.
|
|
231
236
|
* @param {Function} fetch Provider reader.
|
|
232
237
|
* @param {Function} write Provider writer.
|
|
233
238
|
* @returns {string} Human-readable rollback outcome with no secret material.
|
|
234
239
|
*/
|
|
235
|
-
function rollback(cfg, previous, fetch, write) {
|
|
240
|
+
function rollback(cfg, previous, candidateRaw, fetch, write) {
|
|
236
241
|
try {
|
|
242
|
+
const current = currentEntry(cfg, fetch);
|
|
243
|
+
if (current.value !== candidateRaw) {
|
|
244
|
+
return "rollback skipped because the provider changed after this publication";
|
|
245
|
+
}
|
|
237
246
|
write(cfg, previous.id, previous.value);
|
|
238
247
|
const restored = currentEntry(cfg, fetch);
|
|
239
248
|
if (restored.value !== previous.value) {
|
|
@@ -288,7 +297,13 @@ export function publishAwsBootstrap(raw, cfg, operations = {}) {
|
|
|
288
297
|
} catch (error) {
|
|
289
298
|
const reason =
|
|
290
299
|
error instanceof Error ? error.message.split("\n")[0] : String(error);
|
|
291
|
-
const rollbackOutcome = rollback(
|
|
300
|
+
const rollbackOutcome = rollback(
|
|
301
|
+
cfg,
|
|
302
|
+
previous,
|
|
303
|
+
candidate.raw,
|
|
304
|
+
fetch,
|
|
305
|
+
write
|
|
306
|
+
);
|
|
292
307
|
throw new Error(
|
|
293
308
|
`${BOOTSTRAP_KEY} publication failed: ${reason}; ${rollbackOutcome}`
|
|
294
309
|
);
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
* Fail a commit whose generated artifacts no longer vouch for the bytes beside
|
|
4
4
|
* them.
|
|
5
5
|
*
|
|
6
|
-
* Lisa has
|
|
6
|
+
* Lisa has three artifact families derived from tracked sources:
|
|
7
7
|
*
|
|
8
8
|
* - `src/core/upstream-evidence-manifest.ts`, regenerated by
|
|
9
9
|
* `bun run build:upstream-evidence-manifest`
|
|
10
10
|
* - `src/core/lisa-owned-hash-ledger.ts`, regenerated by
|
|
11
11
|
* `bun run build:lisa-owned-hash-ledger`
|
|
12
|
+
* - `src/core/nightly-e2e-guard-behavior-certificate.ts`, regenerated by
|
|
13
|
+
* `bun run build:nightly-guard-certificate`
|
|
12
14
|
*
|
|
13
15
|
* Each already has a suite-level guard, but the suite is the slow and late place
|
|
14
16
|
* to learn it. CodySwannGT/lisa#2557 is what that costs: three branches, each
|
|
@@ -20,8 +22,8 @@
|
|
|
20
22
|
*
|
|
21
23
|
* **This script verifies nothing itself.** It delegates to each generator's own
|
|
22
24
|
* `--check`, which is the point: a second implementation of "is the artifact
|
|
23
|
-
* current" would be a second thing to keep in step. The
|
|
24
|
-
* assert different properties, and
|
|
25
|
+
* current" would be a second thing to keep in step. The checks deliberately
|
|
26
|
+
* assert different properties, and each is correct for its artifact:
|
|
25
27
|
*
|
|
26
28
|
* - The manifest hashes tracked files with no history walk, so a fresh
|
|
27
29
|
* regeneration is byte-reproducible anywhere and byte-identity is a fair test.
|
|
@@ -38,7 +40,7 @@
|
|
|
38
40
|
* NOT true: the manifest does not hash the ledger, so there is no required order
|
|
39
41
|
* between the two, and a stale manifest is not evidence of a stale ledger.
|
|
40
42
|
*
|
|
41
|
-
*
|
|
43
|
+
* The triggers below are deliberate **supersets** of what each artifact covers.
|
|
42
44
|
* Re-deriving exact membership here would create a second copy of the rule that
|
|
43
45
|
* could drift and silently stop firing; over-triggering only costs a few seconds
|
|
44
46
|
* on a commit that owed nothing, which is the safe direction to be wrong in.
|
|
@@ -101,6 +103,25 @@ export function requiresLedgerCheck(stagedPaths) {
|
|
|
101
103
|
return stagedPaths.some(staged => staged.includes(COPY_OVERWRITE));
|
|
102
104
|
}
|
|
103
105
|
|
|
106
|
+
const NIGHTLY_GUARD_CERTIFICATE_INPUTS = Object.freeze([
|
|
107
|
+
"package.json",
|
|
108
|
+
"scripts/generate-nightly-e2e-guard-certificate.mjs",
|
|
109
|
+
"src/core/nightly-e2e-guard-behavior-certificate.ts",
|
|
110
|
+
"typescript/copy-overwrite/scripts/check-nightly-e2e-health.mjs",
|
|
111
|
+
"typescript/copy-overwrite/scripts/lib/invoked-as-script.mjs",
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Whether staged bytes can change a behavior-certificate generation result.
|
|
116
|
+
* @param {readonly string[]} stagedPaths - Repo-relative staged paths
|
|
117
|
+
* @returns {boolean} True when the dedicated certificate check is owed
|
|
118
|
+
*/
|
|
119
|
+
export function requiresNightlyGuardCertificateCheck(stagedPaths) {
|
|
120
|
+
return stagedPaths.some(staged =>
|
|
121
|
+
NIGHTLY_GUARD_CERTIFICATE_INPUTS.includes(staged)
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
104
125
|
/**
|
|
105
126
|
* Whether this change touches a path the evidence manifest hash-pins.
|
|
106
127
|
* @param {readonly string[]} stagedPaths - Repo-relative staged paths
|
|
@@ -221,6 +242,12 @@ const ARTIFACTS = Object.freeze([
|
|
|
221
242
|
command: "bun run build:lisa-owned-hash-ledger",
|
|
222
243
|
trigger: requiresLedgerCheck,
|
|
223
244
|
}),
|
|
245
|
+
Object.freeze({
|
|
246
|
+
name: "nightly guard behavior certificate",
|
|
247
|
+
script: "scripts/generate-nightly-e2e-guard-certificate.mjs",
|
|
248
|
+
command: "bun run build:nightly-guard-certificate",
|
|
249
|
+
trigger: requiresNightlyGuardCertificateCheck,
|
|
250
|
+
}),
|
|
224
251
|
]);
|
|
225
252
|
|
|
226
253
|
/**
|
|
@@ -257,7 +284,7 @@ function main() {
|
|
|
257
284
|
"\n\n"
|
|
258
285
|
)}\n\n` +
|
|
259
286
|
`The order is: git add the real changes, THEN regenerate, THEN git add the\n` +
|
|
260
|
-
`regenerated file.
|
|
287
|
+
`regenerated file. The generators read the git index and/or working tree,\n` +
|
|
261
288
|
`so regenerating before staging records the file set you had a moment ago.\n\n` +
|
|
262
289
|
`Passing by hand and failing here is expected, not a puzzle: lint-staged\n` +
|
|
263
290
|
`reformats every staged file just above, and this gate runs after it on\n` +
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generate the digest-to-contract behavior certificate for the nightly guard.
|
|
4
|
+
*
|
|
5
|
+
* A Lisa-owned hash proves ownership, not behavior. This generator instead
|
|
6
|
+
* loads only Lisa package/release artifacts and exercises the actual exported
|
|
7
|
+
* waiver handler: valid audited waiver, self-service, non-maintainer refusal,
|
|
8
|
+
* malformed-trailer refusal, the 72-hour ceiling, and verdict application.
|
|
9
|
+
* Runtime doctor then needs only an exact digest lookup and never evaluates a
|
|
10
|
+
* host project's target.
|
|
11
|
+
*
|
|
12
|
+
* Retention is explicit. A future guard-changing release adds its immutable git
|
|
13
|
+
* ref to `RETAINED_RELEASES`; regeneration rereads that release artifact and
|
|
14
|
+
* reruns the behavior suite. No digest is copied forward from the generated
|
|
15
|
+
* output, so arbitrary historical bytes cannot become trusted by inertia.
|
|
16
|
+
* @module scripts/generate-nightly-e2e-guard-certificate
|
|
17
|
+
*/
|
|
18
|
+
import { createHash } from "node:crypto";
|
|
19
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
20
|
+
import * as os from "node:os";
|
|
21
|
+
import * as path from "node:path";
|
|
22
|
+
import { pathToFileURL } from "node:url";
|
|
23
|
+
import { format } from "prettier";
|
|
24
|
+
|
|
25
|
+
import { boundedExecFileSync } from "./lib/bounded-spawn.mjs";
|
|
26
|
+
import { invokedAsScript } from "./lib/invoked-as-script.mjs";
|
|
27
|
+
|
|
28
|
+
const DEFAULT_REPO_ROOT = path.resolve(import.meta.dirname, "..");
|
|
29
|
+
const GUARD_PATH =
|
|
30
|
+
"typescript/copy-overwrite/scripts/check-nightly-e2e-health.mjs";
|
|
31
|
+
const HELPER_PATH =
|
|
32
|
+
"typescript/copy-overwrite/scripts/lib/invoked-as-script.mjs";
|
|
33
|
+
const OUTPUT_PATH = "src/core/nightly-e2e-guard-behavior-certificate.ts";
|
|
34
|
+
const CERTIFICATE_SCHEMA_VERSION = 1;
|
|
35
|
+
const SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/u;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Immutable releases retained after the workspace guard changes.
|
|
39
|
+
*
|
|
40
|
+
* Every entry is reread from git and behavior-tested. Adding a digest or source
|
|
41
|
+
* blob here is intentionally impossible; the only input is a release ref.
|
|
42
|
+
*/
|
|
43
|
+
export const RETAINED_RELEASES = Object.freeze(["v2.353.0", "v4.17.16"]);
|
|
44
|
+
|
|
45
|
+
const digest = bytes => createHash("sha256").update(bytes).digest("hex");
|
|
46
|
+
|
|
47
|
+
const invariant = (condition, message) => {
|
|
48
|
+
if (!condition) {
|
|
49
|
+
throw new Error(`Nightly guard behavior certificate refused: ${message}`);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const validRequest = Object.freeze({
|
|
54
|
+
labelEvent: Object.freeze({
|
|
55
|
+
actor: "maintainer",
|
|
56
|
+
createdAt: "2026-08-12T06:00:00Z",
|
|
57
|
+
}),
|
|
58
|
+
prAuthor: "author",
|
|
59
|
+
prNumber: 2519,
|
|
60
|
+
label: "nightly-e2e-bypass",
|
|
61
|
+
prBody: "Nightly-E2E-Bypass: #2519 bounded handler proof\n",
|
|
62
|
+
actorPermission: "maintain",
|
|
63
|
+
maxHours: 24,
|
|
64
|
+
extraReasonPattern: "",
|
|
65
|
+
now: new Date("2026-08-12T09:00:00Z"),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const VERSION_EXPECTATIONS = Object.freeze({
|
|
69
|
+
"1.1.0": Object.freeze({ selfService: "refused" }),
|
|
70
|
+
"1.7.0": Object.freeze({ selfService: "valid" }),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Exercise the exported handler and the waiver-to-verdict boundary.
|
|
75
|
+
* @param {Record<string, unknown>} mod - Imported Lisa package artifact
|
|
76
|
+
* @returns {string} Strict compatible contract version proven by behavior
|
|
77
|
+
*/
|
|
78
|
+
export function verifyNightlyGuardBehavior(mod) {
|
|
79
|
+
const version = mod.NIGHTLY_E2E_CONTRACT_VERSION;
|
|
80
|
+
const semantic = typeof version === "string" ? SEMVER.exec(version) : null;
|
|
81
|
+
invariant(
|
|
82
|
+
semantic?.[1] === "1",
|
|
83
|
+
"contract must be strict ASCII major-1 semver"
|
|
84
|
+
);
|
|
85
|
+
invariant(
|
|
86
|
+
typeof mod.evaluateBypass === "function" &&
|
|
87
|
+
typeof mod.decide === "function",
|
|
88
|
+
"actual handler exports evaluateBypass and decide"
|
|
89
|
+
);
|
|
90
|
+
const expectations = VERSION_EXPECTATIONS[version];
|
|
91
|
+
invariant(
|
|
92
|
+
expectations !== undefined,
|
|
93
|
+
`contract ${version} has no explicit version-appropriate behavior expectations`
|
|
94
|
+
);
|
|
95
|
+
invariant(
|
|
96
|
+
mod.BYPASS_ABSOLUTE_MAX_HOURS === 72,
|
|
97
|
+
"bounded waiver ceiling must remain 72 hours"
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const valid = mod.evaluateBypass(validRequest);
|
|
101
|
+
invariant(
|
|
102
|
+
valid?.valid === true &&
|
|
103
|
+
valid.reason === "valid" &&
|
|
104
|
+
valid.ticket === "#2519" &&
|
|
105
|
+
valid.expiresAt === "2026-08-13T06:00:00.000Z",
|
|
106
|
+
"valid audited waiver behavior changed"
|
|
107
|
+
);
|
|
108
|
+
const selfService = mod.evaluateBypass({
|
|
109
|
+
...validRequest,
|
|
110
|
+
labelEvent: { actor: "author", createdAt: "2026-08-12T06:00:00Z" },
|
|
111
|
+
});
|
|
112
|
+
invariant(
|
|
113
|
+
expectations.selfService === "valid"
|
|
114
|
+
? selfService?.valid === true && selfService.actor === "author"
|
|
115
|
+
: selfService?.valid === false && selfService.reason === "self_bypass",
|
|
116
|
+
"self-service audited waiver behavior changed"
|
|
117
|
+
);
|
|
118
|
+
const unauthorized = mod.evaluateBypass({
|
|
119
|
+
...validRequest,
|
|
120
|
+
actorPermission: "write",
|
|
121
|
+
});
|
|
122
|
+
invariant(
|
|
123
|
+
unauthorized?.valid === false &&
|
|
124
|
+
unauthorized.reason === "actor_not_maintainer",
|
|
125
|
+
"non-maintainer waiver refusal changed"
|
|
126
|
+
);
|
|
127
|
+
const malformed = mod.evaluateBypass({
|
|
128
|
+
...validRequest,
|
|
129
|
+
prBody: "no ticketed waiver trailer",
|
|
130
|
+
extraReasonPattern: ".*",
|
|
131
|
+
});
|
|
132
|
+
invariant(
|
|
133
|
+
malformed?.valid === false && malformed.reason === "no_reason_or_ticket",
|
|
134
|
+
"built-in waiver reason may no longer be widened"
|
|
135
|
+
);
|
|
136
|
+
const expired = mod.evaluateBypass({
|
|
137
|
+
...validRequest,
|
|
138
|
+
labelEvent: { actor: "maintainer", createdAt: "2026-08-08T06:00:00Z" },
|
|
139
|
+
maxHours: 24 * 365,
|
|
140
|
+
});
|
|
141
|
+
invariant(
|
|
142
|
+
expired?.valid === false &&
|
|
143
|
+
expired.reason === "bypass_expired" &&
|
|
144
|
+
expired.expiresAt === "2026-08-11T06:00:00.000Z",
|
|
145
|
+
"bounded waiver lifetime behavior changed"
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const red = Object.freeze({ state: "fail", label: "nightly" });
|
|
149
|
+
const bootstrap = Object.freeze({ active: false, until: null });
|
|
150
|
+
const waived = mod.decide([red], { bootstrap, bypass: valid });
|
|
151
|
+
const rejected = mod.decide([red], { bootstrap, bypass: unauthorized });
|
|
152
|
+
invariant(
|
|
153
|
+
waived?.verdict === "bypassed" &&
|
|
154
|
+
waived.blocked === false &&
|
|
155
|
+
waived.bypass?.waived?.length === 1,
|
|
156
|
+
"valid waiver no longer produces one audited bypass verdict"
|
|
157
|
+
);
|
|
158
|
+
invariant(
|
|
159
|
+
rejected?.verdict === "fail" && rejected.blocked === true,
|
|
160
|
+
"rejected waiver no longer leaves the gate closed"
|
|
161
|
+
);
|
|
162
|
+
return version;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Import one candidate with only the sibling helper the package ships.
|
|
167
|
+
* @param {Buffer} guardBytes - Exact guard artifact bytes
|
|
168
|
+
* @param {Buffer | undefined} invokedAsScriptBytes - Exact sibling helper bytes when that release imports it
|
|
169
|
+
* @returns {Promise<Record<string, unknown>>} Imported module namespace
|
|
170
|
+
*/
|
|
171
|
+
async function importPackageArtifact(guardBytes, invokedAsScriptBytes) {
|
|
172
|
+
const temp = await mkdtemp(path.join(os.tmpdir(), "lisa-guard-cert-"));
|
|
173
|
+
try {
|
|
174
|
+
await mkdir(path.join(temp, "lib"));
|
|
175
|
+
await writeFile(path.join(temp, "guard.mjs"), guardBytes);
|
|
176
|
+
if (invokedAsScriptBytes !== undefined) {
|
|
177
|
+
await writeFile(
|
|
178
|
+
path.join(temp, "lib", "invoked-as-script.mjs"),
|
|
179
|
+
invokedAsScriptBytes
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
return await import(pathToFileURL(path.join(temp, "guard.mjs")).href);
|
|
183
|
+
} finally {
|
|
184
|
+
await rm(temp, { force: true, recursive: true });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Certify exact bytes from an identified Lisa package artifact.
|
|
190
|
+
* @param {{guardBytes: Buffer, invokedAsScriptBytes?: Buffer, packageVersion: string, provenance: string}} input - Package bytes and immutable origin
|
|
191
|
+
* @returns {Promise<{digest: string, contractVersion: string, packageVersion: string, provenance: string}>} Verified certificate source
|
|
192
|
+
*/
|
|
193
|
+
export async function certifyNightlyGuardPackageArtifact(input) {
|
|
194
|
+
invariant(
|
|
195
|
+
SEMVER.test(input.packageVersion),
|
|
196
|
+
"package provenance must carry a strict semantic version"
|
|
197
|
+
);
|
|
198
|
+
invariant(
|
|
199
|
+
typeof input.provenance === "string" && input.provenance.length > 0,
|
|
200
|
+
"package provenance must identify its artifact"
|
|
201
|
+
);
|
|
202
|
+
const mod = await importPackageArtifact(
|
|
203
|
+
input.guardBytes,
|
|
204
|
+
input.invokedAsScriptBytes
|
|
205
|
+
);
|
|
206
|
+
return Object.freeze({
|
|
207
|
+
digest: digest(input.guardBytes),
|
|
208
|
+
contractVersion: verifyNightlyGuardBehavior(mod),
|
|
209
|
+
packageVersion: input.packageVersion,
|
|
210
|
+
provenance: input.provenance,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const gitFile = (repoRoot, ref, file) =>
|
|
215
|
+
Buffer.from(
|
|
216
|
+
boundedExecFileSync("git", ["show", `${ref}:${file}`], {
|
|
217
|
+
cwd: repoRoot,
|
|
218
|
+
encoding: "buffer",
|
|
219
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
220
|
+
})
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
const gitFileOptional = (repoRoot, ref, file) => {
|
|
224
|
+
try {
|
|
225
|
+
return gitFile(repoRoot, ref, file);
|
|
226
|
+
} catch {
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const packageMetadata = bytes => {
|
|
232
|
+
const parsed = JSON.parse(bytes.toString("utf8"));
|
|
233
|
+
invariant(
|
|
234
|
+
parsed?.name === "@codyswann/lisa" &&
|
|
235
|
+
Array.isArray(parsed.files) &&
|
|
236
|
+
parsed.files.includes("typescript"),
|
|
237
|
+
"artifact is not an @codyswann/lisa package containing typescript"
|
|
238
|
+
);
|
|
239
|
+
return parsed;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const workspaceArtifact = async repoRoot => {
|
|
243
|
+
const metadata = packageMetadata(
|
|
244
|
+
await readFile(path.join(repoRoot, "package.json"))
|
|
245
|
+
);
|
|
246
|
+
return await certifyNightlyGuardPackageArtifact({
|
|
247
|
+
guardBytes: await readFile(path.join(repoRoot, GUARD_PATH)),
|
|
248
|
+
invokedAsScriptBytes: await readFile(path.join(repoRoot, HELPER_PATH)),
|
|
249
|
+
packageVersion: metadata.version,
|
|
250
|
+
provenance: `workspace package @codyswann/lisa@${metadata.version} (${GUARD_PATH})`,
|
|
251
|
+
});
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const releaseArtifact = async (repoRoot, ref) => {
|
|
255
|
+
const metadata = packageMetadata(gitFile(repoRoot, ref, "package.json"));
|
|
256
|
+
return await certifyNightlyGuardPackageArtifact({
|
|
257
|
+
guardBytes: gitFile(repoRoot, ref, GUARD_PATH),
|
|
258
|
+
invokedAsScriptBytes: gitFileOptional(repoRoot, ref, HELPER_PATH),
|
|
259
|
+
packageVersion: metadata.version,
|
|
260
|
+
provenance: `git tag ${ref} package @codyswann/lisa@${metadata.version} (${GUARD_PATH})`,
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const mergeCertificates = entries => {
|
|
265
|
+
const grouped = new Map();
|
|
266
|
+
for (const entry of entries) {
|
|
267
|
+
const prior = grouped.get(entry.digest);
|
|
268
|
+
if (prior && prior.contractVersion !== entry.contractVersion) {
|
|
269
|
+
throw new Error(
|
|
270
|
+
`Nightly guard behavior certificate refused: digest ${entry.digest} has conflicting contracts`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
grouped.set(entry.digest, {
|
|
274
|
+
digest: entry.digest,
|
|
275
|
+
contractVersion: entry.contractVersion,
|
|
276
|
+
packageVersions: [
|
|
277
|
+
...new Set([...(prior?.packageVersions ?? []), entry.packageVersion]),
|
|
278
|
+
].sort(),
|
|
279
|
+
provenances: [
|
|
280
|
+
...new Set([...(prior?.provenances ?? []), entry.provenance]),
|
|
281
|
+
].sort(),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
return [...grouped.values()].sort((left, right) =>
|
|
285
|
+
left.digest < right.digest ? -1 : left.digest > right.digest ? 1 : 0
|
|
286
|
+
);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const render = async certificates => {
|
|
290
|
+
const rows = certificates
|
|
291
|
+
.map(
|
|
292
|
+
certificate => ` ${JSON.stringify(certificate.digest)}: Object.freeze({
|
|
293
|
+
contractVersion: ${JSON.stringify(certificate.contractVersion)},
|
|
294
|
+
packageVersions: Object.freeze(${JSON.stringify(certificate.packageVersions)}),
|
|
295
|
+
provenances: Object.freeze(${JSON.stringify(certificate.provenances)}),
|
|
296
|
+
}),`
|
|
297
|
+
)
|
|
298
|
+
.join("\n");
|
|
299
|
+
return await format(
|
|
300
|
+
`/**
|
|
301
|
+
* @file nightly-e2e-guard-behavior-certificate.ts
|
|
302
|
+
* @description Generated exact-byte certificates for behavior-proven Lisa guard artifacts
|
|
303
|
+
* @module core/nightly-e2e-guard-behavior-certificate
|
|
304
|
+
* Generated by scripts/generate-nightly-e2e-guard-certificate.mjs.
|
|
305
|
+
*/
|
|
306
|
+
|
|
307
|
+
/** Behavior-certificate schema consumed by doctor. */
|
|
308
|
+
export const NIGHTLY_E2E_GUARD_CERTIFICATE_SCHEMA_VERSION = ${CERTIFICATE_SCHEMA_VERSION};
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Exact digests whose real package handlers passed the bounded-waiver suite.
|
|
312
|
+
* Generic Lisa ownership hashes are deliberately not consulted at runtime.
|
|
313
|
+
*/
|
|
314
|
+
export const NIGHTLY_E2E_GUARD_BEHAVIOR_CERTIFICATES = Object.freeze({
|
|
315
|
+
${rows}
|
|
316
|
+
});
|
|
317
|
+
`,
|
|
318
|
+
{ parser: "typescript" }
|
|
319
|
+
);
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Generate current workspace plus explicitly retained release certificates.
|
|
324
|
+
* @param {string} [repoRoot] - Canonical Lisa checkout
|
|
325
|
+
* @returns {Promise<{source: string, certificates: readonly object[]}>} Generated artifact and structured records
|
|
326
|
+
*/
|
|
327
|
+
export async function generateNightlyGuardBehaviorCertificate(
|
|
328
|
+
repoRoot = DEFAULT_REPO_ROOT
|
|
329
|
+
) {
|
|
330
|
+
const sources = [await workspaceArtifact(repoRoot)];
|
|
331
|
+
for (const ref of RETAINED_RELEASES) {
|
|
332
|
+
sources.push(await releaseArtifact(repoRoot, ref));
|
|
333
|
+
}
|
|
334
|
+
const certificates = mergeCertificates(sources);
|
|
335
|
+
return Object.freeze({
|
|
336
|
+
source: await render(certificates),
|
|
337
|
+
certificates,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Generate or verify the checked-in artifact.
|
|
343
|
+
* @param {readonly string[]} argv - CLI arguments
|
|
344
|
+
* @param {string} [repoRoot] - Canonical Lisa checkout
|
|
345
|
+
* @returns {Promise<number>} Process exit code
|
|
346
|
+
*/
|
|
347
|
+
export async function runCertificateCli(argv, repoRoot = DEFAULT_REPO_ROOT) {
|
|
348
|
+
const generated = await generateNightlyGuardBehaviorCertificate(repoRoot);
|
|
349
|
+
const output = path.join(repoRoot, OUTPUT_PATH);
|
|
350
|
+
if (argv.includes("--check")) {
|
|
351
|
+
const current = await readFile(output, "utf8").catch(() => "");
|
|
352
|
+
if (current === generated.source) {
|
|
353
|
+
process.stdout.write("Nightly guard behavior certificate is current.\n");
|
|
354
|
+
return 0;
|
|
355
|
+
}
|
|
356
|
+
process.stderr.write(
|
|
357
|
+
"Nightly guard behavior certificate drifted. Run `bun run build:nightly-guard-certificate` and commit the generated artifact.\n"
|
|
358
|
+
);
|
|
359
|
+
return 1;
|
|
360
|
+
}
|
|
361
|
+
await writeFile(output, generated.source);
|
|
362
|
+
process.stdout.write(
|
|
363
|
+
`Wrote ${OUTPUT_PATH} (${generated.certificates.length} certified digest(s)).\n`
|
|
364
|
+
);
|
|
365
|
+
return 0;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (invokedAsScript(import.meta.url)) {
|
|
369
|
+
process.exitCode = await runCertificateCli(process.argv.slice(2));
|
|
370
|
+
}
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"ratified": {
|
|
3
|
-
"quality.yml::scripts/check-learnings-budget.ts": "Lisa-source-only branch, and the else arm is the delivery. The step tests for BOTH scripts/check-learnings-budget.ts and src/core/learnings-budget-check.ts, which co-exist only in this repository; every consumer takes the else arm and runs the prover as `bunx @codyswann/lisa@<their own declared version>`, which is the PACKAGE channel and not a caller-tree read at all. A trailing `grep -qE \"learnings budget (passed|saturated)|no learnings file\"` then refuses a run that printed no verdict, so the arm cannot pass vacuously. Ratified because the caller-tree read is unreachable outside this repository, not because the gap is tolerated."
|
|
3
|
+
"quality.yml::scripts/check-learnings-budget.ts": "Lisa-source-only branch, and the else arm is the delivery. The step tests for BOTH scripts/check-learnings-budget.ts and src/core/learnings-budget-check.ts, which co-exist only in this repository; every consumer takes the else arm and runs the prover as `bunx @codyswann/lisa@<their own declared version>`, which is the PACKAGE channel and not a caller-tree read at all. A trailing `grep -qE \"learnings budget (passed|saturated)|no learnings file\"` then refuses a run that printed no verdict, so the arm cannot pass vacuously. Ratified because the caller-tree read is unreachable outside this repository, not because the gap is tolerated.",
|
|
4
|
+
"quality.yml::scripts/generate-nightly-e2e-guard-certificate.mjs": "Lisa-source-only branch. Both reusable jobs require `github.repository == 'CodySwannGT/lisa'` before the step can start, and the run body repeats that exact repository guard before checking this artifact path. Ordinary and private host callers therefore skip the step without reading or fetching from their own tree. The scoped branch fetches only the immutable refs needed to behavior-test Lisa's own retained certificate inputs. Ratified because the caller-tree read is unreachable outside this repository, not because an undelivered host dependency is tolerated."
|
|
4
5
|
},
|
|
5
6
|
"inspected": {
|
|
6
7
|
"workflows": 23,
|
|
7
|
-
"steps":
|
|
8
|
-
"couplings":
|
|
8
|
+
"steps": 636,
|
|
9
|
+
"couplings": 26,
|
|
9
10
|
"inventory": 56
|
|
10
11
|
},
|
|
11
12
|
"counts": {
|
|
12
13
|
"package-backed": 10,
|
|
13
14
|
"apply-lagged": 14,
|
|
14
15
|
"never-delivered": 0,
|
|
15
|
-
"undelivered":
|
|
16
|
+
"undelivered": 2
|
|
16
17
|
},
|
|
17
18
|
"couplings": [
|
|
18
19
|
{
|
|
@@ -184,6 +185,17 @@
|
|
|
184
185
|
],
|
|
185
186
|
"detail": "`quality.yml` is delivered at `@main`, so this step is live in every consumer on their next run. It reads `scripts/check-verification-coverage.mjs` from the CALLER's tree with no package-relative candidate, and Lisa delivers that path on the `lisa apply` channel (typescript/copy-overwrite). The two halves land in the wrong order: this step is live everywhere immediately, the file it needs is live only where somebody applied. The read is unguarded, so an absent path fails the job loudly. Visible, which makes it the better of the two failures."
|
|
186
187
|
},
|
|
188
|
+
{
|
|
189
|
+
"key": "quality.yml::scripts/generate-nightly-e2e-guard-certificate.mjs",
|
|
190
|
+
"workflow": "quality.yml",
|
|
191
|
+
"path": "scripts/generate-nightly-e2e-guard-certificate.mjs",
|
|
192
|
+
"channel": "undelivered",
|
|
193
|
+
"verdict": "undelivered",
|
|
194
|
+
"remedy": "author-the-artifact",
|
|
195
|
+
"guarded": true,
|
|
196
|
+
"lanes": [],
|
|
197
|
+
"detail": "`quality.yml` is delivered at `@main`, so this step is live in every consumer on their next run. It reads `scripts/generate-nightly-e2e-guard-certificate.mjs` from the CALLER's tree, and Lisa ships no such path in any delivery lane — no apply and no bump ever produces it. Either the consumer authors it or this step proves nothing wherever it is absent. The read is guarded by an existence test, so an absent path SKIPS rather than fails — the step posts no context, and an absent required context is not a red one. Nothing reads as broken."
|
|
198
|
+
},
|
|
187
199
|
{
|
|
188
200
|
"key": "quality.yml::scripts/lisa-floor-collisions.mjs",
|
|
189
201
|
"workflow": "quality.yml",
|