@codyswann/lisa 4.21.5 → 4.22.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.
Files changed (130) hide show
  1. package/all/copy-overwrite/scripts/lisa-environment-prepare.mjs +28 -12
  2. package/dist/cli/doctor-nightly-e2e-guard-command-files.d.ts +19 -0
  3. package/dist/cli/doctor-nightly-e2e-guard-command-files.d.ts.map +1 -0
  4. package/dist/cli/doctor-nightly-e2e-guard-command-files.js +221 -0
  5. package/dist/cli/doctor-nightly-e2e-guard-command-files.js.map +1 -0
  6. package/dist/cli/doctor-nightly-e2e-guard-contract.d.ts +113 -0
  7. package/dist/cli/doctor-nightly-e2e-guard-contract.d.ts.map +1 -0
  8. package/dist/cli/doctor-nightly-e2e-guard-contract.js +87 -0
  9. package/dist/cli/doctor-nightly-e2e-guard-contract.js.map +1 -0
  10. package/dist/cli/doctor-nightly-e2e-guard-deadline.d.ts +61 -0
  11. package/dist/cli/doctor-nightly-e2e-guard-deadline.d.ts.map +1 -0
  12. package/dist/cli/doctor-nightly-e2e-guard-deadline.js +97 -0
  13. package/dist/cli/doctor-nightly-e2e-guard-deadline.js.map +1 -0
  14. package/dist/cli/doctor-nightly-e2e-guard-evidence.d.ts +52 -0
  15. package/dist/cli/doctor-nightly-e2e-guard-evidence.d.ts.map +1 -0
  16. package/dist/cli/doctor-nightly-e2e-guard-evidence.js +68 -0
  17. package/dist/cli/doctor-nightly-e2e-guard-evidence.js.map +1 -0
  18. package/dist/cli/doctor-nightly-e2e-guard-graph.d.ts +14 -0
  19. package/dist/cli/doctor-nightly-e2e-guard-graph.d.ts.map +1 -0
  20. package/dist/cli/doctor-nightly-e2e-guard-graph.js +130 -0
  21. package/dist/cli/doctor-nightly-e2e-guard-graph.js.map +1 -0
  22. package/dist/cli/doctor-nightly-e2e-guard-io.d.ts +41 -0
  23. package/dist/cli/doctor-nightly-e2e-guard-io.d.ts.map +1 -0
  24. package/dist/cli/doctor-nightly-e2e-guard-io.js +239 -0
  25. package/dist/cli/doctor-nightly-e2e-guard-io.js.map +1 -0
  26. package/dist/cli/doctor-nightly-e2e-guard-job.d.ts +31 -0
  27. package/dist/cli/doctor-nightly-e2e-guard-job.d.ts.map +1 -0
  28. package/dist/cli/doctor-nightly-e2e-guard-job.js +156 -0
  29. package/dist/cli/doctor-nightly-e2e-guard-job.js.map +1 -0
  30. package/dist/cli/doctor-nightly-e2e-guard-proof.d.ts +42 -0
  31. package/dist/cli/doctor-nightly-e2e-guard-proof.d.ts.map +1 -0
  32. package/dist/cli/doctor-nightly-e2e-guard-proof.js +57 -0
  33. package/dist/cli/doctor-nightly-e2e-guard-proof.js.map +1 -0
  34. package/dist/cli/doctor-nightly-e2e-guard-remediation.d.ts +32 -0
  35. package/dist/cli/doctor-nightly-e2e-guard-remediation.d.ts.map +1 -0
  36. package/dist/cli/doctor-nightly-e2e-guard-remediation.js +55 -0
  37. package/dist/cli/doctor-nightly-e2e-guard-remediation.js.map +1 -0
  38. package/dist/cli/doctor-nightly-e2e-guard-runtime.d.ts +34 -0
  39. package/dist/cli/doctor-nightly-e2e-guard-runtime.d.ts.map +1 -0
  40. package/dist/cli/doctor-nightly-e2e-guard-runtime.js +93 -0
  41. package/dist/cli/doctor-nightly-e2e-guard-runtime.js.map +1 -0
  42. package/dist/cli/doctor-nightly-e2e-guard-scan.d.ts +17 -0
  43. package/dist/cli/doctor-nightly-e2e-guard-scan.d.ts.map +1 -0
  44. package/dist/cli/doctor-nightly-e2e-guard-scan.js +86 -0
  45. package/dist/cli/doctor-nightly-e2e-guard-scan.js.map +1 -0
  46. package/dist/cli/doctor-nightly-e2e-guard-shell-lexer.d.ts +38 -0
  47. package/dist/cli/doctor-nightly-e2e-guard-shell-lexer.d.ts.map +1 -0
  48. package/dist/cli/doctor-nightly-e2e-guard-shell-lexer.js +160 -0
  49. package/dist/cli/doctor-nightly-e2e-guard-shell-lexer.js.map +1 -0
  50. package/dist/cli/doctor-nightly-e2e-guard-shell.d.ts +40 -0
  51. package/dist/cli/doctor-nightly-e2e-guard-shell.d.ts.map +1 -0
  52. package/dist/cli/doctor-nightly-e2e-guard-shell.js +201 -0
  53. package/dist/cli/doctor-nightly-e2e-guard-shell.js.map +1 -0
  54. package/dist/cli/doctor-nightly-e2e-guard.d.ts +36 -0
  55. package/dist/cli/doctor-nightly-e2e-guard.d.ts.map +1 -0
  56. package/dist/cli/doctor-nightly-e2e-guard.js +186 -0
  57. package/dist/cli/doctor-nightly-e2e-guard.js.map +1 -0
  58. package/dist/cli/doctor.d.ts.map +1 -1
  59. package/dist/cli/doctor.js +7 -0
  60. package/dist/cli/doctor.js.map +1 -1
  61. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  62. package/dist/core/lisa-owned-hash-ledger.js +2 -0
  63. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  64. package/dist/core/nightly-e2e-guard-behavior-certificate.d.ts +25 -0
  65. package/dist/core/nightly-e2e-guard-behavior-certificate.d.ts.map +1 -0
  66. package/dist/core/nightly-e2e-guard-behavior-certificate.js +30 -0
  67. package/dist/core/nightly-e2e-guard-behavior-certificate.js.map +1 -0
  68. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  69. package/dist/core/upstream-evidence-manifest.js +32 -5
  70. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  71. package/dist/migrations/ensure-playwright-dedicated-caller.d.ts +0 -2
  72. package/dist/migrations/ensure-playwright-dedicated-caller.d.ts.map +1 -1
  73. package/dist/migrations/ensure-playwright-dedicated-caller.js +12 -40
  74. package/dist/migrations/ensure-playwright-dedicated-caller.js.map +1 -1
  75. package/expo/create-only/.github/workflows/maestro-e2e.yml +6 -0
  76. package/expo/create-only/.github/workflows/playwright-e2e.yml +5 -5
  77. package/package.json +5 -3
  78. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  80. package/plugins/lisa-agy/plugin.json +1 -1
  81. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  83. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  84. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  85. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  86. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  89. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  90. package/plugins/lisa-expo-agy/plugin.json +1 -1
  91. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  93. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  94. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  95. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  96. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  97. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  98. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  99. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  100. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  101. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  102. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  103. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  104. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  105. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  106. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  108. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  109. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  110. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  111. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  112. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  113. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  114. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  115. package/plugins/lisa-rails-agy/plugin.json +1 -1
  116. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  117. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  118. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  119. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  120. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  121. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  122. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  123. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  124. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  125. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  126. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  127. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  128. package/scripts/check-derived-artifacts.mjs +32 -5
  129. package/scripts/generate-nightly-e2e-guard-certificate.mjs +370 -0
  130. package/scripts/two-channel-couplings.json +16 -4
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.21.5",
3
+ "version": "4.22.0",
4
4
  "description": "TypeScript-specific hooks for formatting, linting, and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.21.5",
3
+ "version": "4.22.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.21.5",
3
+ "version": "4.22.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.21.5",
3
+ "version": "4.22.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.21.5",
3
+ "version": "4.22.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.21.5",
3
+ "version": "4.22.0",
4
4
  "description": "Distributable LLM Wiki kernel — ingest, query, lint, and maintain a git-native markdown knowledge base across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.21.5",
3
+ "version": "4.22.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.21.5",
3
+ "version": "4.22.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.21.5",
3
+ "version": "4.22.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -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 two artifacts derived from tracked sources:
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 two checks deliberately
24
- * assert different properties, and both are correct for their artifact:
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
- * Both triggers below are deliberate **supersets** of what each artifact covers.
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. Both generators read the git index and the working tree,\n` +
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": 634,
8
- "couplings": 25,
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": 1
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",