@codyswann/lisa 4.70.4 → 4.71.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 (139) hide show
  1. package/all/copy-overwrite/scripts/lib/automation-provenance-contract.mjs +174 -0
  2. package/all/copy-overwrite/scripts/lib/automation-provenance-local.mjs +279 -0
  3. package/all/copy-overwrite/scripts/lib/gate-failure-diagnosis.mjs +17 -7
  4. package/all/copy-overwrite/scripts/lib/github-attestation-provider.mjs +241 -0
  5. package/all/copy-overwrite/scripts/lib/github-attestation-recovery.mjs +283 -0
  6. package/all/copy-overwrite/scripts/lib/github-attestation-verifier.mjs +244 -0
  7. package/all/copy-overwrite/scripts/lib/npm-update-allocate.mjs +289 -0
  8. package/all/copy-overwrite/scripts/lib/npm-update-authorization.mjs +235 -0
  9. package/all/copy-overwrite/scripts/lib/npm-update-broker-client.mjs +92 -0
  10. package/all/copy-overwrite/scripts/lib/npm-update-checkpoint.mjs +241 -0
  11. package/all/copy-overwrite/scripts/lib/npm-update-contract.mjs +278 -0
  12. package/all/copy-overwrite/scripts/lib/npm-update-controller-broker.mjs +234 -0
  13. package/all/copy-overwrite/scripts/lib/npm-update-controller-factory.mjs +278 -0
  14. package/all/copy-overwrite/scripts/lib/npm-update-controller-recipe.mjs +135 -0
  15. package/all/copy-overwrite/scripts/lib/npm-update-execution-adapter.mjs +292 -0
  16. package/all/copy-overwrite/scripts/lib/npm-update-gate-hooks.mjs +214 -0
  17. package/all/copy-overwrite/scripts/lib/npm-update-gate-install.mjs +267 -0
  18. package/all/copy-overwrite/scripts/lib/npm-update-gate-proof.mjs +99 -0
  19. package/all/copy-overwrite/scripts/lib/npm-update-gate.mjs +300 -0
  20. package/all/copy-overwrite/scripts/lib/npm-update-gh-dispatch.mjs +182 -0
  21. package/all/copy-overwrite/scripts/lib/npm-update-gh-grants.mjs +156 -0
  22. package/all/copy-overwrite/scripts/lib/npm-update-gh-requests.mjs +221 -0
  23. package/all/copy-overwrite/scripts/lib/npm-update-github.mjs +214 -0
  24. package/all/copy-overwrite/scripts/lib/npm-update-helper-graph.mjs +238 -0
  25. package/all/copy-overwrite/scripts/lib/npm-update-helper-inventory.mjs +126 -0
  26. package/all/copy-overwrite/scripts/lib/npm-update-helper.mjs +292 -0
  27. package/all/copy-overwrite/scripts/lib/npm-update-hook-installation.mjs +116 -0
  28. package/all/copy-overwrite/scripts/lib/npm-update-hook-preload.mjs +153 -0
  29. package/all/copy-overwrite/scripts/lib/npm-update-hook-provider.mjs +207 -0
  30. package/all/copy-overwrite/scripts/lib/npm-update-hook-read-client.mjs +164 -0
  31. package/all/copy-overwrite/scripts/lib/npm-update-hosted-gate.mjs +272 -0
  32. package/all/copy-overwrite/scripts/lib/npm-update-hosted-hook.mjs +157 -0
  33. package/all/copy-overwrite/scripts/lib/npm-update-invariants.mjs +28 -0
  34. package/all/copy-overwrite/scripts/lib/npm-update-isolation.mjs +11 -0
  35. package/all/copy-overwrite/scripts/lib/npm-update-leaf-contract.mjs +171 -0
  36. package/all/copy-overwrite/scripts/lib/npm-update-leaf.mjs +194 -0
  37. package/all/copy-overwrite/scripts/lib/npm-update-native-process.mjs +155 -0
  38. package/all/copy-overwrite/scripts/lib/npm-update-npm.mjs +190 -0
  39. package/all/copy-overwrite/scripts/lib/npm-update-object.mjs +112 -0
  40. package/all/copy-overwrite/scripts/lib/npm-update-orchestrator.mjs +273 -0
  41. package/all/copy-overwrite/scripts/lib/npm-update-owner.mjs +292 -0
  42. package/all/copy-overwrite/scripts/lib/npm-update-prepare.mjs +263 -0
  43. package/all/copy-overwrite/scripts/lib/npm-update-process-core.mjs +179 -0
  44. package/all/copy-overwrite/scripts/lib/npm-update-process.mjs +16 -0
  45. package/all/copy-overwrite/scripts/lib/npm-update-publication.mjs +258 -0
  46. package/all/copy-overwrite/scripts/lib/npm-update-publish.mjs +258 -0
  47. package/all/copy-overwrite/scripts/lib/npm-update-quality.mjs +153 -0
  48. package/all/copy-overwrite/scripts/lib/npm-update-recovery.mjs +264 -0
  49. package/all/copy-overwrite/scripts/lib/npm-update-runtime-archive.mjs +204 -0
  50. package/all/copy-overwrite/scripts/lib/npm-update-runtime-graph.mjs +185 -0
  51. package/all/copy-overwrite/scripts/lib/npm-update-runtime-transport.mjs +221 -0
  52. package/all/copy-overwrite/scripts/lib/npm-update-runtime.mjs +284 -0
  53. package/all/copy-overwrite/scripts/lib/npm-update-tool-launcher.mjs +287 -0
  54. package/all/copy-overwrite/scripts/lib/npm-update-worker-environment.mjs +161 -0
  55. package/all/copy-overwrite/scripts/lib/npm-update-worker-inspection.mjs +142 -0
  56. package/all/copy-overwrite/scripts/lib/npm-update-worker-lifecycle.mjs +256 -0
  57. package/all/copy-overwrite/scripts/lib/npm-update-worker-policy.mjs +266 -0
  58. package/all/copy-overwrite/scripts/lib/process-tree-runner.mjs +230 -36
  59. package/all/copy-overwrite/scripts/lisa-automation-provenance.mjs +161 -0
  60. package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +89 -5
  61. package/all/copy-overwrite/scripts/lisa-npm-updater.mjs +204 -0
  62. package/all/copy-overwrite/scripts/lisa-run-gates.mjs +48 -123
  63. package/all/copy-overwrite/scripts/lisa-work-item.mjs +107 -41
  64. package/all/copy-overwrite/scripts/npm-updater-gate-runtime.json +1 -0
  65. package/all/copy-overwrite/scripts/npm-updater-gate-supervisor.c +121 -0
  66. package/all/copy-overwrite/scripts/npm-updater-gate.Dockerfile +49 -0
  67. package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +1085 -0
  68. package/dist/codex/scripts/block-no-verify.sh +89 -5
  69. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  70. package/dist/core/lisa-owned-hash-ledger.js +598 -0
  71. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  72. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  73. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  74. package/dist/core/upstream-evidence-manifest.js +157 -11
  75. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  76. package/dist/opencode/plugin-templates/block-no-verify.sh +89 -5
  77. package/package.json +4 -4
  78. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  80. package/plugins/lisa/hooks/block-no-verify.agy.sh +89 -5
  81. package/plugins/lisa/hooks/block-no-verify.sh +89 -5
  82. package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +89 -5
  83. package/plugins/lisa-agy/plugin.json +1 -1
  84. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  85. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  86. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  87. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  89. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  90. package/plugins/lisa-copilot/hooks/block-no-verify.sh +89 -5
  91. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-cursor/hooks/block-no-verify.sh +89 -5
  93. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  94. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  95. package/plugins/lisa-expo-agy/plugin.json +1 -1
  96. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  97. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  98. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  99. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  100. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  101. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  102. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  103. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  104. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  105. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  106. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  108. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  109. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  110. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  111. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  112. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  113. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  114. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  115. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  116. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  117. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  118. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  119. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  120. package/plugins/lisa-rails-agy/plugin.json +1 -1
  121. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  122. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  123. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  124. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  125. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  126. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  127. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  128. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  129. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  130. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  131. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  132. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  133. package/plugins/src/base/hooks/block-no-verify.agy.sh +89 -5
  134. package/plugins/src/base/hooks/block-no-verify.sh +89 -5
  135. package/scripts/check-shipped-surface-removals.mjs +96 -24
  136. package/scripts/generate-npm-updater-helper-graph.mjs +184 -0
  137. package/scripts/two-channel-couplings.json +31 -7
  138. package/typescript/copy-contents/.husky/commit-msg +18 -0
  139. package/typescript/copy-overwrite/.prettierignore +2 -0
@@ -0,0 +1,241 @@
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
+ /** Fixed bounded provider reads distinguish historical origin from current authority. */
5
+ import { boundedSpawnSync } from "./bounded-spawn.mjs";
6
+ import {
7
+ ghJson,
8
+ requireProof,
9
+ sha256,
10
+ assertPinnedVerifier,
11
+ } from "./github-attestation-verifier.mjs";
12
+
13
+ const API = "api";
14
+ const HOST_FLAG = "--hostname";
15
+ const PUBLIC_HOST = "github.com";
16
+
17
+ /** Provider identity never treats unavailable state as an absent scope. */
18
+ function repositoryState(read, policy, parent) {
19
+ const repo = read(`repos/${policy.repository}`);
20
+ requireProof(
21
+ String(repo.id) === policy.repositoryId &&
22
+ String(repo.owner?.id) === policy.ownerId,
23
+ "provider repository identity differs"
24
+ );
25
+ requireProof(
26
+ repo.full_name === policy.repository && repo.default_branch === "main",
27
+ "provider main scope differs"
28
+ );
29
+ const main = read(`repos/${policy.repository}/git/ref/heads/main`);
30
+ requireProof(
31
+ main.ref === "refs/heads/main" && main.object?.sha === parent,
32
+ "provider main base differs"
33
+ );
34
+ }
35
+
36
+ /** Private role selection is fixed by separate exported ordinary/historical entry points. */
37
+ function runState(read, policy, descriptor, historical) {
38
+ const run = read(
39
+ `repos/${policy.repository}/actions/runs/${descriptor.runId}/attempts/${descriptor.runAttempt}`
40
+ );
41
+ requireProof(
42
+ String(run.id) === descriptor.runId &&
43
+ String(run.run_attempt) === descriptor.runAttempt,
44
+ "provider run differs"
45
+ );
46
+ requireProof(
47
+ run.head_sha === descriptor.parent &&
48
+ run.head_branch === "main" &&
49
+ (historical
50
+ ? ["in_progress", "completed"].includes(run.status)
51
+ : run.status === "in_progress"),
52
+ "stale/cancelled provider run"
53
+ );
54
+ requireProof(
55
+ String(run.head_repository?.id) === policy.repositoryId &&
56
+ policy.allowedTriggers.includes(run.event),
57
+ "fork/unapproved provider run"
58
+ );
59
+ requireProof(
60
+ Array.isArray(run.referenced_workflows) &&
61
+ run.referenced_workflows.some(
62
+ workflow =>
63
+ workflow.path?.split("@")[0] === policy.signerWorkflow &&
64
+ workflow.sha === policy.signerDigest
65
+ ),
66
+ "actual reusable workflow differs"
67
+ );
68
+ return run;
69
+ }
70
+
71
+ /** A current exact Bot claim remains required even when origin is historical. */
72
+ function claimState(read, policy, descriptor) {
73
+ const comment = read(
74
+ `repos/${descriptor.queue}/issues/comments/${descriptor.claimCommentId}`
75
+ );
76
+ const issueNumber = descriptor.workItem.slice(
77
+ descriptor.workItem.lastIndexOf("#") + 1
78
+ );
79
+ requireProof(
80
+ String(comment.id) === descriptor.claimCommentId &&
81
+ comment.issue_url ===
82
+ `https://api.github.com/repos/${descriptor.queue}/issues/${issueNumber}`,
83
+ "claim scope differs"
84
+ );
85
+ requireProof(
86
+ String(comment.user?.id) === policy.claimActorId &&
87
+ comment.user?.type === "Bot",
88
+ "claim actor differs"
89
+ );
90
+ requireProof(
91
+ typeof comment.body === "string" &&
92
+ sha256(comment.body) === descriptor.claimSha256,
93
+ "claim announcement changed"
94
+ );
95
+ return comment;
96
+ }
97
+
98
+ /** Every role shares bounded pinned reads and immutable repository identity. */
99
+ function providerOrigin(policy, descriptor, historical, execute) {
100
+ const read = endpoint =>
101
+ ghJson(policy, [API, HOST_FLAG, PUBLIC_HOST, endpoint], execute);
102
+ repositoryState(read, policy, descriptor.parent);
103
+ return {
104
+ run: runState(read, policy, descriptor, historical),
105
+ claim: claimState(read, policy, descriptor),
106
+ };
107
+ }
108
+
109
+ /** Prove the trusted human remains assignable using the actual documented status. */
110
+ export function verifyAssignable(
111
+ policy,
112
+ maintainer,
113
+ execute = boundedSpawnSync
114
+ ) {
115
+ requireProof(
116
+ typeof maintainer === "string" &&
117
+ /^[A-Za-z0-9][A-Za-z0-9-]{0,38}$/.test(maintainer),
118
+ "invalid trusted maintainer"
119
+ );
120
+ assertPinnedVerifier(policy);
121
+ const result = execute(
122
+ policy.ghExecutable,
123
+ [
124
+ API,
125
+ HOST_FLAG,
126
+ PUBLIC_HOST,
127
+ "--include",
128
+ `repos/${policy.repository}/assignees/${maintainer}`,
129
+ ],
130
+ {
131
+ encoding: "utf8",
132
+ timeout: 30_000,
133
+ maxBuffer: 65_536,
134
+ env: { ...process.env, GH_HOST: PUBLIC_HOST },
135
+ }
136
+ );
137
+ requireProof(
138
+ !result.error &&
139
+ !result.signal &&
140
+ result.status === 0 &&
141
+ typeof result.stdout === "string" &&
142
+ Buffer.byteLength(result.stdout) <= 65_536 &&
143
+ /^HTTP\/\S+ 204\b/.test(result.stdout),
144
+ "trusted maintainer is not assignable"
145
+ );
146
+ }
147
+
148
+ /** Current v1 authorization always requires its own live run. */
149
+ export function verifyCurrentProvider(
150
+ policy,
151
+ descriptor,
152
+ execute = boundedSpawnSync
153
+ ) {
154
+ return providerOrigin(policy, descriptor, false, execute);
155
+ }
156
+
157
+ /** Historical origin returns qualified provider chronology, never current authority. */
158
+ export function verifyHistoricalProvider(
159
+ policy,
160
+ descriptor,
161
+ execute = boundedSpawnSync
162
+ ) {
163
+ return providerOrigin(policy, descriptor, true, execute);
164
+ }
165
+
166
+ /** Fresh permission checks its own invocation and the exact observed destination. */
167
+ export function verifyRecoveryProvider(
168
+ policy,
169
+ descriptor,
170
+ recovery,
171
+ maintainer,
172
+ execute = boundedSpawnSync
173
+ ) {
174
+ verifyCurrentProvider(
175
+ policy,
176
+ { ...descriptor, runId: recovery.runId, runAttempt: recovery.runAttempt },
177
+ execute
178
+ );
179
+ const read = endpoint =>
180
+ ghJson(policy, [API, HOST_FLAG, PUBLIC_HOST, endpoint], execute);
181
+ const number = descriptor.workItem.slice(
182
+ descriptor.workItem.lastIndexOf("#") + 1
183
+ );
184
+ const issue = read(`repos/${policy.repository}/issues/${number}`);
185
+ requireProof(
186
+ issue.state === "open" &&
187
+ !issue.pull_request &&
188
+ sha256(issue.body) === recovery.leafBodySha256 &&
189
+ Array.isArray(issue.assignees) &&
190
+ issue.assignees.length === 1 &&
191
+ issue.assignees[0].login === maintainer &&
192
+ issue.assignees[0].type === "User",
193
+ "current recovery leaf or owner differs"
194
+ );
195
+ const refs = read(
196
+ `repos/${policy.repository}/git/matching-refs/heads/${recovery.branch}`
197
+ );
198
+ requireProof(
199
+ Array.isArray(refs) && refs.length <= 1,
200
+ "ambiguous recovery branch"
201
+ );
202
+ const head = refs.length ? refs[0].object?.sha : null;
203
+ requireProof(
204
+ !refs.length || refs[0].ref === `refs/heads/${recovery.branch}`,
205
+ "foreign recovery ref"
206
+ );
207
+ requireProof(
208
+ head === recovery.expectedBranchHead,
209
+ "recovery destination changed"
210
+ );
211
+ const pulls = [];
212
+ for (let page = 1; page <= 20; page++) {
213
+ const values = read(
214
+ `repos/${policy.repository}/pulls?state=all&head=${encodeURIComponent(`${policy.repository.split("/")[0]}:${recovery.branch}`)}&per_page=100&page=${page}`
215
+ );
216
+ requireProof(
217
+ Array.isArray(values) && values.length <= 100,
218
+ "invalid recovery PR page"
219
+ );
220
+ pulls.push(...values);
221
+ if (values.length < 100) break;
222
+ requireProof(page < 20, "incomplete recovery PR pages");
223
+ }
224
+ requireProof(pulls.length <= 1, "ambiguous recovery PR");
225
+ if (!pulls.length)
226
+ requireProof(
227
+ recovery.prNumber === null && recovery.expectedPrHead === null,
228
+ "missing expected recovery PR"
229
+ );
230
+ else
231
+ requireProof(
232
+ pulls[0].number === recovery.prNumber &&
233
+ pulls[0].state === "open" &&
234
+ pulls[0].head?.sha === recovery.expectedPrHead &&
235
+ pulls[0].head?.ref === recovery.branch &&
236
+ pulls[0].head?.repo?.full_name === policy.repository &&
237
+ pulls[0].base?.ref === "main" &&
238
+ pulls[0].base?.repo?.full_name === policy.repository,
239
+ "foreign/closed/changed recovery PR"
240
+ );
241
+ }
@@ -0,0 +1,283 @@
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
+ /** Fixed historical-origin and fresh-recovery roles preserve ordinary v1 authority. */
5
+ import {
6
+ verifyHistoricalProvider,
7
+ verifyRecoveryProvider,
8
+ verifyAssignable,
9
+ } from "./github-attestation-provider.mjs";
10
+ import { boundedSpawnSync } from "./bounded-spawn.mjs";
11
+ import {
12
+ requireProof,
13
+ verifiedRole,
14
+ matchSigningTime,
15
+ officialResult,
16
+ PROPOSAL_PREDICATE,
17
+ RECOVERY_PREDICATE,
18
+ } from "./github-attestation-verifier.mjs";
19
+
20
+ /** Historical origin authenticates bytes only within witnessed provider chronology. */
21
+ export function assertHistoricalAttestation(
22
+ results,
23
+ descriptor,
24
+ digest,
25
+ policy,
26
+ run,
27
+ claim,
28
+ now = Date.now()
29
+ ) {
30
+ const result = verifiedRole(
31
+ results,
32
+ descriptor,
33
+ digest,
34
+ policy,
35
+ PROPOSAL_PREDICATE,
36
+ "descriptor.json"
37
+ );
38
+ const start = Date.parse(run.run_started_at),
39
+ updated = Date.parse(run.updated_at),
40
+ claimed = Date.parse(claim.created_at);
41
+ requireProof(
42
+ [start, updated, claimed].every(Number.isFinite) &&
43
+ start <= updated &&
44
+ updated <= now + 60_000,
45
+ "missing/invalid historical provider chronology"
46
+ );
47
+ // Official signature/timestamp verification already authenticates these times.
48
+ matchSigningTime(
49
+ result.verifiedTimestamps,
50
+ { ...policy, maxAgeSeconds: Number.MAX_SAFE_INTEGER },
51
+ now
52
+ );
53
+ for (const time of result.verifiedTimestamps) {
54
+ const signed = Date.parse(time.timestamp);
55
+ requireProof(
56
+ signed >= start - 60_000 &&
57
+ signed >= claimed - 60_000 &&
58
+ signed <= updated + 60_000,
59
+ "origin signed outside provider chronology"
60
+ );
61
+ }
62
+ return result.signature.certificate;
63
+ }
64
+
65
+ const RECOVERY_KEYS = [
66
+ "version",
67
+ "purpose",
68
+ "repository",
69
+ "repositoryId",
70
+ "ownerId",
71
+ "queue",
72
+ "workItem",
73
+ "proposalKey",
74
+ "bindingKey",
75
+ "npmPolicySha256",
76
+ "automationPolicySha256",
77
+ "originDescriptorSha256",
78
+ "originRunId",
79
+ "originRunAttempt",
80
+ "commit",
81
+ "parent",
82
+ "tree",
83
+ "messageSha256",
84
+ "claimCommentId",
85
+ "claimSha256",
86
+ "leafBodySha256",
87
+ "branch",
88
+ "expectedBranchHead",
89
+ "prNumber",
90
+ "expectedPrHead",
91
+ "runId",
92
+ "runAttempt",
93
+ ];
94
+
95
+ /** Closed recovery fields bind fresh permission to one unchanged origin. */
96
+ export function validateRecovery(value, origin, digest, policy) {
97
+ requireProof(
98
+ value &&
99
+ typeof value === "object" &&
100
+ !Array.isArray(value) &&
101
+ Object.keys(value).sort().join("\n") ===
102
+ [...RECOVERY_KEYS].sort().join("\n"),
103
+ "recovery fields differ"
104
+ );
105
+ const expected = {
106
+ version: 1,
107
+ purpose: "resume-identical-npm-proposal",
108
+ repository: policy.repository,
109
+ repositoryId: policy.repositoryId,
110
+ ownerId: policy.ownerId,
111
+ queue: origin.queue,
112
+ workItem: origin.workItem,
113
+ bindingKey: origin.proposalKey,
114
+ automationPolicySha256: origin.policySha256,
115
+ originDescriptorSha256: digest,
116
+ originRunId: origin.runId,
117
+ originRunAttempt: origin.runAttempt,
118
+ parent: origin.parent,
119
+ tree: origin.tree,
120
+ messageSha256: origin.messageSha256,
121
+ claimCommentId: origin.claimCommentId,
122
+ claimSha256: origin.claimSha256,
123
+ };
124
+ for (const [key, expectedValue] of Object.entries(expected))
125
+ requireProof(value[key] === expectedValue, `recovery ${key} differs`);
126
+ for (const key of ["proposalKey", "npmPolicySha256", "leafBodySha256"])
127
+ requireProof(/^[a-f0-9]{64}$/.test(value[key]), "invalid recovery digest");
128
+ requireProof(
129
+ /^[a-f0-9]{40}$/.test(value.commit) &&
130
+ value.branch === `lisa/npm-${value.proposalKey}`,
131
+ "invalid recovery destination"
132
+ );
133
+ requireProof(
134
+ [value.runId, value.runAttempt].every(
135
+ item => typeof item === "string" && /^[1-9]\d*$/.test(item)
136
+ ),
137
+ "invalid recovery invocation"
138
+ );
139
+ requireProof(
140
+ value.runId !== origin.runId || value.runAttempt !== origin.runAttempt,
141
+ "recovery repeats origin invocation"
142
+ );
143
+ requireProof(
144
+ value.expectedBranchHead === null ||
145
+ value.expectedBranchHead === value.commit,
146
+ "foreign recovery branch"
147
+ );
148
+ requireProof(
149
+ (value.prNumber === null && value.expectedPrHead === null) ||
150
+ (Number.isSafeInteger(value.prNumber) &&
151
+ value.prNumber > 0 &&
152
+ value.expectedPrHead === value.commit &&
153
+ value.expectedBranchHead === value.commit),
154
+ "partial/foreign recovery PR"
155
+ );
156
+ return value;
157
+ }
158
+
159
+ /** Fresh recovery has its own signed subject and retains ordinary freshness. */
160
+ export function assertRecoveryAttestation(
161
+ results,
162
+ recovery,
163
+ digest,
164
+ policy,
165
+ now = Date.now()
166
+ ) {
167
+ const result = verifiedRole(
168
+ results,
169
+ recovery,
170
+ digest,
171
+ policy,
172
+ RECOVERY_PREDICATE,
173
+ "recovery.json"
174
+ );
175
+ matchSigningTime(result.verifiedTimestamps, policy, now);
176
+ return result.signature.certificate;
177
+ }
178
+
179
+ /** Historical verification cannot be selected by an ordinary-v1 caller flag. */
180
+ export function verifyHistoricalDescriptor(
181
+ policy,
182
+ descriptor,
183
+ paths,
184
+ digest,
185
+ run,
186
+ claim,
187
+ execute = boundedSpawnSync
188
+ ) {
189
+ return assertHistoricalAttestation(
190
+ officialResult(
191
+ policy,
192
+ descriptor,
193
+ paths.descriptor,
194
+ paths.bundle,
195
+ PROPOSAL_PREDICATE,
196
+ execute
197
+ ),
198
+ descriptor,
199
+ digest,
200
+ policy,
201
+ run,
202
+ claim
203
+ );
204
+ }
205
+
206
+ /** Recovery verification uses only its fixed separate private subject and bundle. */
207
+ export function verifyRecoveryAttestation(
208
+ policy,
209
+ recovery,
210
+ paths,
211
+ digest,
212
+ execute = boundedSpawnSync
213
+ ) {
214
+ return assertRecoveryAttestation(
215
+ officialResult(
216
+ policy,
217
+ recovery,
218
+ paths.recovery,
219
+ paths.recoveryBundle,
220
+ RECOVERY_PREDICATE,
221
+ execute
222
+ ),
223
+ recovery,
224
+ digest,
225
+ policy
226
+ );
227
+ }
228
+
229
+ // Compatibility export preserves callers of the fixed local Git construction.
230
+ export { predictedCommit } from "./automation-provenance-local.mjs";
231
+
232
+ /** Both proofs and current live authority are necessary; historical data alone grants nothing. */
233
+ export function verifyRecoveryOrigin(
234
+ policy,
235
+ descriptor,
236
+ recovery,
237
+ digest,
238
+ paths,
239
+ scope,
240
+ execute = boundedSpawnSync
241
+ ) {
242
+ validateRecovery(recovery, descriptor, digest, policy);
243
+ for (const key of [
244
+ "npmPolicySha256",
245
+ "proposalKey",
246
+ "leafBodySha256",
247
+ "commit",
248
+ ])
249
+ requireProof(
250
+ recovery[key] === scope[key],
251
+ `current recovery ${key} differs`
252
+ );
253
+ requireProof(
254
+ process.env.GITHUB_RUN_ID === recovery.runId &&
255
+ process.env.GITHUB_RUN_ATTEMPT === recovery.runAttempt,
256
+ "recovery replayed into different current invocation"
257
+ );
258
+ const origin = verifyHistoricalProvider(policy, descriptor, execute);
259
+ verifyHistoricalDescriptor(
260
+ policy,
261
+ descriptor,
262
+ paths,
263
+ digest,
264
+ origin.run,
265
+ origin.claim,
266
+ execute
267
+ );
268
+ verifyRecoveryAttestation(
269
+ policy,
270
+ recovery,
271
+ paths,
272
+ scope.recoverySha256,
273
+ execute
274
+ );
275
+ verifyRecoveryProvider(
276
+ policy,
277
+ descriptor,
278
+ recovery,
279
+ scope.maintainer,
280
+ execute
281
+ );
282
+ verifyAssignable(policy, scope.maintainer, execute);
283
+ }