@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,244 @@
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 official verification followed by exact authorization policy. */
5
+ import { createHash } from "node:crypto";
6
+ import { lstatSync, readFileSync } from "node:fs";
7
+ import { isAbsolute } from "node:path";
8
+ import { boundedSpawnSync } from "./bounded-spawn.mjs";
9
+
10
+ export const ACTIONS_ISSUER = "https://token.actions.githubusercontent.com";
11
+ export const PROPOSAL_PREDICATE =
12
+ "https://lisa.dev/attestations/npm-proposal/v1";
13
+ export const RECOVERY_PREDICATE =
14
+ "https://lisa.dev/attestations/npm-recovery/v1";
15
+
16
+ /** Hash exact bytes; never an estimated identifier. */
17
+ export function sha256(value) {
18
+ return createHash("sha256").update(value).digest("hex");
19
+ }
20
+
21
+ /** Require a predicate, hiding potentially sensitive provider output. */
22
+ export function requireProof(condition, reason) {
23
+ if (!condition) throw new Error(`Invalid automation provenance: ${reason}`);
24
+ }
25
+
26
+ /** Run only the trusted, hash-pinned official executable with bounded output. */
27
+ export function assertPinnedVerifier(policy) {
28
+ requireProof(
29
+ isAbsolute(policy.ghExecutable ?? ""),
30
+ "official gh executable is not pinned"
31
+ );
32
+ const stat = lstatSync(policy.ghExecutable);
33
+ requireProof(
34
+ stat.isFile() && stat.size <= 150_000_000,
35
+ "invalid official gh file"
36
+ );
37
+ requireProof(
38
+ sha256(readFileSync(policy.ghExecutable)) === policy.ghSha256,
39
+ "official gh bytes differ"
40
+ );
41
+ }
42
+
43
+ /** Run only the already pinned verifier/provider reader under fixed bounds. */
44
+ export function ghJson(policy, args, execute = boundedSpawnSync) {
45
+ assertPinnedVerifier(policy);
46
+ const result = execute(policy.ghExecutable, args, {
47
+ encoding: "utf8",
48
+ timeout: 30_000,
49
+ maxBuffer: 1_048_576,
50
+ env: { ...process.env, GH_HOST: "github.com" },
51
+ });
52
+ requireProof(
53
+ !result.error && !result.signal && result.status === 0,
54
+ "official verifier/provider request failed"
55
+ );
56
+ requireProof(
57
+ typeof result.stdout === "string" &&
58
+ Buffer.byteLength(result.stdout) <= 1_048_576,
59
+ "oversize verifier result"
60
+ );
61
+ return JSON.parse(result.stdout);
62
+ }
63
+
64
+ /** Strictly match fields from a successfully verified certificate. */
65
+ function matchCertificate(certificate, descriptor, policy) {
66
+ const expected = {
67
+ issuer: ACTIONS_ISSUER,
68
+ buildSignerURI: `https://github.com/${policy.signerWorkflow}@${policy.signerDigest}`,
69
+ buildSignerDigest: policy.signerDigest,
70
+ buildConfigURI: `https://github.com/${policy.callerWorkflow}@refs/heads/main`,
71
+ buildConfigDigest: descriptor.parent,
72
+ sourceRepositoryURI: `https://github.com/${policy.repository}`,
73
+ sourceRepositoryDigest: descriptor.parent,
74
+ sourceRepositoryRef: "refs/heads/main",
75
+ sourceRepositoryIdentifier: policy.repositoryId,
76
+ sourceRepositoryOwnerIdentifier: policy.ownerId,
77
+ runInvocationURI: `https://github.com/${policy.repository}/actions/runs/${descriptor.runId}/attempts/${descriptor.runAttempt}`,
78
+ runnerEnvironment: "github-hosted",
79
+ };
80
+ for (const [key, value] of Object.entries(expected)) {
81
+ requireProof(
82
+ typeof value === "string" && value !== "" && certificate?.[key] === value,
83
+ `certificate ${key} differs`
84
+ );
85
+ }
86
+ requireProof(
87
+ policy.allowedTriggers.includes(certificate.buildTrigger),
88
+ "unapproved workflow trigger"
89
+ );
90
+ }
91
+
92
+ /** Only authenticated observer times count; current-time-only is insufficient. */
93
+ export function matchSigningTime(timestamps, policy, now) {
94
+ requireProof(
95
+ Array.isArray(timestamps) &&
96
+ timestamps.length > 0 &&
97
+ timestamps.length <= 8,
98
+ "missing signing timestamp"
99
+ );
100
+ for (const time of timestamps) {
101
+ const milliseconds = Date.parse(time.timestamp);
102
+ requireProof(
103
+ ["Tlog", "TimestampAuthority"].includes(time.type),
104
+ "unsigned/current signing time"
105
+ );
106
+ requireProof(
107
+ typeof time.uri === "string" && time.uri.startsWith("https://"),
108
+ "missing timestamp authority"
109
+ );
110
+ requireProof(
111
+ Number.isFinite(milliseconds) && milliseconds <= now + 60_000,
112
+ "future/invalid signing time"
113
+ );
114
+ requireProof(
115
+ now - milliseconds <= policy.maxAgeSeconds * 1000,
116
+ "proposal authorization expired"
117
+ );
118
+ }
119
+ }
120
+
121
+ /** Fixed role matching never accepts a caller-selected expiry or predicate waiver. */
122
+ export function verifiedRole(
123
+ results,
124
+ descriptor,
125
+ digest,
126
+ policy,
127
+ predicate,
128
+ name
129
+ ) {
130
+ requireProof(
131
+ Array.isArray(results) && results.length === 1,
132
+ "ambiguous/missing attestation"
133
+ );
134
+ const result = results[0].verificationResult;
135
+ requireProof(
136
+ result?.statement?._type === "https://in-toto.io/Statement/v1",
137
+ "unsupported statement"
138
+ );
139
+ requireProof(
140
+ result.statement.predicateType === predicate,
141
+ "wrong predicate type"
142
+ );
143
+ const subjects = result.statement.subject;
144
+ requireProof(
145
+ Array.isArray(subjects) && subjects.length === 1,
146
+ "ambiguous subject"
147
+ );
148
+ requireProof(
149
+ subjects[0].name === name && subjects[0].digest?.sha256 === digest,
150
+ "proof subject differs"
151
+ );
152
+ matchCertificate(result.signature?.certificate, descriptor, policy);
153
+ return result;
154
+ }
155
+
156
+ /** Ordinary v1 remains short-lived current authorization. */
157
+ export function assertVerifiedAttestation(
158
+ results,
159
+ descriptor,
160
+ digest,
161
+ policy,
162
+ now = Date.now()
163
+ ) {
164
+ const result = verifiedRole(
165
+ results,
166
+ descriptor,
167
+ digest,
168
+ policy,
169
+ PROPOSAL_PREDICATE,
170
+ "descriptor.json"
171
+ );
172
+ matchSigningTime(result.verifiedTimestamps, policy, now);
173
+ return result.signature.certificate;
174
+ }
175
+
176
+ /** Shared literal argv does not replace official verification or authorization policy. */
177
+ export function officialArguments(policy, identity, file, bundle, predicate) {
178
+ return [
179
+ "attestation",
180
+ "verify",
181
+ file,
182
+ "--bundle",
183
+ bundle,
184
+ "--repo",
185
+ policy.repository,
186
+ "--signer-workflow",
187
+ policy.signerWorkflow,
188
+ "--signer-digest",
189
+ policy.signerDigest,
190
+ "--source-digest",
191
+ identity.parent,
192
+ "--source-ref",
193
+ "refs/heads/main",
194
+ "--predicate-type",
195
+ predicate,
196
+ "--cert-oidc-issuer",
197
+ ACTIONS_ISSUER,
198
+ "--deny-self-hosted-runners",
199
+ "--format",
200
+ "json",
201
+ ];
202
+ }
203
+
204
+ /** The three explicit entry points select fixed official verification roles. */
205
+ export function officialResult(
206
+ policy,
207
+ identity,
208
+ file,
209
+ bundle,
210
+ predicate,
211
+ execute
212
+ ) {
213
+ return ghJson(
214
+ policy,
215
+ officialArguments(policy, identity, file, bundle, predicate),
216
+ execute
217
+ );
218
+ }
219
+
220
+ /** Invoke official signature/root/timestamp verification before applying policy. */
221
+ export function verifyDescriptorAttestation(
222
+ policy,
223
+ descriptor,
224
+ paths,
225
+ digest,
226
+ execute = boundedSpawnSync
227
+ ) {
228
+ return assertVerifiedAttestation(
229
+ officialResult(
230
+ policy,
231
+ descriptor,
232
+ paths.descriptor,
233
+ paths.bundle,
234
+ PROPOSAL_PREDICATE,
235
+ execute
236
+ ),
237
+ descriptor,
238
+ digest,
239
+ policy
240
+ );
241
+ }
242
+
243
+ // Compatibility exports retain ordinary callers without top-level provider work.
244
+ export { verifyCurrentProvider } from "./github-attestation-provider.mjs";
@@ -0,0 +1,289 @@
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-schema writer evidence and idempotent current claim. @module npm-updater */
5
+ import { required, CLAIM, validateProposal } from "./npm-update-contract.mjs";
6
+ import { baseline } from "./npm-update-prepare.mjs";
7
+ import { locateCheckpoint } from "./npm-update-recovery.mjs";
8
+ import { runProcess } from "./npm-update-process.mjs";
9
+ import { sha256 } from "./github-attestation-verifier.mjs";
10
+ import { registryVersion } from "./npm-update-npm.mjs";
11
+ import {
12
+ buildDraft,
13
+ qualityGates,
14
+ inspectLeaf,
15
+ leafRoles,
16
+ historicalFiling,
17
+ claimLeaf,
18
+ } from "./npm-update-leaf.mjs";
19
+ /** Probe the named surfaces, all-state relationships and committed history before filing. */
20
+ export async function filingEvidence(api, proposal, config, cwd) {
21
+ await baseline(
22
+ cwd,
23
+ { PATH: process.env.PATH, HOME: "/nonexistent", GIT_TERMINAL_PROMPT: "0" },
24
+ proposal
25
+ );
26
+ await api.main(proposal.parent);
27
+ const query = `repo:${proposal.repository} npm is:issue in:title,body`;
28
+ const search = await api.request(
29
+ `search/issues?q=${encodeURIComponent(query)}&per_page=100`
30
+ );
31
+ required(
32
+ !search.incomplete_results && search.total_count <= 100,
33
+ "relationship search is incomplete or oversized"
34
+ );
35
+ for (const item of search.items) await api.issue(item.number);
36
+ const history = await runProcess(
37
+ "git",
38
+ [
39
+ "log",
40
+ proposal.parent,
41
+ "--max-count=20",
42
+ "--format=%H %s",
43
+ "--",
44
+ "package.json",
45
+ "package-lock.json",
46
+ ],
47
+ {
48
+ cwd,
49
+ env: {
50
+ PATH: process.env.PATH,
51
+ HOME: "/nonexistent",
52
+ GIT_TERMINAL_PROMPT: "0",
53
+ },
54
+ }
55
+ );
56
+ const registry = [];
57
+ for (const update of proposal.updates)
58
+ registry.push(await registryVersion(update, proposal));
59
+ const labels = (await api.list(`repos/${proposal.repository}/labels`)).map(
60
+ label => label.name
61
+ );
62
+ return {
63
+ history: {
64
+ command: `git log ${proposal.parent} --max-count=20 -- package.json package-lock.json`,
65
+ result: history.stdout.toString() || "No matching history.",
66
+ },
67
+ search: { query, total: search.total_count },
68
+ registry,
69
+ labels,
70
+ main: proposal.parent,
71
+ };
72
+ }
73
+
74
+ /** Same selected versions are discovered before deriving a new parent-bound key. */
75
+ export async function discoverPrior(api, proposal) {
76
+ const query = `repo:${proposal.repository} "${proposal.selectionKey}" is:issue in:body`;
77
+ const found = await api.request(
78
+ `search/issues?q=${encodeURIComponent(query)}&per_page=100`
79
+ );
80
+ required(
81
+ found.incomplete_results === false &&
82
+ Number.isSafeInteger(found.total_count) &&
83
+ found.total_count <= 1 &&
84
+ Array.isArray(found.items) &&
85
+ found.items.length === found.total_count,
86
+ "prior selection search is incomplete or ambiguous"
87
+ );
88
+ if (!found.total_count) return { status: "new" };
89
+ const issue = await api.issue(found.items[0].number);
90
+ required(
91
+ issue.state === "open" &&
92
+ !issue.pull_request &&
93
+ typeof issue.body === "string",
94
+ "closed or foreign prior proposal is preserved"
95
+ );
96
+ const selected = [
97
+ ...issue.body.matchAll(/^Selection key: ([a-f0-9]{64})$/gm),
98
+ ];
99
+ const parents = [...issue.body.matchAll(/^Base parent: ([a-f0-9]{40})$/gm)];
100
+ required(
101
+ selected.length === 1 &&
102
+ selected[0][1] === proposal.selectionKey &&
103
+ parents.length === 1,
104
+ "prior proposal identity differs"
105
+ );
106
+ return {
107
+ status:
108
+ parents[0][1] === proposal.parent ? "existing" : "stale-outstanding",
109
+ issue,
110
+ };
111
+ }
112
+
113
+ /** Restoration preserves original filing bytes while checking the live claim and holds. */
114
+ async function restoredAllocation({
115
+ api,
116
+ proposal,
117
+ policy,
118
+ config,
119
+ prior,
120
+ checkpoint,
121
+ evidence,
122
+ currentEvidence,
123
+ }) {
124
+ if (prior.issue) {
125
+ const state = await inspectLeaf(
126
+ prior.issue,
127
+ buildDraft(proposal, policy, config, evidence),
128
+ policy,
129
+ config
130
+ );
131
+ if (checkpoint?.signed) {
132
+ const allocation = checkpoint.payload.allocation;
133
+ required(
134
+ state.claimed &&
135
+ !state.unassigned &&
136
+ state.claims.length === 1 &&
137
+ String(state.claims[0].id) === allocation.claimCommentId &&
138
+ sha256(state.claims[0].body) === allocation.claimSha256 &&
139
+ String(state.claims[0].user?.id) === api.policy.claimActorId &&
140
+ state.claims[0].user?.type === "Bot",
141
+ "origin checkpoint current claim changed"
142
+ );
143
+ qualityGates(
144
+ {
145
+ ...allocation.draft,
146
+ title: prior.issue.title,
147
+ body: prior.issue.body,
148
+ },
149
+ proposal,
150
+ policy,
151
+ config,
152
+ evidence
153
+ );
154
+ return { ...allocation, currentEvidence, origin: checkpoint };
155
+ }
156
+ }
157
+ }
158
+
159
+ /** Missing configured lifecycle labels are provisioned before the fresh fixed draft. */
160
+ async function ensureLabels({
161
+ api,
162
+ policy,
163
+ roles,
164
+ currentEvidence,
165
+ prior,
166
+ evidence,
167
+ }) {
168
+ for (const name of [
169
+ "type:Task",
170
+ "priority:medium",
171
+ roles.ready,
172
+ roles.claimed,
173
+ ]) {
174
+ if (!currentEvidence.labels.includes(name)) {
175
+ await api.request(`repos/${policy.repository}/labels`, "POST", {
176
+ name,
177
+ color: "ededed",
178
+ description: "Managed dependency Task lifecycle",
179
+ });
180
+ currentEvidence.labels.push(name);
181
+ if (!prior.issue) evidence.labels = currentEvidence.labels;
182
+ }
183
+ }
184
+ }
185
+
186
+ /** The shipped claim and post-write quality check complete one canonical leaf. */
187
+ async function completeAllocation({
188
+ api,
189
+ proposal,
190
+ policy,
191
+ config,
192
+ prior,
193
+ evidence,
194
+ currentEvidence,
195
+ roles,
196
+ }) {
197
+ const draft = buildDraft(proposal, policy, config, evidence);
198
+ const before = qualityGates(draft, proposal, policy, config, evidence);
199
+ let issue =
200
+ prior.issue ??
201
+ (await api.request(`repos/${policy.repository}/issues`, "POST", {
202
+ title: draft.title,
203
+ body: draft.body,
204
+ labels: draft.labels,
205
+ assignees: draft.assignees,
206
+ }));
207
+ const claimed = await claimLeaf(
208
+ api,
209
+ issue,
210
+ draft,
211
+ proposal,
212
+ policy,
213
+ config,
214
+ evidence,
215
+ roles
216
+ );
217
+ issue = claimed.issue;
218
+ const state = claimed.state;
219
+ const after = qualityGates(
220
+ { ...draft, title: issue.title, body: issue.body },
221
+ proposal,
222
+ policy,
223
+ config,
224
+ evidence
225
+ );
226
+ const announcement = `[${policy.repository.split("/")[1]}] Fresh dependency Task ${policy.repository}#${issue.number} is assigned to ${policy.maintainer}. Proposal key: ${proposal.key}. Pre/post writer quality gates completed; ordinary gates and runtime evidence remain required.`;
227
+ if (!issue.comments.some(comment => comment.body === announcement))
228
+ await api.request(
229
+ `repos/${policy.repository}/issues/${issue.number}/comments`,
230
+ "POST",
231
+ { body: announcement }
232
+ );
233
+ return {
234
+ number: issue.number,
235
+ workItem: `${policy.repository}#${issue.number}`,
236
+ claimCommentId: String(state.claims[0].id),
237
+ claimSha256: sha256(CLAIM),
238
+ draft,
239
+ evidence,
240
+ quality: { before, after },
241
+ currentEvidence,
242
+ originalProposal: proposal,
243
+ };
244
+ }
245
+
246
+ /** Fixed writer pre/post quality plus real idempotent claim; no Skill RPC is invented. */
247
+ export async function allocateLeaf({
248
+ api,
249
+ proposal: suppliedProposal,
250
+ policy,
251
+ config,
252
+ cwd,
253
+ }) {
254
+ let proposal = suppliedProposal;
255
+ validateProposal(proposal, policy);
256
+ await api.main(proposal.parent);
257
+ const prior = await discoverPrior(api, proposal);
258
+ if (prior.status === "stale-outstanding")
259
+ return {
260
+ status: prior.status,
261
+ number: prior.issue.number,
262
+ workItem: `${policy.repository}#${prior.issue.number}`,
263
+ };
264
+ await api.assignable(policy.maintainer);
265
+ const currentEvidence = await filingEvidence(api, proposal, config, cwd);
266
+ const checkpoint = prior.issue
267
+ ? locateCheckpoint(prior.issue.comments, proposal, policy)
268
+ : undefined;
269
+ if (checkpoint) proposal = checkpoint.payload.proposal;
270
+ const evidence = prior.issue
271
+ ? historicalFiling(prior.issue)
272
+ : currentEvidence;
273
+ const roles = leafRoles(config, policy.repository);
274
+ const inputs = {
275
+ api,
276
+ proposal,
277
+ policy,
278
+ config,
279
+ prior,
280
+ checkpoint,
281
+ evidence,
282
+ currentEvidence,
283
+ roles,
284
+ };
285
+ const restored = await restoredAllocation(inputs);
286
+ if (restored) return restored;
287
+ await ensureLabels(inputs);
288
+ return completeAllocation(inputs);
289
+ }