@codyswann/lisa 4.84.6 → 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 (165) 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 +108 -15
  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/block-host-name-leak.mjs +25 -13
  47. package/plugins/lisa/hooks/host-name-command-context.mjs +16 -4
  48. package/plugins/lisa/hooks/parity-safety-net.sh +3 -1
  49. package/plugins/lisa/rules/eager/00-rule-index.md +1 -0
  50. package/plugins/lisa/rules/reference/completion-authorization.md +10 -0
  51. package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +15 -1
  52. package/plugins/lisa/skills/lisa-tracker-evidence/SKILL.md +7 -1
  53. package/plugins/lisa/skills/lisa-tracker-evidence/references/completion-authorization.md +193 -0
  54. package/plugins/lisa-agy/hooks/block-host-name-leak.mjs +25 -13
  55. package/plugins/lisa-agy/hooks/host-name-command-context.mjs +16 -4
  56. package/plugins/lisa-agy/hooks/parity-safety-net.sh +3 -1
  57. package/plugins/lisa-agy/plugin.json +1 -1
  58. package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +15 -1
  59. package/plugins/lisa-agy/skills/lisa-tracker-evidence/SKILL.md +7 -1
  60. package/plugins/lisa-agy/skills/lisa-tracker-evidence/references/completion-authorization.md +193 -0
  61. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  63. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  64. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-cdk-devin/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-cdk-devin/.devin-plugin/plugin.json +1 -1
  68. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-copilot/hooks/block-host-name-leak.mjs +25 -13
  70. package/plugins/lisa-copilot/hooks/host-name-command-context.mjs +16 -4
  71. package/plugins/lisa-copilot/hooks/parity-safety-net.sh +3 -1
  72. package/plugins/lisa-copilot/rules/eager/00-rule-index.md +1 -0
  73. package/plugins/lisa-copilot/rules/reference/completion-authorization.md +10 -0
  74. package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +15 -1
  75. package/plugins/lisa-copilot/skills/lisa-tracker-evidence/SKILL.md +7 -1
  76. package/plugins/lisa-copilot/skills/lisa-tracker-evidence/references/completion-authorization.md +193 -0
  77. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-cursor/hooks/block-host-name-leak.mjs +25 -13
  79. package/plugins/lisa-cursor/hooks/host-name-command-context.mjs +16 -4
  80. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +3 -1
  81. package/plugins/lisa-cursor/rules/00-rule-index.mdc +1 -0
  82. package/plugins/lisa-cursor/rules/completion-authorization-reference.mdc +15 -0
  83. package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +15 -1
  84. package/plugins/lisa-cursor/skills/lisa-tracker-evidence/SKILL.md +7 -1
  85. package/plugins/lisa-cursor/skills/lisa-tracker-evidence/references/completion-authorization.md +193 -0
  86. package/plugins/lisa-devin/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-devin/.devin-plugin/plugin.json +1 -1
  88. package/plugins/lisa-devin/hooks/block-host-name-leak.mjs +25 -13
  89. package/plugins/lisa-devin/hooks/host-name-command-context.mjs +16 -4
  90. package/plugins/lisa-devin/hooks/parity-safety-net.sh +3 -1
  91. package/plugins/lisa-devin/rules/eager/00-rule-index.md +1 -0
  92. package/plugins/lisa-devin/rules/reference/completion-authorization.md +10 -0
  93. package/plugins/lisa-devin/skills/lisa-drive-pr-to-merge/SKILL.md +15 -1
  94. package/plugins/lisa-devin/skills/lisa-tracker-evidence/SKILL.md +7 -1
  95. package/plugins/lisa-devin/skills/lisa-tracker-evidence/references/completion-authorization.md +193 -0
  96. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  97. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  98. package/plugins/lisa-expo-agy/plugin.json +1 -1
  99. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  100. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  101. package/plugins/lisa-expo-devin/.claude-plugin/plugin.json +1 -1
  102. package/plugins/lisa-expo-devin/.devin-plugin/plugin.json +1 -1
  103. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  104. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  105. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  106. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  108. package/plugins/lisa-harper-fabric-devin/.claude-plugin/plugin.json +1 -1
  109. package/plugins/lisa-harper-fabric-devin/.devin-plugin/plugin.json +1 -1
  110. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  111. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  112. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  113. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  114. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  115. package/plugins/lisa-nestjs-devin/.claude-plugin/plugin.json +1 -1
  116. package/plugins/lisa-nestjs-devin/.devin-plugin/plugin.json +1 -1
  117. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  118. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  119. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  120. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  121. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  122. package/plugins/lisa-openclaw-devin/.claude-plugin/plugin.json +1 -1
  123. package/plugins/lisa-openclaw-devin/.devin-plugin/plugin.json +1 -1
  124. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  125. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  126. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  127. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  128. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  129. package/plugins/lisa-phaser-devin/.claude-plugin/plugin.json +1 -1
  130. package/plugins/lisa-phaser-devin/.devin-plugin/plugin.json +1 -1
  131. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  132. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  133. package/plugins/lisa-rails-agy/plugin.json +1 -1
  134. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  135. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  136. package/plugins/lisa-rails-devin/.claude-plugin/plugin.json +1 -1
  137. package/plugins/lisa-rails-devin/.devin-plugin/plugin.json +1 -1
  138. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  139. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  140. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  141. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  142. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  143. package/plugins/lisa-typescript-devin/.claude-plugin/plugin.json +1 -1
  144. package/plugins/lisa-typescript-devin/.devin-plugin/plugin.json +1 -1
  145. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  146. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  147. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  148. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  149. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  150. package/plugins/lisa-wiki-devin/.claude-plugin/plugin.json +1 -1
  151. package/plugins/lisa-wiki-devin/.devin-plugin/plugin.json +1 -1
  152. package/plugins/src/base/hooks/block-host-name-leak.mjs +25 -13
  153. package/plugins/src/base/hooks/host-name-command-context.mjs +16 -4
  154. package/plugins/src/base/hooks/parity-safety-net.sh +3 -1
  155. package/plugins/src/base/rules/eager/00-rule-index.md +1 -0
  156. package/plugins/src/base/rules/reference/completion-authorization.md +10 -0
  157. package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +15 -1
  158. package/plugins/src/base/skills/lisa-tracker-evidence/SKILL.md +7 -1
  159. package/plugins/src/base/skills/lisa-tracker-evidence/references/completion-authorization.md +193 -0
  160. package/scripts/lib/host-name-expanded-command-cases.mjs +286 -0
  161. package/scripts/lib/host-name-field-occurrence-cases.mjs +60 -0
  162. package/scripts/two-channel-couplings.json +2 -2
  163. package/scripts/verify-host-name-target-scope.mjs +27 -1
  164. package/typescript/copy-overwrite/.prettierignore +15 -0
  165. package/typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs +32 -0
@@ -0,0 +1,70 @@
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 top-level boundaries; examples cannot become runtime declarations. */
5
+ import { requireProof } from "./completion-proof-errors.mjs";
6
+
7
+ /** Scan the same maximal fence run without ambiguous regex partitions. */
8
+ function fenceMarker(line) {
9
+ let start = 0;
10
+ while (start < 3 && line[start] === " ") start += 1;
11
+ const character = line[start];
12
+ if (character !== "`" && character !== "~") return null;
13
+ let end = start;
14
+ while (line[end] === character) end += 1;
15
+ if (end - start < 3) return null;
16
+ const suffix = line.slice(end);
17
+ if (/[\r\n\u2028\u2029]/.test(suffix)) return null;
18
+ return [line, line.slice(start, end), suffix];
19
+ }
20
+
21
+ /** Retain original offsets while excluding fenced/indented examples and comments. */
22
+ export function topLevelLines(body) {
23
+ const rows = [];
24
+ let fence = null;
25
+ let comment = false;
26
+ let start = 0;
27
+ for (const raw of body.split("\n")) {
28
+ const end = start + raw.length + (start + raw.length < body.length ? 1 : 0);
29
+ const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
30
+ const marker = fenceMarker(line);
31
+ if (fence) {
32
+ if (
33
+ marker &&
34
+ marker[1][0] === fence.character &&
35
+ marker[1].length >= fence.length &&
36
+ !marker[2].trim()
37
+ )
38
+ fence = null;
39
+ } else if (comment) {
40
+ if (line.includes("-->")) comment = false;
41
+ } else if (marker && !(marker[1][0] === "`" && marker[2].includes("`"))) {
42
+ fence = { character: marker[1][0], length: marker[1].length };
43
+ } else if (/^ {0,3}<!--/.test(line) && !line.includes("-->")) {
44
+ comment = true;
45
+ } else if (!/^ {4}|^\t/.test(line)) {
46
+ rows.push({
47
+ text: line.trim(),
48
+ start,
49
+ end,
50
+ heading: /^ {0,3}##[ \t]+/.test(line),
51
+ });
52
+ }
53
+ start = end;
54
+ }
55
+ return rows;
56
+ }
57
+
58
+ /** Only actual top-level H2 sections participate in native declarations. */
59
+ export function markdownSection(body, heading) {
60
+ const headings = topLevelLines(body).filter(row => row.heading);
61
+ const matches = headings.filter(row => row.text === `## ${heading}`);
62
+ requireProof(
63
+ matches.length <= 1,
64
+ `Duplicate ${heading} sections make completion requirements ambiguous.`
65
+ );
66
+ if (!matches.length) return "";
67
+ const selected = matches[0];
68
+ const next = headings.find(row => row.start > selected.start);
69
+ return body.slice(selected.end, next?.start ?? body.length).trim();
70
+ }
@@ -0,0 +1,169 @@
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
+ /** Exclude only actual canonical accounting; retain unknown requirement text. */
5
+ import { parseLisaUsageSection } from "./usage-ledger.mjs";
6
+ import { verifyLisaUsageSectionIntegrity } from "./usage-ledger-integrity.mjs";
7
+ import { requireProof } from "./completion-proof-errors.mjs";
8
+
9
+ /** Stable presentation matching; this does not parse or authorize usage data. */
10
+ const normalizedRow = line =>
11
+ line
12
+ .trim()
13
+ .split("|")
14
+ .map(part => part.trim())
15
+ .join("|");
16
+
17
+ /** Match the canonical writer's visible table rows to the already parsed data. */
18
+ function accountingRows(entries) {
19
+ const known = new Set([
20
+ "## Lisa Usage",
21
+ "_This section is managed by Lisa. Rewrites update matching usage entries in place and preserve older rows._",
22
+ "|Flow|Source|Model|Tokens|Cost|",
23
+ "|---|---|---|---:|---:|",
24
+ "|_No direct entries recorded_|||||",
25
+ ]);
26
+ for (const entry of entries) {
27
+ const tokens =
28
+ entry.totalTokens !== null
29
+ ? String(entry.totalTokens)
30
+ : entry.measuredSubsetTokens == null
31
+ ? "null"
32
+ : `${entry.measuredSubsetTokens} measured subset`;
33
+ const cost =
34
+ entry.cost === null
35
+ ? "null"
36
+ : entry.currency
37
+ ? `${entry.cost} ${entry.currency}`
38
+ : String(entry.cost);
39
+ known.add(
40
+ normalizedRow(
41
+ `| ${entry.flow} | ${entry.source} | ${entry.provider}/${entry.model} | ${tokens} | ${cost} |`
42
+ )
43
+ );
44
+ }
45
+ return known;
46
+ }
47
+
48
+ /** A known token must positively parse; a tag-shaped comment is insufficient. */
49
+ function canonicalToken(token, section) {
50
+ if (token.startsWith("<!-- lisa:usage-entry "))
51
+ return (
52
+ parseLisaUsageSection(`## Lisa Usage\n${token}\n`).entries.length === 1
53
+ );
54
+ if (token.startsWith("<!-- lisa:usage-rollup "))
55
+ return parseLisaUsageSection(`## Lisa Usage\n${token}\n`).rollup !== null;
56
+ if (
57
+ token ===
58
+ "<!-- lisa:usage-rollup-token-status child_tokens_incomplete=true -->"
59
+ )
60
+ return true;
61
+ if (/^<!-- lisa:usage-effectiveness entry_id=\S+ data=\S+ -->$/.test(token))
62
+ return true;
63
+ const subset =
64
+ /^<!-- lisa:usage-entry-measured-subset entry_id=(\S+) measured_subset_tokens=(\S+) -->$/.exec(
65
+ token
66
+ );
67
+ return (
68
+ subset &&
69
+ section.entries.some(
70
+ row => encodeURIComponent(row.entryId) === subset[1]
71
+ ) &&
72
+ (subset[2] === "null" || Number.isFinite(Number(subset[2])))
73
+ );
74
+ }
75
+
76
+ /** Unknown prose/table rows within usage remain part of the full spec digest. */
77
+ export function usageSpecRemainder(document) {
78
+ let parsed;
79
+ try {
80
+ parsed = parseLisaUsageSection(document);
81
+ requireProof(
82
+ verifyLisaUsageSectionIntegrity(document).ok,
83
+ "The canonical usage section is not an intact stored ledger."
84
+ );
85
+ } catch {
86
+ requireProof(
87
+ false,
88
+ "The canonical usage section is malformed; restore its actual accounting before completion."
89
+ );
90
+ }
91
+ const known = accountingRows(parsed.entries);
92
+ return document
93
+ .split(/(?<=\n)/)
94
+ .map(raw => {
95
+ const line = raw.replace(
96
+ /<!-- lisa:usage-(?:entry(?:-measured-subset)?|rollup(?:-token-status)?|effectiveness) [^\r\n]*? -->/g,
97
+ token => (canonicalToken(token, parsed) ? "" : token)
98
+ );
99
+ return !line.trim() || known.has(normalizedRow(line)) ? "" : line;
100
+ })
101
+ .join("");
102
+ }
103
+
104
+ /** Only a closed canonical metadata schema can be omitted from the primary spec. */
105
+ export function canonicalCompletionBlock(name, payload) {
106
+ let value;
107
+ try {
108
+ value = JSON.parse(payload);
109
+ } catch {
110
+ return false;
111
+ }
112
+ if (
113
+ payload !== JSON.stringify(value) &&
114
+ payload !== JSON.stringify(value, null, 2)
115
+ )
116
+ return false;
117
+ if (
118
+ !value ||
119
+ typeof value !== "object" ||
120
+ Array.isArray(value) ||
121
+ value.schema_version !== 1
122
+ )
123
+ return false;
124
+ const digest = input =>
125
+ typeof input === "string" && /^[a-f0-9]{64}$/.test(input);
126
+ const id = input => Number.isSafeInteger(input) && input > 0;
127
+ const exact = fields =>
128
+ Object.keys(value).sort().join() === fields.sort().join();
129
+ if (name === "authorization")
130
+ return (
131
+ exact([
132
+ "schema_version",
133
+ "record_id",
134
+ "revision",
135
+ "repository",
136
+ "asset_id",
137
+ "sha256",
138
+ ]) &&
139
+ /^completion-[a-f0-9]{64}$/.test(value.record_id) &&
140
+ id(value.revision) &&
141
+ id(value.asset_id) &&
142
+ /^[\w.-]+\/[\w.-]+$/.test(value.repository) &&
143
+ digest(value.sha256)
144
+ );
145
+ return (
146
+ exact([
147
+ "schema_version",
148
+ "provider",
149
+ "ref",
150
+ "native_id",
151
+ "record_sha256",
152
+ "completed_at",
153
+ "state",
154
+ ]) &&
155
+ ["github", "linear"].includes(value.provider) &&
156
+ (value.provider === "github"
157
+ ? /^[\w.-]+\/[\w.-]+#[1-9]\d*$/.test(value.ref) &&
158
+ value.state === "CLOSED"
159
+ : /^[A-Z][A-Z0-9_]*-[1-9]\d*$/.test(value.ref) &&
160
+ value.state === "completed") &&
161
+ (id(value.native_id) ||
162
+ (typeof value.native_id === "string" &&
163
+ /^[\w:/#.-]{1,256}$/.test(value.native_id))) &&
164
+ digest(value.record_sha256) &&
165
+ typeof value.completed_at === "string" &&
166
+ value.completed_at.endsWith("Z") &&
167
+ Number.isFinite(Date.parse(value.completed_at))
168
+ );
169
+ }
@@ -0,0 +1,238 @@
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
+ /** Publish accepted/pending/revoked proof without performing a terminal transition. */
5
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
6
+ import { tmpdir } from "node:os";
7
+ import { join } from "node:path";
8
+ import {
9
+ CompletionProofError,
10
+ PROOF_KIND,
11
+ PROOF_MARKER,
12
+ MAX_RECORD_BYTES,
13
+ sha256,
14
+ primarySpecProjection,
15
+ requireProof,
16
+ validateRecordShape,
17
+ parseLocator,
18
+ isId,
19
+ } from "./completion-proof-shape.mjs";
20
+ import { githubProofTransport } from "./completion-proof-github.mjs";
21
+ import { completionRecordId } from "./completion-proof-reader.mjs";
22
+ import { evaluateAcceptedRecord } from "./completion-authorization.mjs";
23
+
24
+ const READ_ERROR = "read-error";
25
+
26
+ /** Recover the monotonic native index without treating old malformed proof as valid. */
27
+ function previousLocator(
28
+ context,
29
+ comments,
30
+ transport,
31
+ repositoryId,
32
+ recordId,
33
+ trackerActor
34
+ ) {
35
+ const candidates = [];
36
+ for (const comment of comments) {
37
+ if (!comment.body?.startsWith(`${PROOF_MARKER}\n`)) continue;
38
+ const actor = comment.user ?? comment.author;
39
+ if (
40
+ context.provider === "github"
41
+ ? !transport.eligibleCommentAuthor(actor, repositoryId)
42
+ : String(actor?.id) !== trackerActor
43
+ )
44
+ continue;
45
+ let locator;
46
+ try {
47
+ locator = parseLocator(comment.body);
48
+ } catch {
49
+ continue;
50
+ }
51
+ if (
52
+ locator.repository === context.repository &&
53
+ locator.record_id === recordId
54
+ )
55
+ candidates.push(locator);
56
+ }
57
+ candidates.sort((left, right) => right.revision - left.revision);
58
+ return candidates[0] ?? null;
59
+ }
60
+
61
+ /** A candidate bot id is only a hint until native upload metadata confirms it. */
62
+ function publisherIdentity(context, input, transport, repository) {
63
+ try {
64
+ const actor = JSON.parse(transport.call(["api", "user"]));
65
+ requireProof(
66
+ isId(actor.id) && actor.type === "User",
67
+ "The native publisher identity is unsupported."
68
+ );
69
+ return actor.id;
70
+ } catch (error) {
71
+ const id = input.producer?.github_actor_id;
72
+ const configured = (context.policy.automationPublishers ?? []).some(
73
+ row =>
74
+ row.user_id === id &&
75
+ row.repository_id === repository.id &&
76
+ row.type === "Bot"
77
+ );
78
+ if (!configured) {
79
+ const refusal = new CompletionProofError(
80
+ READ_ERROR,
81
+ "The publisher identity is unreadable and no explicit repository automation identity was configured."
82
+ );
83
+ refusal.cause = error;
84
+ throw refusal;
85
+ }
86
+ return id;
87
+ }
88
+ }
89
+
90
+ /** Strict immutable publication, followed by exact native readback and locator. */
91
+ export function publishCompletionProof(context, input) {
92
+ const transport = githubProofTransport(
93
+ context.run,
94
+ context.repository,
95
+ context.policy
96
+ );
97
+ const repository = transport.json(transport.root);
98
+ const subject = {
99
+ ...context.readSubject(),
100
+ comments: context.readComments(),
101
+ };
102
+ const publisher = publisherIdentity(context, input, transport, repository);
103
+ const trackerActor = context.trackerActor(publisher);
104
+ requireProof(
105
+ trackerActor && trackerActor !== "undefined",
106
+ "The native tracker publisher identity could not be established."
107
+ );
108
+ const identity = {
109
+ provider: context.provider,
110
+ ref: context.ref,
111
+ scope: context.scope,
112
+ native_id: subject.native_id,
113
+ };
114
+ const recordId = completionRecordId(identity);
115
+ const previous = previousLocator(
116
+ context,
117
+ subject.comments,
118
+ transport,
119
+ repository.id,
120
+ recordId,
121
+ trackerActor
122
+ );
123
+ const record = {
124
+ ...input,
125
+ schema_version: 1,
126
+ kind: PROOF_KIND,
127
+ record_id: recordId,
128
+ revision: previous ? previous.revision + 1 : 1,
129
+ supersedes: previous?.sha256 ?? null,
130
+ captured_at: new Date().toISOString(),
131
+ subject: identity,
132
+ producer: {
133
+ ...input.producer,
134
+ github_actor_id: publisher,
135
+ tracker_actor_id: trackerActor,
136
+ },
137
+ repository: { name: context.repository, id: repository.id },
138
+ environment: context.environment,
139
+ branch: context.branch,
140
+ spec_sha256: sha256(primarySpecProjection(subject.body)),
141
+ };
142
+ validateRecordShape(record);
143
+ if (record.state === "accepted-for-completion")
144
+ evaluateAcceptedRecord({ record }, context, subject, transport);
145
+ const bytes = Buffer.from(`${JSON.stringify(record, null, 2)}\n`);
146
+ requireProof(
147
+ bytes.length <= MAX_RECORD_BYTES,
148
+ "The completion record exceeds its bounded publication size."
149
+ );
150
+ const digest = sha256(bytes);
151
+ const name = `${recordId}-r${record.revision}-${digest}.json`;
152
+ const temporary = mkdtempSync(join(tmpdir(), "lisa-completion-proof-"));
153
+ try {
154
+ mkdirSync(temporary, { recursive: true });
155
+ const file = join(temporary, name);
156
+ writeFileSync(file, bytes);
157
+ const uploaded = context.run(
158
+ "gh",
159
+ ["release", "upload", "pr-assets", file, "--repo", context.repository],
160
+ { allowFailure: true }
161
+ );
162
+ requireProof(
163
+ uploaded.status === 0 && !uploaded.error,
164
+ "Immutable completion-proof upload failed; no acceptance locator was published.",
165
+ "write-error"
166
+ );
167
+ const release = transport.json(`${transport.root}/releases/tags/pr-assets`);
168
+ const pages = transport.json(
169
+ `${transport.root}/releases/${release.id}/assets?per_page=100`,
170
+ true
171
+ );
172
+ requireProof(
173
+ pages.every(Array.isArray),
174
+ "The native completion-asset listing is incomplete.",
175
+ READ_ERROR
176
+ );
177
+ const matching = pages.flat().filter(asset => asset.name === name);
178
+ requireProof(
179
+ matching.length === 1,
180
+ "The uploaded completion asset cannot be uniquely read back.",
181
+ READ_ERROR
182
+ );
183
+ const stored = transport.asset(matching[0].id, MAX_RECORD_BYTES);
184
+ requireProof(
185
+ stored.bytes.equals(bytes) &&
186
+ sha256(stored.bytes) === digest &&
187
+ stored.metadata.uploader?.id === publisher,
188
+ "The native uploaded acceptance bytes or publisher differ from the producer's record."
189
+ );
190
+ const publisherState = transport.trustedPublisher(stored.metadata, record, {
191
+ publishing: true,
192
+ });
193
+ // Recheck native scope after upload; do not publish acceptance for changed requirements.
194
+ const fresh = {
195
+ ...context.readSubject(),
196
+ comments: context.readComments(),
197
+ };
198
+ requireProof(
199
+ sha256(primarySpecProjection(fresh.body)) === record.spec_sha256,
200
+ "Requirements changed during proof publication; no locator was written.",
201
+ "pending-proof"
202
+ );
203
+ if (record.state === "accepted-for-completion")
204
+ evaluateAcceptedRecord({ record }, context, fresh, transport);
205
+ const locator = {
206
+ schema_version: 1,
207
+ record_id: recordId,
208
+ revision: record.revision,
209
+ repository: context.repository,
210
+ asset_id: stored.metadata.id,
211
+ sha256: digest,
212
+ };
213
+ const body = `${PROOF_MARKER}\n${JSON.stringify(locator)}`;
214
+ const comment = context.postLocator(body, subject.native_id);
215
+ const readback = context
216
+ .readComments()
217
+ .find(row => String(row.id) === String(comment.id));
218
+ requireProof(
219
+ readback?.body === body &&
220
+ String((readback.user ?? readback.author)?.id) === trackerActor,
221
+ "The native completion locator or publisher did not read back exactly.",
222
+ "write-error"
223
+ );
224
+ const deferredProducer = !publisherState.ready;
225
+ return {
226
+ category: deferredProducer ? "published/pending-producer" : "published",
227
+ record_id: recordId,
228
+ revision: record.revision,
229
+ asset_id: stored.metadata.id,
230
+ sha256: digest,
231
+ comment_id: comment.id,
232
+ ref: context.ref,
233
+ terminal_mutation: false,
234
+ };
235
+ } finally {
236
+ rmSync(temporary, { recursive: true, force: true });
237
+ }
238
+ }
@@ -0,0 +1,175 @@
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
+ /** Discover immutable proof through native, attributable locator revisions. */
5
+ import {
6
+ CompletionProofError,
7
+ PROOF_MARKER,
8
+ MAX_RECORD_BYTES,
9
+ sha256,
10
+ parseLocator,
11
+ requireProof,
12
+ validateRecordShape,
13
+ isObject,
14
+ } from "./completion-proof-shape.mjs";
15
+
16
+ /** Stable identity cannot be reset to replay a revoked item's older record. */
17
+ export function completionRecordId(subject) {
18
+ return `completion-${sha256([subject.provider, subject.scope, subject.ref].join("\0"))}`;
19
+ }
20
+
21
+ /** Native timestamps and identifiers, not caller-supplied capture times. */
22
+ function order(comment) {
23
+ const time = Date.parse(
24
+ comment.updated_at ??
25
+ comment.updatedAt ??
26
+ comment.created_at ??
27
+ comment.createdAt
28
+ );
29
+ requireProof(
30
+ Number.isFinite(time),
31
+ "A native completion locator has no usable revision timestamp."
32
+ );
33
+ return time;
34
+ }
35
+
36
+ /** A marker alone, an anonymous comment or an issue edit is never authority. */
37
+ export function readTrustedRecord(context, subject, transport) {
38
+ const comments = context
39
+ .readComments()
40
+ .filter(row => row.body?.startsWith(`${PROOF_MARKER}\n`));
41
+ const repository = transport.json(transport.root);
42
+ const observations = [];
43
+ const trustedLinearActors = new Set();
44
+ for (const comment of comments) {
45
+ const actor = comment.user ?? comment.author;
46
+ const trustedNativeActor =
47
+ context.provider === "github"
48
+ ? transport.eligibleCommentAuthor(actor, repository.id)
49
+ : false;
50
+ if (context.provider === "github" && !trustedNativeActor) continue;
51
+ let observation;
52
+ let locator;
53
+ try {
54
+ locator = parseLocator(comment.body);
55
+ requireProof(
56
+ locator.repository === context.repository,
57
+ "The proof locator selected another implementation repository."
58
+ );
59
+ const asset = transport.asset(locator.asset_id, MAX_RECORD_BYTES);
60
+ requireProof(
61
+ sha256(asset.bytes) === locator.sha256,
62
+ "The accepted record's actual bytes no longer match its locator digest."
63
+ );
64
+ let record;
65
+ try {
66
+ record = JSON.parse(asset.bytes.toString("utf8"));
67
+ } catch {
68
+ throw new CompletionProofError(
69
+ "invalid-proof",
70
+ "The completion record attachment is not readable JSON."
71
+ );
72
+ }
73
+ requireProof(
74
+ isObject(record) &&
75
+ isObject(record.producer) &&
76
+ record.producer.github_actor_id === asset.metadata.uploader?.id,
77
+ "The completion record publisher differs from the asset's native uploader."
78
+ );
79
+ transport.trustedPublisher(
80
+ asset.metadata,
81
+ { ...record, repository },
82
+ { identityOnly: true }
83
+ );
84
+ if (context.provider === "linear") {
85
+ if (String(record.producer.tracker_actor_id) !== String(actor?.id))
86
+ continue;
87
+ trustedLinearActors.add(String(actor.id));
88
+ } else
89
+ requireProof(
90
+ record.producer.github_actor_id === actor.id,
91
+ "The managed locator was not published by the record's native publisher."
92
+ );
93
+ validateRecordShape(record);
94
+ transport.trustedPublisher(asset.metadata, record);
95
+ requireProof(
96
+ record.subject.provider === context.provider &&
97
+ record.subject.ref === context.ref &&
98
+ String(record.subject.native_id) === String(subject.native_id) &&
99
+ record.subject.scope === context.scope &&
100
+ record.record_id === completionRecordId(record.subject) &&
101
+ record.repository.id === repository.id &&
102
+ record.repository.name === context.repository &&
103
+ record.revision === locator.revision &&
104
+ record.record_id === locator.record_id,
105
+ "The accepted proof belongs to another work item, native scope, repository or revision."
106
+ );
107
+ observation = { comment, locator, record, metadata: asset.metadata };
108
+ } catch (error) {
109
+ observation = { comment, locator, error };
110
+ }
111
+ observations.push(observation);
112
+ }
113
+ const trusted = observations.filter(
114
+ row =>
115
+ context.provider === "github" ||
116
+ trustedLinearActors.has(
117
+ String((row.comment.user ?? row.comment.author)?.id)
118
+ )
119
+ );
120
+ trusted.sort(
121
+ (left, right) =>
122
+ order(right.comment) - order(left.comment) ||
123
+ (right.locator?.revision ?? 0) - (left.locator?.revision ?? 0) ||
124
+ Number(right.comment.id || 0) - Number(left.comment.id || 0)
125
+ );
126
+ if (trusted.length === 0) return null;
127
+ const chosen = trusted[0];
128
+ if (chosen.error) throw chosen.error;
129
+ const older = trusted.slice(1).filter(row => row.locator);
130
+ requireProof(
131
+ !older.some(
132
+ row =>
133
+ row.locator.revision > chosen.record.revision ||
134
+ (row.locator.revision === chosen.record.revision &&
135
+ row.locator.sha256 !== chosen.locator.sha256)
136
+ ),
137
+ "The latest trusted locator attempts to roll back or conflict with a newer proof revision."
138
+ );
139
+ if (chosen.record.revision > 1)
140
+ requireProof(
141
+ older.some(
142
+ row =>
143
+ row.locator.revision === chosen.record.revision - 1 &&
144
+ row.locator.sha256 === chosen.record.supersedes
145
+ ),
146
+ "The completion proof revision does not extend its trusted immutable predecessor."
147
+ );
148
+ return { ...chosen, history: locatorHistoryFingerprint(comments) };
149
+ }
150
+
151
+ /** Detect a concurrent locator edit locally after the final authoritative read. */
152
+ export function locatorHistoryFingerprint(comments) {
153
+ const markers = comments
154
+ .filter(row => row.body?.startsWith(`${PROOF_MARKER}\n`))
155
+ .map(row => ({
156
+ id: String(row.id),
157
+ body: row.body,
158
+ actor: (row.user ?? row.author)?.id,
159
+ updated:
160
+ row.updated_at ?? row.updatedAt ?? row.created_at ?? row.createdAt,
161
+ }))
162
+ .sort((left, right) => left.id.localeCompare(right.id));
163
+ return sha256(JSON.stringify(markers));
164
+ }
165
+
166
+ /** A native pointer revision is re-read independently before the first write. */
167
+ export function recordFingerprint(chosen) {
168
+ return JSON.stringify({
169
+ id: chosen.comment.id,
170
+ updated: order(chosen.comment),
171
+ asset: chosen.locator.asset_id,
172
+ sha256: chosen.locator.sha256,
173
+ revision: chosen.record.revision,
174
+ });
175
+ }