@codyswann/lisa 4.70.4 → 4.71.1
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.
- package/all/copy-overwrite/scripts/lib/automation-provenance-contract.mjs +174 -0
- package/all/copy-overwrite/scripts/lib/automation-provenance-local.mjs +279 -0
- package/all/copy-overwrite/scripts/lib/gate-failure-diagnosis.mjs +17 -7
- package/all/copy-overwrite/scripts/lib/github-attestation-provider.mjs +241 -0
- package/all/copy-overwrite/scripts/lib/github-attestation-recovery.mjs +283 -0
- package/all/copy-overwrite/scripts/lib/github-attestation-verifier.mjs +244 -0
- package/all/copy-overwrite/scripts/lib/npm-update-allocate.mjs +289 -0
- package/all/copy-overwrite/scripts/lib/npm-update-authorization.mjs +235 -0
- package/all/copy-overwrite/scripts/lib/npm-update-broker-client.mjs +92 -0
- package/all/copy-overwrite/scripts/lib/npm-update-checkpoint.mjs +241 -0
- package/all/copy-overwrite/scripts/lib/npm-update-contract.mjs +278 -0
- package/all/copy-overwrite/scripts/lib/npm-update-controller-broker.mjs +234 -0
- package/all/copy-overwrite/scripts/lib/npm-update-controller-factory.mjs +278 -0
- package/all/copy-overwrite/scripts/lib/npm-update-controller-recipe.mjs +135 -0
- package/all/copy-overwrite/scripts/lib/npm-update-execution-adapter.mjs +292 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate-hooks.mjs +214 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate-install.mjs +267 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate-proof.mjs +99 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate.mjs +300 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gh-dispatch.mjs +182 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gh-grants.mjs +156 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gh-requests.mjs +221 -0
- package/all/copy-overwrite/scripts/lib/npm-update-github.mjs +214 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper-graph.mjs +238 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper-inventory.mjs +126 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper.mjs +292 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-installation.mjs +116 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-preload.mjs +153 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-provider.mjs +207 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-read-client.mjs +164 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hosted-gate.mjs +300 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hosted-hook.mjs +157 -0
- package/all/copy-overwrite/scripts/lib/npm-update-invariants.mjs +28 -0
- package/all/copy-overwrite/scripts/lib/npm-update-isolation.mjs +11 -0
- package/all/copy-overwrite/scripts/lib/npm-update-leaf-contract.mjs +171 -0
- package/all/copy-overwrite/scripts/lib/npm-update-leaf.mjs +194 -0
- package/all/copy-overwrite/scripts/lib/npm-update-native-process.mjs +155 -0
- package/all/copy-overwrite/scripts/lib/npm-update-npm.mjs +190 -0
- package/all/copy-overwrite/scripts/lib/npm-update-object.mjs +112 -0
- package/all/copy-overwrite/scripts/lib/npm-update-orchestrator.mjs +273 -0
- package/all/copy-overwrite/scripts/lib/npm-update-owner.mjs +292 -0
- package/all/copy-overwrite/scripts/lib/npm-update-prepare.mjs +263 -0
- package/all/copy-overwrite/scripts/lib/npm-update-process-core.mjs +179 -0
- package/all/copy-overwrite/scripts/lib/npm-update-process.mjs +16 -0
- package/all/copy-overwrite/scripts/lib/npm-update-publication.mjs +258 -0
- package/all/copy-overwrite/scripts/lib/npm-update-publish.mjs +254 -0
- package/all/copy-overwrite/scripts/lib/npm-update-quality.mjs +153 -0
- package/all/copy-overwrite/scripts/lib/npm-update-recovery.mjs +264 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime-archive.mjs +204 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime-graph.mjs +185 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime-transport.mjs +221 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime.mjs +284 -0
- package/all/copy-overwrite/scripts/lib/npm-update-tool-launcher.mjs +287 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-environment.mjs +161 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-inspection.mjs +142 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-lifecycle.mjs +256 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-policy.mjs +266 -0
- package/all/copy-overwrite/scripts/lib/process-tree-runner.mjs +230 -36
- package/all/copy-overwrite/scripts/lisa-automation-provenance.mjs +161 -0
- package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +89 -5
- package/all/copy-overwrite/scripts/lisa-npm-updater.mjs +204 -0
- package/all/copy-overwrite/scripts/lisa-run-gates.mjs +48 -123
- package/all/copy-overwrite/scripts/lisa-work-item.mjs +107 -41
- package/all/copy-overwrite/scripts/npm-updater-gate-runtime.json +1 -0
- package/all/copy-overwrite/scripts/npm-updater-gate-supervisor.c +121 -0
- package/all/copy-overwrite/scripts/npm-updater-gate.Dockerfile +49 -0
- package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +1085 -0
- package/dist/codex/scripts/block-no-verify.sh +89 -5
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +604 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +159 -11
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/block-no-verify.sh +89 -5
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/hooks/block-no-verify.agy.sh +89 -5
- package/plugins/lisa/hooks/block-no-verify.sh +89 -5
- package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +89 -5
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/hooks/block-no-verify.sh +89 -5
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-no-verify.sh +89 -5
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/hooks/block-no-verify.agy.sh +89 -5
- package/plugins/src/base/hooks/block-no-verify.sh +89 -5
- package/scripts/check-shipped-surface-removals.mjs +96 -24
- package/scripts/generate-npm-updater-helper-graph.mjs +184 -0
- package/scripts/two-channel-couplings.json +31 -7
- package/typescript/copy-contents/.husky/commit-msg +18 -0
- package/typescript/copy-overwrite/.prettierignore +2 -0
|
@@ -0,0 +1,258 @@
|
|
|
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
|
+
/** Current-head checks and human approval retain their real integration and reviewer identities. @module npm-updater */
|
|
5
|
+
import { workItemLines } from "../lisa-work-item.mjs";
|
|
6
|
+
import { required } from "./npm-update-contract.mjs";
|
|
7
|
+
import {
|
|
8
|
+
sha256,
|
|
9
|
+
verifyDescriptorAttestation,
|
|
10
|
+
verifyCurrentProvider,
|
|
11
|
+
} from "./github-attestation-verifier.mjs";
|
|
12
|
+
import { canonicalJson } from "../lisa-automation-provenance.mjs";
|
|
13
|
+
import { readJson, readBytes } from "./npm-update-process.mjs";
|
|
14
|
+
import { existsSync } from "node:fs";
|
|
15
|
+
import { verifyRecoveryOrigin } from "./github-attestation-recovery.mjs";
|
|
16
|
+
|
|
17
|
+
/** Fixed distinct proof slots select origin or recovery; partial authority always refuses. */
|
|
18
|
+
export function publicationProof(
|
|
19
|
+
api,
|
|
20
|
+
descriptor,
|
|
21
|
+
proof,
|
|
22
|
+
proposal,
|
|
23
|
+
policy,
|
|
24
|
+
issue,
|
|
25
|
+
commit
|
|
26
|
+
) {
|
|
27
|
+
required(
|
|
28
|
+
canonicalJson(readJson(proof.descriptor, 65_536)) ===
|
|
29
|
+
canonicalJson(descriptor),
|
|
30
|
+
"publication descriptor bytes differ"
|
|
31
|
+
);
|
|
32
|
+
const digest = sha256(readBytes(proof.descriptor, 65_536));
|
|
33
|
+
const recovery = Boolean(proof.recovery && existsSync(proof.recovery));
|
|
34
|
+
const bundle = Boolean(
|
|
35
|
+
proof.recoveryBundle && existsSync(proof.recoveryBundle)
|
|
36
|
+
);
|
|
37
|
+
required(recovery === bundle, "partial publication recovery authority");
|
|
38
|
+
if (recovery) {
|
|
39
|
+
verifyRecoveryOrigin(
|
|
40
|
+
api.policy,
|
|
41
|
+
descriptor,
|
|
42
|
+
readJson(proof.recovery, 65_536),
|
|
43
|
+
digest,
|
|
44
|
+
proof,
|
|
45
|
+
{
|
|
46
|
+
npmPolicySha256: proposal.policySha256,
|
|
47
|
+
proposalKey: proposal.key,
|
|
48
|
+
leafBodySha256: sha256(issue.body),
|
|
49
|
+
commit: commit.sha,
|
|
50
|
+
maintainer: policy.maintainer,
|
|
51
|
+
recoverySha256: sha256(readBytes(proof.recovery, 65_536)),
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
} else {
|
|
55
|
+
verifyDescriptorAttestation(api.policy, descriptor, proof, digest);
|
|
56
|
+
verifyCurrentProvider(api.policy, descriptor);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The publisher consumes evidence of the actual ordinary commit and both original hook streams. */
|
|
61
|
+
export function ordinaryGateReceipt(
|
|
62
|
+
context,
|
|
63
|
+
object,
|
|
64
|
+
hook,
|
|
65
|
+
streams,
|
|
66
|
+
destination,
|
|
67
|
+
remote
|
|
68
|
+
) {
|
|
69
|
+
return {
|
|
70
|
+
raw: object.raw.toString("base64"),
|
|
71
|
+
receipt: {
|
|
72
|
+
version: 1,
|
|
73
|
+
proposalKey: context.proposal.key,
|
|
74
|
+
parent: context.proposal.parent,
|
|
75
|
+
tree: context.preview.descriptor.tree,
|
|
76
|
+
commit: object.head,
|
|
77
|
+
commitExit: object.committed.code,
|
|
78
|
+
pushExit: streams.destination.exit,
|
|
79
|
+
refs: streams.destination.refs,
|
|
80
|
+
audit: streams.audit,
|
|
81
|
+
destination: streams.destination,
|
|
82
|
+
destinationHead: destination.expectedBranchHead,
|
|
83
|
+
remote,
|
|
84
|
+
range: object.ranges,
|
|
85
|
+
hookSha256: hook.sourceSha256,
|
|
86
|
+
hookManager: hook.manager,
|
|
87
|
+
hookSource: hook.source,
|
|
88
|
+
hookWrapperSha256: hook.wrapperSha256,
|
|
89
|
+
commitLogSha256: sha256(
|
|
90
|
+
Buffer.concat([object.committed.stdout, object.committed.stderr])
|
|
91
|
+
),
|
|
92
|
+
pushLogSha256: streams.destination.logSha256,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Original writer commands retain independent current authorization at each boundary. */
|
|
98
|
+
export async function publicationBacklink(
|
|
99
|
+
{ api, cwd, proposal, allocation, commit, authorize, controller },
|
|
100
|
+
pr
|
|
101
|
+
) {
|
|
102
|
+
await authorize();
|
|
103
|
+
await api.workItem(
|
|
104
|
+
cwd,
|
|
105
|
+
["backlink", "--ref", allocation.workItem, "--pr-url", pr.html_url],
|
|
106
|
+
{ ...controller, proposal, allocation, commit, pr }
|
|
107
|
+
);
|
|
108
|
+
await authorize();
|
|
109
|
+
await api.workItem(
|
|
110
|
+
cwd,
|
|
111
|
+
[
|
|
112
|
+
"validate-pr",
|
|
113
|
+
"--base",
|
|
114
|
+
proposal.parent,
|
|
115
|
+
"--head",
|
|
116
|
+
commit.sha,
|
|
117
|
+
"--pr-number",
|
|
118
|
+
String(pr.number),
|
|
119
|
+
"--repo",
|
|
120
|
+
proposal.repository,
|
|
121
|
+
"--pr-url",
|
|
122
|
+
pr.html_url,
|
|
123
|
+
],
|
|
124
|
+
{ ...controller, proposal, allocation, commit, pr }
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** A fixed snapshot refuses foreign existing refs or PRs before any mutation. */
|
|
129
|
+
export async function destinationSnapshot(api, proposal, allocation, commit) {
|
|
130
|
+
const branch = `lisa/npm-${proposal.key}`;
|
|
131
|
+
const ref = await api.maybe(
|
|
132
|
+
`repos/${proposal.repository}/git/ref/heads/${branch}`
|
|
133
|
+
);
|
|
134
|
+
required(
|
|
135
|
+
!ref ||
|
|
136
|
+
(ref.ref === `refs/heads/${branch}` &&
|
|
137
|
+
ref.object?.type === "commit" &&
|
|
138
|
+
ref.object.sha === commit),
|
|
139
|
+
"foreign proposal branch is preserved"
|
|
140
|
+
);
|
|
141
|
+
const pulls = await api.list(
|
|
142
|
+
`repos/${proposal.repository}/pulls?state=all&head=${encodeURIComponent(`${proposal.repository.split("/")[0]}:${branch}`)}`
|
|
143
|
+
);
|
|
144
|
+
required(pulls.length <= 1, "ambiguous proposal PRs are preserved");
|
|
145
|
+
const pr = pulls[0];
|
|
146
|
+
if (pr)
|
|
147
|
+
required(
|
|
148
|
+
ref &&
|
|
149
|
+
Number.isSafeInteger(pr.number) &&
|
|
150
|
+
pr.number > 0 &&
|
|
151
|
+
pr.html_url ===
|
|
152
|
+
`https://github.com/${proposal.repository}/pull/${pr.number}` &&
|
|
153
|
+
pr.state === "open" &&
|
|
154
|
+
pr.head?.sha === commit &&
|
|
155
|
+
pr.head?.ref === branch &&
|
|
156
|
+
pr.head?.repo?.full_name === proposal.repository &&
|
|
157
|
+
pr.base?.ref === "main" &&
|
|
158
|
+
pr.base?.repo?.full_name === proposal.repository &&
|
|
159
|
+
pr.body === publicationBody(allocation),
|
|
160
|
+
"foreign/closed/changed proposal PR is preserved"
|
|
161
|
+
);
|
|
162
|
+
return {
|
|
163
|
+
branch,
|
|
164
|
+
expectedBranchHead: ref?.object.sha ?? null,
|
|
165
|
+
prNumber: pr?.number ?? null,
|
|
166
|
+
expectedPrHead: pr?.head.sha ?? null,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** The declaration is complete and the relationship waits for postmerge verification. */
|
|
171
|
+
export function publicationBody(allocation) {
|
|
172
|
+
const body = `${allocation.draft.body}\nWork-Item: ${allocation.workItem}\n\nRelates to https://github.com/${allocation.workItem.replace("#", "/issues/")}\n`;
|
|
173
|
+
const closing = [
|
|
174
|
+
...body.matchAll(/\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+/gi),
|
|
175
|
+
];
|
|
176
|
+
const references = [
|
|
177
|
+
/^#[1-9]\d*\b/,
|
|
178
|
+
/^[\w.-]+\/[\w.-]+#[1-9]\d*\b/,
|
|
179
|
+
/^https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/issues\/[1-9]\d*\b/,
|
|
180
|
+
];
|
|
181
|
+
required(
|
|
182
|
+
!closing.some(match =>
|
|
183
|
+
references.some(expression =>
|
|
184
|
+
expression.test(body.slice(match.index + match[0].length))
|
|
185
|
+
)
|
|
186
|
+
),
|
|
187
|
+
"premature closing relationship is forbidden"
|
|
188
|
+
);
|
|
189
|
+
const declaredReferences = workItemLines(body);
|
|
190
|
+
required(
|
|
191
|
+
declaredReferences.length === 1 &&
|
|
192
|
+
declaredReferences[0] === allocation.workItem,
|
|
193
|
+
"complete PR declaration differs"
|
|
194
|
+
);
|
|
195
|
+
return body;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Default Actions policy may forbid creation; activation belongs to the operator. */
|
|
199
|
+
export function assertPublicationPolicy(value) {
|
|
200
|
+
required(
|
|
201
|
+
value.can_approve_pull_request_reviews === true,
|
|
202
|
+
"Actions pull-request creation is disabled; explicitly activate repository Actions PR creation before retrying"
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Current-head success and real human approval cannot be supplied by stale/skip records. */
|
|
207
|
+
export function assertPublication(pr, checks, reviews, sha, requirements = []) {
|
|
208
|
+
required(
|
|
209
|
+
pr.state === "open" && pr.head?.sha === sha && pr.base?.ref === "main",
|
|
210
|
+
"current-head PR differs"
|
|
211
|
+
);
|
|
212
|
+
required(
|
|
213
|
+
checks.length > 0 && requirements.length > 0,
|
|
214
|
+
"required current-head checks are not qualified"
|
|
215
|
+
);
|
|
216
|
+
required(
|
|
217
|
+
requirements.every(requirement =>
|
|
218
|
+
successfulCheck(checks, requirement, sha)
|
|
219
|
+
),
|
|
220
|
+
"required current-head checks are incomplete"
|
|
221
|
+
);
|
|
222
|
+
const latest = new Map();
|
|
223
|
+
for (const review of [...reviews].sort((left, right) => left.id - right.id))
|
|
224
|
+
if (review.user?.type === "User") latest.set(review.user.id, review);
|
|
225
|
+
required(
|
|
226
|
+
![...latest.values()].some(
|
|
227
|
+
review => review.state === "CHANGES_REQUESTED"
|
|
228
|
+
) &&
|
|
229
|
+
[...latest.values()].some(
|
|
230
|
+
review => review.state === "APPROVED" && review.commit_id === sha
|
|
231
|
+
),
|
|
232
|
+
"genuine current-head human approval is pending"
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** The newest run from the exact required integration must succeed on this head. */
|
|
237
|
+
function successfulCheck(checks, requirement, sha) {
|
|
238
|
+
required(
|
|
239
|
+
requirement &&
|
|
240
|
+
typeof requirement.name === "string" &&
|
|
241
|
+
Number.isSafeInteger(requirement.appId) &&
|
|
242
|
+
requirement.appId > 0,
|
|
243
|
+
"required check integration identity is unresolved"
|
|
244
|
+
);
|
|
245
|
+
const matches = checks.filter(
|
|
246
|
+
check =>
|
|
247
|
+
check.name === requirement.name &&
|
|
248
|
+
check.app?.id === requirement.appId &&
|
|
249
|
+
check.head_sha === sha
|
|
250
|
+
);
|
|
251
|
+
required(
|
|
252
|
+
matches.every(check => Number.isSafeInteger(check.id) && check.id > 0) &&
|
|
253
|
+
new Set(matches.map(check => check.id)).size === matches.length,
|
|
254
|
+
"ambiguous current check execution"
|
|
255
|
+
);
|
|
256
|
+
const latest = matches.sort((left, right) => right.id - left.id)[0];
|
|
257
|
+
return latest?.status === "completed" && latest.conclusion === "success";
|
|
258
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
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
|
+
/** Durable proof and exact gated raw-object publication. @module npm-updater */
|
|
5
|
+
import { canonicalJson } from "../lisa-automation-provenance.mjs";
|
|
6
|
+
import {
|
|
7
|
+
required,
|
|
8
|
+
validateProposal,
|
|
9
|
+
validateRawCommit,
|
|
10
|
+
} from "./npm-update-contract.mjs";
|
|
11
|
+
import {
|
|
12
|
+
authorizeAllocation,
|
|
13
|
+
destinationSnapshot,
|
|
14
|
+
} from "./npm-update-authorization.mjs";
|
|
15
|
+
import { runProcess } from "./npm-update-process.mjs";
|
|
16
|
+
import { descriptorFor } from "./npm-update-gate.mjs";
|
|
17
|
+
import { assertGateTransport } from "./npm-update-gate-hooks.mjs";
|
|
18
|
+
import {
|
|
19
|
+
publicationBody,
|
|
20
|
+
publicationBacklink,
|
|
21
|
+
publicationProof,
|
|
22
|
+
} from "./npm-update-publication.mjs";
|
|
23
|
+
export {
|
|
24
|
+
publicationBody,
|
|
25
|
+
assertPublicationPolicy,
|
|
26
|
+
assertPublication,
|
|
27
|
+
} from "./npm-update-publication.mjs";
|
|
28
|
+
|
|
29
|
+
/** Create an absent exact ref only after a second live destination and authority read. */
|
|
30
|
+
async function destinationRef(api, proposal, allocation, commit, authorize) {
|
|
31
|
+
const path = `repos/${proposal.repository}`;
|
|
32
|
+
await authorize();
|
|
33
|
+
let snapshot = await destinationSnapshot(
|
|
34
|
+
api,
|
|
35
|
+
proposal,
|
|
36
|
+
allocation,
|
|
37
|
+
commit.sha
|
|
38
|
+
);
|
|
39
|
+
if (snapshot.expectedBranchHead === null) {
|
|
40
|
+
await authorize();
|
|
41
|
+
snapshot = await destinationSnapshot(api, proposal, allocation, commit.sha);
|
|
42
|
+
required(
|
|
43
|
+
snapshot.expectedBranchHead === null && snapshot.prNumber === null,
|
|
44
|
+
"publication destination changed before ref creation"
|
|
45
|
+
);
|
|
46
|
+
const ref = await api.request(`${path}/git/refs`, "POST", {
|
|
47
|
+
ref: `refs/heads/${snapshot.branch}`,
|
|
48
|
+
sha: commit.sha,
|
|
49
|
+
});
|
|
50
|
+
required(
|
|
51
|
+
ref.ref === `refs/heads/${snapshot.branch}` &&
|
|
52
|
+
ref.object?.type === "commit" &&
|
|
53
|
+
ref.object.sha === commit.sha,
|
|
54
|
+
"published ref differs"
|
|
55
|
+
);
|
|
56
|
+
snapshot = await destinationSnapshot(api, proposal, allocation, commit.sha);
|
|
57
|
+
required(
|
|
58
|
+
snapshot.expectedBranchHead === commit.sha,
|
|
59
|
+
"published ref readback differs"
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return snapshot;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Resume the exact owned ref/PR and repair only its supported managed backlink. */
|
|
66
|
+
export async function publishDestination({
|
|
67
|
+
api,
|
|
68
|
+
cwd,
|
|
69
|
+
proposal,
|
|
70
|
+
allocation,
|
|
71
|
+
commit,
|
|
72
|
+
authorize,
|
|
73
|
+
controller,
|
|
74
|
+
}) {
|
|
75
|
+
const snapshot = await destinationRef(
|
|
76
|
+
api,
|
|
77
|
+
proposal,
|
|
78
|
+
allocation,
|
|
79
|
+
commit,
|
|
80
|
+
authorize
|
|
81
|
+
);
|
|
82
|
+
const path = `repos/${proposal.repository}`;
|
|
83
|
+
let pr;
|
|
84
|
+
if (snapshot.prNumber === null) {
|
|
85
|
+
await authorize();
|
|
86
|
+
const current = await destinationSnapshot(
|
|
87
|
+
api,
|
|
88
|
+
proposal,
|
|
89
|
+
allocation,
|
|
90
|
+
commit.sha
|
|
91
|
+
);
|
|
92
|
+
required(
|
|
93
|
+
current.prNumber === null,
|
|
94
|
+
"publication PR appeared before creation"
|
|
95
|
+
);
|
|
96
|
+
pr = await api.request(`${path}/pulls`, "POST", {
|
|
97
|
+
title: allocation.draft.title,
|
|
98
|
+
head: snapshot.branch,
|
|
99
|
+
base: "main",
|
|
100
|
+
body: publicationBody(allocation),
|
|
101
|
+
});
|
|
102
|
+
} else pr = await api.request(`${path}/pulls/${snapshot.prNumber}`);
|
|
103
|
+
const readback = await destinationSnapshot(
|
|
104
|
+
api,
|
|
105
|
+
proposal,
|
|
106
|
+
allocation,
|
|
107
|
+
commit.sha
|
|
108
|
+
);
|
|
109
|
+
required(
|
|
110
|
+
readback.prNumber === pr.number &&
|
|
111
|
+
pr.base?.sha === proposal.parent &&
|
|
112
|
+
pr.user?.type === "Bot" &&
|
|
113
|
+
pr.user.login === "github-actions[bot]",
|
|
114
|
+
"actual publisher identity or immutable PR base differs"
|
|
115
|
+
);
|
|
116
|
+
await publicationBacklink(
|
|
117
|
+
{ api, cwd, proposal, allocation, commit, authorize, controller },
|
|
118
|
+
pr
|
|
119
|
+
);
|
|
120
|
+
return {
|
|
121
|
+
status: "published-awaiting-review",
|
|
122
|
+
number: pr.number,
|
|
123
|
+
url: pr.html_url,
|
|
124
|
+
commit: commit.sha,
|
|
125
|
+
base: proposal.parent,
|
|
126
|
+
publisher: pr.user.login,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The publisher consumes signed/gated data and writes only an identical two-file commit. */
|
|
131
|
+
export async function publishProposal({
|
|
132
|
+
api,
|
|
133
|
+
proposal,
|
|
134
|
+
policy,
|
|
135
|
+
config,
|
|
136
|
+
allocation,
|
|
137
|
+
descriptor,
|
|
138
|
+
proof,
|
|
139
|
+
receipt,
|
|
140
|
+
raw,
|
|
141
|
+
cwd,
|
|
142
|
+
}) {
|
|
143
|
+
const deadline = Date.now() + 900_000;
|
|
144
|
+
validateProposal(proposal, policy);
|
|
145
|
+
const commit = validateRawCommit(raw, descriptor, cwd);
|
|
146
|
+
required(
|
|
147
|
+
descriptor.parent === proposal.parent &&
|
|
148
|
+
descriptor.proposalKey === proposal.bindingKey &&
|
|
149
|
+
canonicalJson(descriptor.files) === canonicalJson(proposal.hashes) &&
|
|
150
|
+
canonicalJson(descriptor.updates) === canonicalJson(proposal.updates) &&
|
|
151
|
+
descriptor.workItem === allocation.workItem &&
|
|
152
|
+
descriptor.claimCommentId === allocation.claimCommentId,
|
|
153
|
+
"signed proposal allocation differs"
|
|
154
|
+
);
|
|
155
|
+
required(
|
|
156
|
+
receipt.commit === commit.sha &&
|
|
157
|
+
receipt.tree === descriptor.tree &&
|
|
158
|
+
receipt.parent === proposal.parent &&
|
|
159
|
+
receipt.proposalKey === proposal.key &&
|
|
160
|
+
receipt.commitExit === 0 &&
|
|
161
|
+
receipt.pushExit === 0,
|
|
162
|
+
"ordinary gate receipt differs"
|
|
163
|
+
);
|
|
164
|
+
assertGateTransport(
|
|
165
|
+
receipt,
|
|
166
|
+
`lisa/npm-${proposal.key}`,
|
|
167
|
+
commit.sha,
|
|
168
|
+
proposal.parent,
|
|
169
|
+
receipt.destinationHead
|
|
170
|
+
);
|
|
171
|
+
required(
|
|
172
|
+
receipt.remote === `https://github.com/${policy.repository}.git` &&
|
|
173
|
+
receipt.refs === receipt.destination.refs &&
|
|
174
|
+
canonicalJson(receipt.range) === canonicalJson([commit.sha]),
|
|
175
|
+
"gated transport range differs"
|
|
176
|
+
);
|
|
177
|
+
const authorize = async () => {
|
|
178
|
+
const issue = await authorizeAllocation(
|
|
179
|
+
api,
|
|
180
|
+
proposal,
|
|
181
|
+
allocation,
|
|
182
|
+
policy,
|
|
183
|
+
config
|
|
184
|
+
);
|
|
185
|
+
await destinationSnapshot(api, proposal, allocation, commit.sha);
|
|
186
|
+
publicationProof(api, descriptor, proof, proposal, policy, issue, commit);
|
|
187
|
+
return issue;
|
|
188
|
+
};
|
|
189
|
+
await authorize();
|
|
190
|
+
await publicationObjects(
|
|
191
|
+
cwd,
|
|
192
|
+
proposal,
|
|
193
|
+
policy,
|
|
194
|
+
config,
|
|
195
|
+
allocation,
|
|
196
|
+
descriptor,
|
|
197
|
+
raw,
|
|
198
|
+
commit
|
|
199
|
+
);
|
|
200
|
+
await api.gitCommit(proposal, descriptor, commit, authorize, cwd);
|
|
201
|
+
return publishDestination({
|
|
202
|
+
api,
|
|
203
|
+
cwd,
|
|
204
|
+
proposal,
|
|
205
|
+
allocation,
|
|
206
|
+
commit,
|
|
207
|
+
authorize,
|
|
208
|
+
controller: { config, policy, descriptor, proof, deadline },
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Reconstruct local objects as Git data so the original validator reads the complete range. */
|
|
213
|
+
export async function publicationObjects(
|
|
214
|
+
cwd,
|
|
215
|
+
proposal,
|
|
216
|
+
policy,
|
|
217
|
+
config,
|
|
218
|
+
allocation,
|
|
219
|
+
descriptor,
|
|
220
|
+
raw,
|
|
221
|
+
commit
|
|
222
|
+
) {
|
|
223
|
+
const preview = await descriptorFor({
|
|
224
|
+
cwd,
|
|
225
|
+
proposal,
|
|
226
|
+
policy,
|
|
227
|
+
automation: config.automationProvenance,
|
|
228
|
+
allocation,
|
|
229
|
+
runId: descriptor.runId,
|
|
230
|
+
runAttempt: descriptor.runAttempt,
|
|
231
|
+
epoch: Number(descriptor.author.split(" ").at(-2)),
|
|
232
|
+
});
|
|
233
|
+
required(
|
|
234
|
+
canonicalJson(preview.descriptor) === canonicalJson(descriptor),
|
|
235
|
+
"publisher original object reconstruction differs"
|
|
236
|
+
);
|
|
237
|
+
const result = await runProcess(
|
|
238
|
+
"git",
|
|
239
|
+
["hash-object", "-t", "commit", "-w", "--stdin"],
|
|
240
|
+
{
|
|
241
|
+
cwd,
|
|
242
|
+
env: {
|
|
243
|
+
PATH: process.env.PATH,
|
|
244
|
+
HOME: "/nonexistent",
|
|
245
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
246
|
+
},
|
|
247
|
+
input: raw,
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
required(
|
|
251
|
+
result.stdout.toString().trim() === commit.sha,
|
|
252
|
+
"publisher local raw object differs"
|
|
253
|
+
);
|
|
254
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
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
|
+
/** Twenty-five fixed writer checks retain semantic quality independently of storage. */
|
|
5
|
+
import { required } from "./npm-update-contract.mjs";
|
|
6
|
+
import { environment, buildDraft } from "./npm-update-leaf-contract.mjs";
|
|
7
|
+
|
|
8
|
+
/** Core Task fields are validated independently of provider storage. */
|
|
9
|
+
function coreChecks(draft, body, policy) {
|
|
10
|
+
return [
|
|
11
|
+
[
|
|
12
|
+
"S1",
|
|
13
|
+
Boolean(draft.title && body && policy.repository),
|
|
14
|
+
"complete Task core",
|
|
15
|
+
],
|
|
16
|
+
[
|
|
17
|
+
"S2",
|
|
18
|
+
draft.title.startsWith("Update ") &&
|
|
19
|
+
draft.title.length <= 100 &&
|
|
20
|
+
!draft.title.includes("\n"),
|
|
21
|
+
"imperative bounded title",
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
"S3",
|
|
25
|
+
[
|
|
26
|
+
"Context / Business Value",
|
|
27
|
+
"Technical Approach",
|
|
28
|
+
"Acceptance Criteria",
|
|
29
|
+
"Out of Scope",
|
|
30
|
+
].every(name => body.includes(`## ${name}\n`)),
|
|
31
|
+
"three audiences and scope",
|
|
32
|
+
],
|
|
33
|
+
[
|
|
34
|
+
"S4",
|
|
35
|
+
/Scenario:.*\n {2}Given .*\n {2}When .*\n {2}Then /.test(body),
|
|
36
|
+
"Gherkin concrete proposal",
|
|
37
|
+
],
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Runtime and repository scope retain the actual configured main mapping. */
|
|
42
|
+
function runtimeChecks(body, config, policy, evidence) {
|
|
43
|
+
return [
|
|
44
|
+
[
|
|
45
|
+
"S8",
|
|
46
|
+
body.includes(
|
|
47
|
+
`Assumption: ${environment(config)} — remote default branch main`
|
|
48
|
+
),
|
|
49
|
+
"runtime target derived",
|
|
50
|
+
],
|
|
51
|
+
[
|
|
52
|
+
"S9",
|
|
53
|
+
body.includes(`Trusted maintainer: ${policy.maintainer}.`),
|
|
54
|
+
"role and runtime credential source",
|
|
55
|
+
],
|
|
56
|
+
[
|
|
57
|
+
"S10",
|
|
58
|
+
body.includes(`## Repository\n\n${policy.repository}\n`),
|
|
59
|
+
"single configured repository",
|
|
60
|
+
],
|
|
61
|
+
["S11", body.includes("## Validation Journey\n"), "terminal journey"],
|
|
62
|
+
[
|
|
63
|
+
"S12",
|
|
64
|
+
["Business rules:", "Visual:", "Flow:", "API/data:"].every(axis =>
|
|
65
|
+
body.includes(axis)
|
|
66
|
+
),
|
|
67
|
+
"four authority axes",
|
|
68
|
+
],
|
|
69
|
+
[
|
|
70
|
+
"S13",
|
|
71
|
+
Boolean(
|
|
72
|
+
evidence.history.command &&
|
|
73
|
+
evidence.history.result &&
|
|
74
|
+
evidence.search.query &&
|
|
75
|
+
Number.isInteger(evidence.search.total)
|
|
76
|
+
),
|
|
77
|
+
"actual history and all-state search",
|
|
78
|
+
],
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Declared evidence and fixed proposal fields must match actual prerequisite reads. */
|
|
83
|
+
function evidenceChecks(draft, body, proposal, expected, config, evidence) {
|
|
84
|
+
return [
|
|
85
|
+
[
|
|
86
|
+
"S14",
|
|
87
|
+
body.includes("[EVIDENCE: cli-output: npm-ci]") &&
|
|
88
|
+
body.includes("[EVIDENCE: cli-output: rejected-publication]"),
|
|
89
|
+
"typed success and error manifest",
|
|
90
|
+
],
|
|
91
|
+
["S15", draft.build_ready === true, "standalone Task without child work"],
|
|
92
|
+
[
|
|
93
|
+
"S18",
|
|
94
|
+
body === expected.body && draft.title === expected.title,
|
|
95
|
+
"complete fixed proposal and stateless recipe",
|
|
96
|
+
],
|
|
97
|
+
[
|
|
98
|
+
"S19",
|
|
99
|
+
body.includes(
|
|
100
|
+
`Derived from: Target Backend Environment ${environment(config)} via .lisa.config.json deploy.branches`
|
|
101
|
+
),
|
|
102
|
+
"actual main mapping",
|
|
103
|
+
],
|
|
104
|
+
["F1", evidence.labels.includes("type:Task"), "Task label exists"],
|
|
105
|
+
[
|
|
106
|
+
"F4",
|
|
107
|
+
expected.labels.every(label => evidence.labels.includes(label)),
|
|
108
|
+
"priority and configured readiness exist",
|
|
109
|
+
],
|
|
110
|
+
[
|
|
111
|
+
"F5",
|
|
112
|
+
evidence.main === proposal.parent &&
|
|
113
|
+
proposal.updates.every(update =>
|
|
114
|
+
evidence.registry.some(
|
|
115
|
+
item => item.name === update.name && item.version === update.to
|
|
116
|
+
)
|
|
117
|
+
),
|
|
118
|
+
"actual base and selected registry versions",
|
|
119
|
+
],
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** These gates validate the bounded Task semantics, independently of API storage. */
|
|
124
|
+
export function qualityGates(draft, proposal, policy, config, evidence) {
|
|
125
|
+
const expected = buildDraft(proposal, policy, config, evidence);
|
|
126
|
+
const body = draft.body ?? "";
|
|
127
|
+
const checks = [
|
|
128
|
+
...coreChecks(draft, body, policy),
|
|
129
|
+
...runtimeChecks(body, config, policy, evidence),
|
|
130
|
+
...evidenceChecks(draft, body, proposal, expected, config, evidence),
|
|
131
|
+
];
|
|
132
|
+
required(
|
|
133
|
+
checks.every(([, passed]) => passed),
|
|
134
|
+
"fixed Task quality gate failed"
|
|
135
|
+
);
|
|
136
|
+
const gates = checks.map(([id, , reason]) => ({
|
|
137
|
+
id,
|
|
138
|
+
verdict: "PASS",
|
|
139
|
+
reason,
|
|
140
|
+
}));
|
|
141
|
+
for (const [id, reason] of [
|
|
142
|
+
["S5", "not Bug"],
|
|
143
|
+
["S6", "not Spike"],
|
|
144
|
+
["S7", "standalone build-ready Task"],
|
|
145
|
+
["S16", "no PRD lineage"],
|
|
146
|
+
["S17", "not Improvement"],
|
|
147
|
+
["S20", "no existing red-before-green control"],
|
|
148
|
+
["F2", "no parent declared"],
|
|
149
|
+
["F3", "no linked issue declared"],
|
|
150
|
+
])
|
|
151
|
+
gates.push({ id, verdict: "N/A", reason });
|
|
152
|
+
return gates;
|
|
153
|
+
}
|