@codyswann/lisa 4.84.7 → 4.84.8

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 (150) hide show
  1. package/all/copy-overwrite/scripts/lib/completion-authorization.mjs +282 -0
  2. package/all/copy-overwrite/scripts/lib/completion-proof-adapters.mjs +213 -0
  3. package/all/copy-overwrite/scripts/lib/completion-proof-conformance.mjs +115 -0
  4. package/all/copy-overwrite/scripts/lib/completion-proof-content.mjs +408 -0
  5. package/all/copy-overwrite/scripts/lib/completion-proof-errors.mjs +16 -0
  6. package/all/copy-overwrite/scripts/lib/completion-proof-github.mjs +233 -0
  7. package/all/copy-overwrite/scripts/lib/completion-proof-markdown.mjs +70 -0
  8. package/all/copy-overwrite/scripts/lib/completion-proof-projection.mjs +169 -0
  9. package/all/copy-overwrite/scripts/lib/completion-proof-publisher.mjs +238 -0
  10. package/all/copy-overwrite/scripts/lib/completion-proof-reader.mjs +175 -0
  11. package/all/copy-overwrite/scripts/lib/completion-proof-shape.mjs +374 -0
  12. package/all/copy-overwrite/scripts/lib/completion-proof-source.mjs +174 -0
  13. package/all/copy-overwrite/scripts/lib/history-secret-scanner.mjs +20 -5
  14. package/all/copy-overwrite/scripts/lib/npm-update-helper-inventory.mjs +15 -0
  15. package/all/copy-overwrite/scripts/lib/usage-effectiveness.d.mts +27 -0
  16. package/all/copy-overwrite/scripts/lib/usage-effectiveness.mjs +156 -0
  17. package/all/copy-overwrite/scripts/lib/usage-ledger-integrity.d.mts +8 -0
  18. package/all/copy-overwrite/scripts/lib/usage-ledger-integrity.mjs +141 -0
  19. package/all/copy-overwrite/scripts/lib/usage-ledger.d.mts +89 -0
  20. package/all/copy-overwrite/scripts/lib/usage-ledger.mjs +206 -0
  21. package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +3 -1
  22. package/all/copy-overwrite/scripts/lisa-work-item.mjs +255 -39
  23. package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +141 -3
  24. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  25. package/dist/core/lisa-owned-hash-ledger.js +186 -0
  26. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  27. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  28. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  29. package/dist/core/upstream-evidence-manifest.js +101 -12
  30. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  31. package/dist/opencode/plugin-templates/parity-safety-net.sh +3 -1
  32. package/dist/utils/effectiveness.d.ts +4 -41
  33. package/dist/utils/effectiveness.d.ts.map +1 -1
  34. package/dist/utils/effectiveness.js +2 -126
  35. package/dist/utils/effectiveness.js.map +1 -1
  36. package/dist/utils/usage-accounting.d.ts +5 -100
  37. package/dist/utils/usage-accounting.d.ts.map +1 -1
  38. package/dist/utils/usage-accounting.js +4 -287
  39. package/dist/utils/usage-accounting.js.map +1 -1
  40. package/package.json +4 -4
  41. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  43. package/plugins/lisa/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +15 -1
  44. package/plugins/lisa/.codex-plugin/skills/lisa-tracker-evidence/SKILL.md +7 -1
  45. package/plugins/lisa/.codex-plugin/skills/lisa-tracker-evidence/references/completion-authorization.md +193 -0
  46. package/plugins/lisa/hooks/parity-safety-net.sh +3 -1
  47. package/plugins/lisa/rules/eager/00-rule-index.md +1 -0
  48. package/plugins/lisa/rules/reference/completion-authorization.md +10 -0
  49. package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +15 -1
  50. package/plugins/lisa/skills/lisa-tracker-evidence/SKILL.md +7 -1
  51. package/plugins/lisa/skills/lisa-tracker-evidence/references/completion-authorization.md +193 -0
  52. package/plugins/lisa-agy/hooks/parity-safety-net.sh +3 -1
  53. package/plugins/lisa-agy/plugin.json +1 -1
  54. package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +15 -1
  55. package/plugins/lisa-agy/skills/lisa-tracker-evidence/SKILL.md +7 -1
  56. package/plugins/lisa-agy/skills/lisa-tracker-evidence/references/completion-authorization.md +193 -0
  57. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  59. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  60. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-cdk-devin/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-cdk-devin/.devin-plugin/plugin.json +1 -1
  64. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-copilot/hooks/parity-safety-net.sh +3 -1
  66. package/plugins/lisa-copilot/rules/eager/00-rule-index.md +1 -0
  67. package/plugins/lisa-copilot/rules/reference/completion-authorization.md +10 -0
  68. package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +15 -1
  69. package/plugins/lisa-copilot/skills/lisa-tracker-evidence/SKILL.md +7 -1
  70. package/plugins/lisa-copilot/skills/lisa-tracker-evidence/references/completion-authorization.md +193 -0
  71. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +3 -1
  73. package/plugins/lisa-cursor/rules/00-rule-index.mdc +1 -0
  74. package/plugins/lisa-cursor/rules/completion-authorization-reference.mdc +15 -0
  75. package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +15 -1
  76. package/plugins/lisa-cursor/skills/lisa-tracker-evidence/SKILL.md +7 -1
  77. package/plugins/lisa-cursor/skills/lisa-tracker-evidence/references/completion-authorization.md +193 -0
  78. package/plugins/lisa-devin/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa-devin/.devin-plugin/plugin.json +1 -1
  80. package/plugins/lisa-devin/hooks/parity-safety-net.sh +3 -1
  81. package/plugins/lisa-devin/rules/eager/00-rule-index.md +1 -0
  82. package/plugins/lisa-devin/rules/reference/completion-authorization.md +10 -0
  83. package/plugins/lisa-devin/skills/lisa-drive-pr-to-merge/SKILL.md +15 -1
  84. package/plugins/lisa-devin/skills/lisa-tracker-evidence/SKILL.md +7 -1
  85. package/plugins/lisa-devin/skills/lisa-tracker-evidence/references/completion-authorization.md +193 -0
  86. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  88. package/plugins/lisa-expo-agy/plugin.json +1 -1
  89. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  90. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  91. package/plugins/lisa-expo-devin/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-expo-devin/.devin-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-harper-fabric-devin/.claude-plugin/plugin.json +1 -1
  99. package/plugins/lisa-harper-fabric-devin/.devin-plugin/plugin.json +1 -1
  100. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  101. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  102. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  103. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  104. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  105. package/plugins/lisa-nestjs-devin/.claude-plugin/plugin.json +1 -1
  106. package/plugins/lisa-nestjs-devin/.devin-plugin/plugin.json +1 -1
  107. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  108. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  109. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  110. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  111. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  112. package/plugins/lisa-openclaw-devin/.claude-plugin/plugin.json +1 -1
  113. package/plugins/lisa-openclaw-devin/.devin-plugin/plugin.json +1 -1
  114. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  115. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  116. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  117. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  118. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  119. package/plugins/lisa-phaser-devin/.claude-plugin/plugin.json +1 -1
  120. package/plugins/lisa-phaser-devin/.devin-plugin/plugin.json +1 -1
  121. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  122. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  123. package/plugins/lisa-rails-agy/plugin.json +1 -1
  124. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  125. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  126. package/plugins/lisa-rails-devin/.claude-plugin/plugin.json +1 -1
  127. package/plugins/lisa-rails-devin/.devin-plugin/plugin.json +1 -1
  128. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  129. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  130. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  131. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  132. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  133. package/plugins/lisa-typescript-devin/.claude-plugin/plugin.json +1 -1
  134. package/plugins/lisa-typescript-devin/.devin-plugin/plugin.json +1 -1
  135. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  136. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  137. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  138. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  139. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  140. package/plugins/lisa-wiki-devin/.claude-plugin/plugin.json +1 -1
  141. package/plugins/lisa-wiki-devin/.devin-plugin/plugin.json +1 -1
  142. package/plugins/src/base/hooks/parity-safety-net.sh +3 -1
  143. package/plugins/src/base/rules/eager/00-rule-index.md +1 -0
  144. package/plugins/src/base/rules/reference/completion-authorization.md +10 -0
  145. package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +15 -1
  146. package/plugins/src/base/skills/lisa-tracker-evidence/SKILL.md +7 -1
  147. package/plugins/src/base/skills/lisa-tracker-evidence/references/completion-authorization.md +193 -0
  148. package/scripts/two-channel-couplings.json +2 -2
  149. package/typescript/copy-overwrite/.prettierignore +15 -0
  150. package/typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs +32 -0
@@ -0,0 +1,408 @@
1
+ // This file is managed by Lisa and IS replaced on each `lisa` run.
2
+ // Do not edit directly — durable changes belong upstream in Lisa.
3
+
4
+ /** Validate captured bytes and the canonical v2 claim/obligation conjunctions. */
5
+ import {
6
+ BOUNDARIES,
7
+ KINDS,
8
+ isObject,
9
+ isText,
10
+ requireProof,
11
+ localMarkers,
12
+ section,
13
+ } from "./completion-proof-shape.mjs";
14
+ import { parseLisaUsageSection } from "./usage-ledger.mjs";
15
+ import { verifyLisaUsageSectionIntegrity } from "./usage-ledger-integrity.mjs";
16
+
17
+ /** Find captured structured records without evaluating their contents. */
18
+ function records(value, depth = 0, budget = { remaining: 100000 }) {
19
+ if (depth > 12 || value === null || typeof value !== "object") return [];
20
+ budget.remaining -= 1;
21
+ requireProof(
22
+ budget.remaining >= 0,
23
+ "The typed capture exceeds its bounded structural inspection size."
24
+ );
25
+ const children = Array.isArray(value) ? value : Object.values(value);
26
+ return [
27
+ value,
28
+ ...children.flatMap(child => records(child, depth + 1, budget)),
29
+ ];
30
+ }
31
+
32
+ /** Recognize a captured process tuple, not a status-only assertion. */
33
+ function processCapture(row) {
34
+ return (
35
+ isObject(row) &&
36
+ (isText(row.command) ||
37
+ (Array.isArray(row.command) &&
38
+ row.command.length > 0 &&
39
+ row.command.every(isText))) &&
40
+ typeof row.stdout === "string" &&
41
+ typeof row.stderr === "string" &&
42
+ Number.isInteger(row.exit_code ?? row.exitCode ?? row.exit ?? row.status)
43
+ );
44
+ }
45
+
46
+ /** Scan count substrings once; historical failed runs are real captures too. */
47
+ function testSummary(text) {
48
+ const lower = text.toLowerCase();
49
+ for (let index = 0; index < lower.length; ) {
50
+ if (
51
+ lower.startsWith("# pass ", index) ||
52
+ lower.startsWith("# fail ", index)
53
+ ) {
54
+ if (/[1-9]/.test(lower[index + 7] ?? "")) return true;
55
+ }
56
+ if (!/\d/.test(lower[index])) {
57
+ index += 1;
58
+ continue;
59
+ }
60
+ let nonzero = false;
61
+ while (/\d/.test(lower[index] ?? "")) {
62
+ nonzero ||= lower[index] !== "0";
63
+ index += 1;
64
+ }
65
+ const end = index;
66
+ while (/\s/.test(lower[index] ?? "")) index += 1;
67
+ if (
68
+ nonzero &&
69
+ index > end &&
70
+ ["passed", "passing", "failed", "failing"].some(word =>
71
+ lower.startsWith(word, index)
72
+ )
73
+ )
74
+ return true;
75
+ }
76
+ return false;
77
+ }
78
+
79
+ /** Positive structural checks for the actual captured artifact type. */
80
+ export function validateTypedBytes(kind, bytes, name) {
81
+ requireProof(
82
+ KINDS.includes(kind) && bytes.length > 0,
83
+ `Evidence ${name} is empty or has an unknown type.`
84
+ );
85
+ let parsed;
86
+ try {
87
+ parsed = JSON.parse(bytes.toString("utf8"));
88
+ } catch {
89
+ parsed = null;
90
+ }
91
+ const rows = records(parsed);
92
+ const text = bytes.toString("utf8");
93
+ const image =
94
+ (bytes.length >= 45 &&
95
+ bytes
96
+ .subarray(0, 8)
97
+ .equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])) &&
98
+ bytes.readUInt32BE(8) === 13 &&
99
+ bytes.subarray(12, 16).toString() === "IHDR" &&
100
+ bytes.readUInt32BE(16) > 0 &&
101
+ bytes.readUInt32BE(20) > 0 &&
102
+ bytes.subarray(-8, -4).toString() === "IEND") ||
103
+ (bytes.length > 16 &&
104
+ bytes[0] === 255 &&
105
+ bytes[1] === 216 &&
106
+ bytes.at(-2) === 255 &&
107
+ bytes.at(-1) === 217) ||
108
+ (bytes.length > 20 &&
109
+ bytes.subarray(0, 4).toString() === "RIFF" &&
110
+ bytes.readUInt32LE(4) === bytes.length - 8 &&
111
+ bytes.subarray(8, 12).toString() === "WEBP" &&
112
+ ["VP8 ", "VP8L", "VP8X"].includes(bytes.subarray(12, 16).toString()));
113
+ const checks = {
114
+ screenshot: () => image,
115
+ recording: () =>
116
+ bytes.subarray(4, 8).toString() === "ftyp" ||
117
+ bytes.subarray(0, 4).equals(Buffer.from([26, 69, 223, 163])),
118
+ "cli-output": () => rows.some(processCapture),
119
+ "http-transcript": () =>
120
+ rows.some(
121
+ row =>
122
+ isObject(row.request) &&
123
+ isText(row.request.method) &&
124
+ isText(row.request.url) &&
125
+ isObject(row.response) &&
126
+ Number.isInteger(row.response.status) &&
127
+ row.response.status >= 100 &&
128
+ row.response.status < 600 &&
129
+ (Object.hasOwn(row.response, "body") ||
130
+ row.response.status === 204 ||
131
+ row.request.method.toUpperCase() === "HEAD")
132
+ ),
133
+ "perf-trace": () =>
134
+ rows.some(
135
+ row =>
136
+ (isText(row.methodology) || isObject(row.methodology)) &&
137
+ Array.isArray(row.measurements) &&
138
+ row.measurements.length > 0 &&
139
+ row.measurements.every(
140
+ item =>
141
+ isText(item.metric) &&
142
+ typeof item.value === "number" &&
143
+ Number.isFinite(item.value) &&
144
+ isText(item.unit)
145
+ )
146
+ ),
147
+ "db-query-output": () =>
148
+ rows.some(row => isText(row.query) && Array.isArray(row.rows)),
149
+ "state-dump": () =>
150
+ rows.some(
151
+ row =>
152
+ isObject(row) &&
153
+ ["state", "data", "response", "records"].some(key =>
154
+ Object.hasOwn(row, key)
155
+ ) &&
156
+ ["id", "source", "command", "captured_at"].some(key =>
157
+ Object.hasOwn(row, key)
158
+ )
159
+ ),
160
+ "test-run-log": () =>
161
+ testSummary(text) &&
162
+ /(?:✓|PASS|\bok\s+\d+|testName|test_name|fullName| > )/.test(text),
163
+ "deploy-log": () =>
164
+ rows.some(
165
+ row =>
166
+ (row.status === "completed" &&
167
+ isText(row.conclusion) &&
168
+ (Number.isInteger(row.id) || isText(row.workflow_path))) ||
169
+ (processCapture(row) &&
170
+ /healthy|health|deploy|release/i.test(row.stdout))
171
+ ),
172
+ "log-snippet": () =>
173
+ /\d{4}-\d\d-\d\d[T ][\d:.]+/.test(text) ||
174
+ rows.some(
175
+ row =>
176
+ isText(row.source) && Array.isArray(row.lines) && row.lines.length > 0
177
+ ),
178
+ };
179
+ requireProof(
180
+ checks[kind](),
181
+ `Evidence ${name} does not contain actual ${kind} capture structure.`
182
+ );
183
+ }
184
+
185
+ /** v1/blocked/advisory Stop-hook results are not production authorization. */
186
+ export function validateVerdict(verdict, evidence, runtime) {
187
+ requireProof(
188
+ isObject(verdict) &&
189
+ verdict.schema_version === 2 &&
190
+ verdict.status === "pass" &&
191
+ verdict.not_established_reviewed === true &&
192
+ isObject(verdict.artifact) &&
193
+ isText(verdict.artifact.head_sha) &&
194
+ Array.isArray(verdict.claims) &&
195
+ Array.isArray(verdict.evidence) &&
196
+ Array.isArray(verdict.criteria),
197
+ "The verification verdict must be an evaluable schema2 PASS with reviewed limits and criteria."
198
+ );
199
+ requireProof(
200
+ verdict.criteria.length > 0 &&
201
+ verdict.criteria.every(row => isObject(row) && row.status === "pass") &&
202
+ verdict.claims.every(isObject) &&
203
+ verdict.evidence.every(isObject),
204
+ "Every applicable verification criterion must pass."
205
+ );
206
+ const claims = verdict.claims.filter(row => row.required_for_gate === true);
207
+ const localEvidenceIds = new Set(
208
+ verdict.evidence.map(row => row.evidence_id)
209
+ );
210
+ requireProof(
211
+ localEvidenceIds.size === verdict.evidence.length,
212
+ "The verdict contains duplicate evidence identities."
213
+ );
214
+ requireProof(
215
+ !runtime || claims.length > 0,
216
+ "Runtime verification has no required reaching claims."
217
+ );
218
+ requireProof(
219
+ new Set(verdict.claims.map(row => row.claim_id)).size ===
220
+ verdict.claims.length,
221
+ "Verification claim identifiers are ambiguous."
222
+ );
223
+ for (const claim of claims) {
224
+ const kinds = BOUNDARIES[claim.boundary];
225
+ requireProof(
226
+ isText(claim.claim_id) &&
227
+ kinds &&
228
+ claim.status === "established" &&
229
+ Array.isArray(claim.required_evidence_kinds) &&
230
+ claim.required_evidence_kinds.length > 0 &&
231
+ claim.required_evidence_kinds.every(kind => kinds.includes(kind)) &&
232
+ Array.isArray(claim.evidence_refs) &&
233
+ claim.evidence_refs.length > 0 &&
234
+ Array.isArray(claim.not_established),
235
+ `Required claim ${claim.claim_id ?? "(unnamed)"} is missing or does not reach its boundary.`
236
+ );
237
+ const refs = claim.evidence_refs.map(id => evidence.get(id));
238
+ requireProof(
239
+ claim.evidence_refs.every(id => localEvidenceIds.has(id)) &&
240
+ refs.every(Boolean) &&
241
+ [undefined, "any", "all"].includes(claim.evidence_operator) &&
242
+ (claim.evidence_operator === "all"
243
+ ? claim.required_evidence_kinds.every(kind =>
244
+ refs.some(row => row.kind === kind)
245
+ )
246
+ : refs.some(row => claim.required_evidence_kinds.includes(row.kind))),
247
+ `Required claim ${claim.claim_id} has no attached reaching evidence.`
248
+ );
249
+ }
250
+ for (const row of verdict.evidence) {
251
+ requireProof(
252
+ evidence.has(row.evidence_id) &&
253
+ row.artifact_head_sha === verdict.artifact.head_sha &&
254
+ evidence.get(row.evidence_id).sha256 === row.sha256 &&
255
+ evidence.get(row.evidence_id).artifact_head_sha ===
256
+ row.artifact_head_sha,
257
+ `Verdict evidence ${row.evidence_id ?? "(unnamed)"} has a different identity or byte digest.`
258
+ );
259
+ }
260
+ return new Map(verdict.claims.map(row => [row.claim_id, row]));
261
+ }
262
+
263
+ /** Every declared local marker binds its real bytes; references are excluded. */
264
+ export function validateManifest(record, body, evidence) {
265
+ const declared = localMarkers(body);
266
+ requireProof(
267
+ record.manifest.length === declared.length,
268
+ "The local typed evidence manifest is incomplete."
269
+ );
270
+ for (const marker of declared) {
271
+ const entries = record.manifest.filter(
272
+ row => row.name === marker.name && row.kind === marker.kind
273
+ );
274
+ requireProof(
275
+ entries.length === 1,
276
+ `Missing or ambiguous local evidence ${marker.kind}: ${marker.name}.`
277
+ );
278
+ const entry = evidence.get(entries[0].evidence_id);
279
+ requireProof(
280
+ entry?.kind === marker.kind,
281
+ `Local evidence ${marker.name} has the wrong attachment type.`
282
+ );
283
+ }
284
+ }
285
+
286
+ /** Closed obligation categories cannot be silently omitted or waived by prose. */
287
+ export function validateObligations(record, scope, claims) {
288
+ for (const [name, row] of Object.entries(record.obligations)) {
289
+ const mandatory =
290
+ ["conformance", "usage", "linkage", "acceptance"].includes(name) ||
291
+ (scope.runtime && ["remote", "delivery"].includes(name)) ||
292
+ (name === "typed_manifest" && record.manifest.length > 0);
293
+ requireProof(
294
+ isObject(row) &&
295
+ typeof row.required === "boolean" &&
296
+ (!mandatory || row.required),
297
+ `Completion obligation ${name} cannot be omitted or waived for this work.`
298
+ );
299
+ if (!row.required) {
300
+ const reasons = scope.runtime
301
+ ? ["no-local-markers"]
302
+ : [scope.reason, "no-local-markers"];
303
+ requireProof(
304
+ row.status === "not-applicable" && reasons.includes(row.reason),
305
+ `Completion obligation ${name} has no contract-allowed exemption.`
306
+ );
307
+ continue;
308
+ }
309
+ const expected =
310
+ name === "conformance"
311
+ ? "CONFORMS"
312
+ : name === "acceptance"
313
+ ? "accepted-for-completion"
314
+ : "pass";
315
+ requireProof(
316
+ row.status === expected,
317
+ `Completion obligation ${name} is not ${expected}.`,
318
+ row.status === "pending" ? "pending-proof" : "invalid-proof"
319
+ );
320
+ }
321
+ if (scope.runtime)
322
+ requireProof(
323
+ Array.isArray(record.obligations.remote.claim_ids) &&
324
+ record.obligations.remote.claim_ids.length > 0 &&
325
+ record.obligations.remote.claim_ids.every(id => {
326
+ const claim = claims.get(id);
327
+ return (
328
+ claim?.required_for_gate === true &&
329
+ claim.status === "established" &&
330
+ claim.verification_role === "remote" &&
331
+ claim.boundary !== "code-unit"
332
+ );
333
+ }),
334
+ "Required remote verification has no established empirical claims against the delivered artifact."
335
+ );
336
+ const criteria = new Set();
337
+ for (const group of record.requirements) {
338
+ requireProof(
339
+ isText(group.requirement_id) &&
340
+ !criteria.has(group.requirement_id) &&
341
+ group.operator === "all" &&
342
+ Array.isArray(group.claim_ids) &&
343
+ (!scope.runtime || group.claim_ids.length > 0),
344
+ "A requirement has an empty, duplicate or non-conjunctive claim map."
345
+ );
346
+ criteria.add(group.requirement_id);
347
+ requireProof(
348
+ group.claim_ids.every(
349
+ id =>
350
+ claims.get(id)?.required_for_gate === true &&
351
+ claims.get(id).status === "established"
352
+ ),
353
+ `Requirement ${group.requirement_id} is missing a required claim conjunct.`
354
+ );
355
+ }
356
+ requireProof(
357
+ record.obligations.conformance.requirement_ids?.length === criteria.size &&
358
+ record.obligations.conformance.requirement_ids.every(id =>
359
+ criteria.has(id)
360
+ ),
361
+ "The CONFORMS result does not cover the complete requirement map."
362
+ );
363
+ }
364
+
365
+ /** Use the same canonical parser/integrity implementation as the TS writer. */
366
+ export function validateVerifyUsage(documents, ref, expectedIds, context) {
367
+ requireProof(
368
+ Array.isArray(expectedIds) && expectedIds.length > 0,
369
+ "Canonical Verify usage is missing."
370
+ );
371
+ const found = new Map();
372
+ for (const document of documents) {
373
+ const usage = section(document, "Lisa Usage");
374
+ if (!usage) continue;
375
+ const canonical = `## Lisa Usage\n${usage}\n`;
376
+ let parsed;
377
+ try {
378
+ parsed = parseLisaUsageSection(canonical);
379
+ } catch {
380
+ requireProof(
381
+ false,
382
+ "The current canonical Verify usage section is malformed."
383
+ );
384
+ }
385
+ if (!parsed.range) continue;
386
+ requireProof(
387
+ verifyLisaUsageSectionIntegrity(canonical).ok,
388
+ "The canonical usage ledger failed its stored-surface integrity check."
389
+ );
390
+ for (const entry of parsed.entries) found.set(entry.entryId, entry);
391
+ }
392
+ for (const id of expectedIds) {
393
+ const entry = found.get(id);
394
+ const identity = String(entry?.artifactRef ?? "").replace(/^github:/, "");
395
+ const linear =
396
+ context.provider === "linear" &&
397
+ identity === `linear:${context.scope.split("/")[0]}/${ref}`;
398
+ requireProof(
399
+ ["lisa-verify", "verify"].includes(entry?.flow) &&
400
+ (linear ||
401
+ identity === ref ||
402
+ identity === `https://github.com/${ref.replace("#", "/issues/")}`),
403
+ `Canonical Verify usage entry ${id} is absent or belongs to another work item.`
404
+ );
405
+ // acceptedAt/lifecycleSources attest the later native terminal event. They
406
+ // are deliberately not prerequisites of the transition they describe.
407
+ }
408
+ }
@@ -0,0 +1,16 @@
1
+ // This file is managed by Lisa and IS replaced on each `lisa` run.
2
+ // Do not edit directly — durable changes belong upstream in Lisa.
3
+
4
+ /** Explicit shared completion refusal categories. */
5
+ export class CompletionProofError extends Error {
6
+ constructor(category, message) {
7
+ super(message);
8
+ this.name = "CompletionProofError";
9
+ this.category = category;
10
+ }
11
+ }
12
+
13
+ /** Refuse unusable data with the exact obligation an operator can repair. */
14
+ export function requireProof(condition, message, category = "invalid-proof") {
15
+ if (!condition) throw new CompletionProofError(category, message);
16
+ }
@@ -0,0 +1,233 @@
1
+ // This file is managed by Lisa and IS replaced on each `lisa` run.
2
+ // Do not edit directly — durable changes belong upstream in Lisa.
3
+
4
+ /** Bounded native GitHub transport; no locator URL is executed or fetched. */
5
+ import {
6
+ CompletionProofError,
7
+ requireProof,
8
+ isId,
9
+ MAX_ATTACHMENT_BYTES,
10
+ MAX_NATIVE_JSON_BYTES,
11
+ } from "./completion-proof-shape.mjs";
12
+
13
+ const READ_ERROR = "read-error";
14
+
15
+ /** The transport reuses the canonical CLI's deadline and failure boundary. */
16
+ export function githubProofTransport(run, repository, policy = {}) {
17
+ requireProof(
18
+ /^[\w.-]+\/[\w.-]+$/.test(repository),
19
+ "The implementation repository is not canonical."
20
+ );
21
+ const root = `repos/${repository}`;
22
+ function call(args, options = {}) {
23
+ const maxBuffer = options.maxBuffer ?? MAX_NATIVE_JSON_BYTES;
24
+ const result = run("gh", args, {
25
+ allowFailure: true,
26
+ maxBuffer,
27
+ ...options,
28
+ });
29
+ if (
30
+ result.status !== 0 ||
31
+ result.error ||
32
+ Buffer.byteLength(result.stdout ?? "") > maxBuffer
33
+ )
34
+ throw new CompletionProofError(
35
+ READ_ERROR,
36
+ "A required GitHub completion-proof read did not complete; restore repository access and retry canonical Verify/evidence."
37
+ );
38
+ return result.stdout;
39
+ }
40
+ function json(path, paginated = false) {
41
+ requireProof(
42
+ path.startsWith(`${root}/`) || path === root,
43
+ "A completion read selected another repository."
44
+ );
45
+ const bytes = call(
46
+ paginated ? ["api", "--paginate", "--slurp", path] : ["api", path]
47
+ );
48
+ try {
49
+ const parsed = JSON.parse(bytes);
50
+ if (paginated) {
51
+ requireProof(
52
+ Array.isArray(parsed) && parsed.length <= 100,
53
+ "The required GitHub pagination is incomplete or exceeds its bound."
54
+ );
55
+ }
56
+ return parsed;
57
+ } catch (error) {
58
+ if (error instanceof CompletionProofError) throw error;
59
+ throw new CompletionProofError(
60
+ READ_ERROR,
61
+ "A required GitHub proof response is not complete JSON."
62
+ );
63
+ }
64
+ }
65
+ function trustedPublisher(
66
+ asset,
67
+ record,
68
+ { publishing = false, identityOnly = false } = {}
69
+ ) {
70
+ requireProof(
71
+ isId(asset.uploader?.id),
72
+ "The proof asset has no native uploader identity."
73
+ );
74
+ if (asset.uploader.type === "User") {
75
+ const user = json(
76
+ `${root}/collaborators/${encodeURIComponent(asset.uploader.login)}/permission`
77
+ );
78
+ requireProof(
79
+ user.user?.id === asset.uploader.id &&
80
+ ["write", "maintain", "admin"].includes(user.permission),
81
+ "The completion-proof publisher does not have repository write authority."
82
+ );
83
+ return { ready: true };
84
+ }
85
+ const allowed = policy.automationPublishers ?? [];
86
+ const rule = allowed.find(
87
+ row =>
88
+ row.user_id === asset.uploader.id &&
89
+ row.type === asset.uploader.type &&
90
+ row.type === "Bot" &&
91
+ row.repository_id === record.repository.id
92
+ );
93
+ requireProof(
94
+ rule && isId(record.producer?.workflow_run_id),
95
+ "The proof uploader is not an explicitly configured repository automation publisher."
96
+ );
97
+ requireProof(
98
+ isId(record.producer.workflow_attempt),
99
+ "The automation publisher attempt is not pinned."
100
+ );
101
+ const runInfo = json(
102
+ `${root}/actions/runs/${record.producer.workflow_run_id}/attempts/${record.producer.workflow_attempt}`
103
+ );
104
+ requireProof(
105
+ runInfo.repository?.id === rule.repository_id &&
106
+ runInfo.workflow_id === rule.workflow_id &&
107
+ runInfo.path === rule.workflow_path &&
108
+ rule.events?.includes(runInfo.event) &&
109
+ runInfo.head_branch === rule.ref &&
110
+ runInfo.head_sha === record.producer.workflow_head &&
111
+ runInfo.run_attempt === record.producer.workflow_attempt &&
112
+ Date.parse(asset.created_at) >= Date.parse(runInfo.run_started_at) &&
113
+ Date.parse(asset.created_at) <=
114
+ (runInfo.status === "completed"
115
+ ? Date.parse(runInfo.updated_at)
116
+ : Date.now()),
117
+ "The automation proof publisher has no matching native repository workflow provenance."
118
+ );
119
+ if (identityOnly)
120
+ return {
121
+ ready:
122
+ runInfo.status === "completed" && runInfo.conclusion === "success",
123
+ };
124
+ if (!publishing) {
125
+ requireProof(
126
+ runInfo.status === "completed",
127
+ "The pinned proof-publisher attempt is still pending.",
128
+ "pending-proof"
129
+ );
130
+ requireProof(
131
+ runInfo.conclusion === "success",
132
+ "The pinned proof-publisher attempt did not succeed; a rerun cannot upgrade this record."
133
+ );
134
+ } else
135
+ requireProof(
136
+ runInfo.status !== "completed" || runInfo.conclusion === "success",
137
+ "The pinned proof-publisher attempt has already failed."
138
+ );
139
+ // Native run facts corroborate repository policy; they do not claim which
140
+ // workflow step uploaded a release asset or identify the triggering user.
141
+ return {
142
+ ready: runInfo.status === "completed" && runInfo.conclusion === "success",
143
+ };
144
+ }
145
+ function eligibleCommentAuthor(user, repositoryId) {
146
+ if (!isId(user?.id)) return false;
147
+ if (user.type !== "User")
148
+ return (policy.automationPublishers ?? []).some(
149
+ row =>
150
+ row.user_id === user.id &&
151
+ row.type === user.type &&
152
+ row.type === "Bot" &&
153
+ row.repository_id === repositoryId
154
+ );
155
+ const result = run(
156
+ "gh",
157
+ [
158
+ "api",
159
+ `${root}/collaborators/${encodeURIComponent(user.login)}/permission`,
160
+ ],
161
+ { allowFailure: true, maxBuffer: MAX_NATIVE_JSON_BYTES }
162
+ );
163
+ if (result.status !== 0) {
164
+ if (/HTTP 404/.test(result.stderr ?? "")) return false;
165
+ throw new CompletionProofError(
166
+ READ_ERROR,
167
+ "The native proof publisher's repository authority could not be read."
168
+ );
169
+ }
170
+ let permission;
171
+ try {
172
+ permission = JSON.parse(result.stdout);
173
+ } catch {
174
+ throw new CompletionProofError(
175
+ READ_ERROR,
176
+ "Native publisher permissions are unreadable."
177
+ );
178
+ }
179
+ return (
180
+ permission.user?.id === user.id &&
181
+ ["write", "maintain", "admin"].includes(permission.permission)
182
+ );
183
+ }
184
+ function asset(id, max = MAX_ATTACHMENT_BYTES) {
185
+ requireProof(
186
+ isId(id),
187
+ "An attached proof selected an invalid native asset identifier."
188
+ );
189
+ const metadata = json(`${root}/releases/assets/${id}`);
190
+ let url;
191
+ try {
192
+ url = new URL(metadata.browser_download_url ?? "https://invalid.invalid");
193
+ } catch {
194
+ requireProof(
195
+ false,
196
+ "The native proof attachment metadata has an unreadable repository URL."
197
+ );
198
+ }
199
+ requireProof(
200
+ metadata.id === id &&
201
+ metadata.state === "uploaded" &&
202
+ Number.isSafeInteger(metadata.size) &&
203
+ metadata.size > 0 &&
204
+ metadata.size <= max &&
205
+ url.origin === "https://github.com" &&
206
+ url.pathname.startsWith(`/${repository}/releases/download/pr-assets/`),
207
+ "The proof attachment is absent, empty, too large or outside the implementation repository's pr-assets release."
208
+ );
209
+ const bytes = call(
210
+ [
211
+ "api",
212
+ `${root}/releases/assets/${id}`,
213
+ "--header",
214
+ "Accept: application/octet-stream",
215
+ ],
216
+ { binary: true, maxBuffer: max }
217
+ );
218
+ requireProof(
219
+ Buffer.isBuffer(bytes) && bytes.length === metadata.size,
220
+ "The proof attachment download is incomplete."
221
+ );
222
+ return { metadata, bytes };
223
+ }
224
+ return {
225
+ call,
226
+ json,
227
+ asset,
228
+ trustedPublisher,
229
+ eligibleCommentAuthor,
230
+ root,
231
+ repository,
232
+ };
233
+ }