@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,235 @@
|
|
|
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
|
+
/** Trusted allocation retains origin bytes and renews only current recovery authority. */
|
|
5
|
+
import { writeFileSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { canonicalJson } from "../lisa-automation-provenance.mjs";
|
|
8
|
+
import { required, keys, validateProposal } from "./npm-update-contract.mjs";
|
|
9
|
+
import { writeJson, replaceJson } from "./npm-update-process.mjs";
|
|
10
|
+
import { descriptorFor } from "./npm-update-gate.mjs";
|
|
11
|
+
import { authorizeAllocation } from "./npm-update-leaf.mjs";
|
|
12
|
+
export { authorizeAllocation } from "./npm-update-leaf.mjs";
|
|
13
|
+
import { destinationSnapshot } from "./npm-update-publication.mjs";
|
|
14
|
+
export { destinationSnapshot } from "./npm-update-publication.mjs";
|
|
15
|
+
import { persistCheckpoint, recoveryRecord } from "./npm-update-recovery.mjs";
|
|
16
|
+
export { recoveryRecord } from "./npm-update-recovery.mjs";
|
|
17
|
+
import { verifyCurrentProvider } from "./github-attestation-verifier.mjs";
|
|
18
|
+
import { verifyHistoricalProvider } from "./github-attestation-provider.mjs";
|
|
19
|
+
import {
|
|
20
|
+
verifyHistoricalDescriptor,
|
|
21
|
+
predictedCommit,
|
|
22
|
+
} from "./github-attestation-recovery.mjs";
|
|
23
|
+
|
|
24
|
+
/** Serialize only original public filing data, never live operator comments. */
|
|
25
|
+
function publicAllocation(allocation) {
|
|
26
|
+
const fields = [
|
|
27
|
+
"number",
|
|
28
|
+
"workItem",
|
|
29
|
+
"claimCommentId",
|
|
30
|
+
"claimSha256",
|
|
31
|
+
"draft",
|
|
32
|
+
"evidence",
|
|
33
|
+
"quality",
|
|
34
|
+
];
|
|
35
|
+
return Object.fromEntries(fields.map(key => [key, allocation[key]]));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Restored public bytes are recomputed and officially verified before renewal. */
|
|
39
|
+
async function restoredOrigin(
|
|
40
|
+
cwd,
|
|
41
|
+
output,
|
|
42
|
+
proposal,
|
|
43
|
+
allocation,
|
|
44
|
+
policy,
|
|
45
|
+
config
|
|
46
|
+
) {
|
|
47
|
+
const origin = allocation.origin;
|
|
48
|
+
const preview = origin.payload.preview;
|
|
49
|
+
keys(preview, ["descriptor", "message", "epoch"]);
|
|
50
|
+
const recomputed = await descriptorFor({
|
|
51
|
+
cwd,
|
|
52
|
+
proposal,
|
|
53
|
+
policy,
|
|
54
|
+
automation: config.automationProvenance,
|
|
55
|
+
allocation,
|
|
56
|
+
runId: preview.descriptor.runId,
|
|
57
|
+
runAttempt: preview.descriptor.runAttempt,
|
|
58
|
+
epoch: preview.epoch,
|
|
59
|
+
});
|
|
60
|
+
required(
|
|
61
|
+
canonicalJson(preview) === canonicalJson(recomputed),
|
|
62
|
+
"restored original artifact differs"
|
|
63
|
+
);
|
|
64
|
+
const paths = {
|
|
65
|
+
descriptor: join(output, "descriptor.json"),
|
|
66
|
+
bundle: join(output, "bundle.json"),
|
|
67
|
+
};
|
|
68
|
+
writeJson(paths.descriptor, preview.descriptor);
|
|
69
|
+
required(
|
|
70
|
+
Buffer.byteLength(origin.payload.bundle) <= 1_048_576,
|
|
71
|
+
"origin bundle exceeds bound"
|
|
72
|
+
);
|
|
73
|
+
writeFileSync(paths.bundle, origin.payload.bundle, {
|
|
74
|
+
mode: 0o600,
|
|
75
|
+
flag: "wx",
|
|
76
|
+
});
|
|
77
|
+
const historical = verifyHistoricalProvider(
|
|
78
|
+
config.automationProvenance,
|
|
79
|
+
preview.descriptor
|
|
80
|
+
);
|
|
81
|
+
verifyHistoricalDescriptor(
|
|
82
|
+
config.automationProvenance,
|
|
83
|
+
preview.descriptor,
|
|
84
|
+
paths,
|
|
85
|
+
origin.digest,
|
|
86
|
+
historical.run,
|
|
87
|
+
historical.claim
|
|
88
|
+
);
|
|
89
|
+
return preview;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Restore the immutable origin before renewing its separately signed current authority. */
|
|
93
|
+
async function recoveryPreview({
|
|
94
|
+
api,
|
|
95
|
+
cwd,
|
|
96
|
+
output,
|
|
97
|
+
proposal,
|
|
98
|
+
allocation,
|
|
99
|
+
original,
|
|
100
|
+
policy,
|
|
101
|
+
config,
|
|
102
|
+
issue,
|
|
103
|
+
}) {
|
|
104
|
+
const preview = await restoredOrigin(
|
|
105
|
+
cwd,
|
|
106
|
+
output,
|
|
107
|
+
proposal,
|
|
108
|
+
allocation,
|
|
109
|
+
policy,
|
|
110
|
+
config
|
|
111
|
+
);
|
|
112
|
+
if (!allocation.origin.complete) {
|
|
113
|
+
verifyCurrentProvider(api.policy, {
|
|
114
|
+
...preview.descriptor,
|
|
115
|
+
runId: process.env.GITHUB_RUN_ID,
|
|
116
|
+
runAttempt: process.env.GITHUB_RUN_ATTEMPT,
|
|
117
|
+
});
|
|
118
|
+
await persistCheckpoint(
|
|
119
|
+
api,
|
|
120
|
+
original.number,
|
|
121
|
+
allocation.origin.payload,
|
|
122
|
+
allocation.origin.digest,
|
|
123
|
+
() => authorizeAllocation(api, proposal, original, policy, config)
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
const commit = predictedCommit(
|
|
127
|
+
preview.descriptor,
|
|
128
|
+
Buffer.from(preview.message)
|
|
129
|
+
);
|
|
130
|
+
const snapshot = await destinationSnapshot(api, proposal, original, commit);
|
|
131
|
+
writeJson(
|
|
132
|
+
join(output, "recovery.json"),
|
|
133
|
+
recoveryRecord(
|
|
134
|
+
proposal,
|
|
135
|
+
original,
|
|
136
|
+
preview,
|
|
137
|
+
policy,
|
|
138
|
+
config.automationProvenance,
|
|
139
|
+
issue,
|
|
140
|
+
snapshot
|
|
141
|
+
)
|
|
142
|
+
);
|
|
143
|
+
return preview;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Public draft storage precedes the first immutable descriptor and prospective commit. */
|
|
147
|
+
async function originPreview({
|
|
148
|
+
api,
|
|
149
|
+
cwd,
|
|
150
|
+
output,
|
|
151
|
+
proposal,
|
|
152
|
+
original,
|
|
153
|
+
policy,
|
|
154
|
+
config,
|
|
155
|
+
}) {
|
|
156
|
+
await persistCheckpoint(
|
|
157
|
+
api,
|
|
158
|
+
original.number,
|
|
159
|
+
{ version: 1, proposal, allocation: original, preview: null, bundle: "" },
|
|
160
|
+
proposal.key,
|
|
161
|
+
() => authorizeAllocation(api, proposal, original, policy, config)
|
|
162
|
+
);
|
|
163
|
+
const preview = await descriptorFor({
|
|
164
|
+
cwd,
|
|
165
|
+
proposal,
|
|
166
|
+
policy,
|
|
167
|
+
automation: config.automationProvenance,
|
|
168
|
+
allocation: original,
|
|
169
|
+
runId: process.env.GITHUB_RUN_ID,
|
|
170
|
+
runAttempt: process.env.GITHUB_RUN_ATTEMPT,
|
|
171
|
+
epoch: Math.floor(Date.now() / 1000),
|
|
172
|
+
});
|
|
173
|
+
const commit = predictedCommit(
|
|
174
|
+
preview.descriptor,
|
|
175
|
+
Buffer.from(preview.message)
|
|
176
|
+
);
|
|
177
|
+
writeJson(
|
|
178
|
+
join(output, "destination.json"),
|
|
179
|
+
await destinationSnapshot(api, proposal, original, commit)
|
|
180
|
+
);
|
|
181
|
+
writeJson(join(output, "descriptor.json"), preview.descriptor);
|
|
182
|
+
return preview;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Origin creation or recovery preparation occurs only in the trusted allocator. */
|
|
186
|
+
export async function prepareAuthorization({
|
|
187
|
+
api,
|
|
188
|
+
cwd,
|
|
189
|
+
output,
|
|
190
|
+
proposal: suppliedProposal,
|
|
191
|
+
allocation,
|
|
192
|
+
policy,
|
|
193
|
+
config,
|
|
194
|
+
}) {
|
|
195
|
+
let proposal = suppliedProposal;
|
|
196
|
+
if (allocation.status === "stale-outstanding")
|
|
197
|
+
return { mode: allocation.status, allocation };
|
|
198
|
+
const restored =
|
|
199
|
+
allocation.origin?.payload.proposal ??
|
|
200
|
+
allocation.originalProposal ??
|
|
201
|
+
proposal;
|
|
202
|
+
validateProposal(restored, policy);
|
|
203
|
+
if (canonicalJson(restored) !== canonicalJson(proposal))
|
|
204
|
+
replaceJson(join(output, "proposal.json"), restored);
|
|
205
|
+
proposal = restored;
|
|
206
|
+
const original = publicAllocation(allocation);
|
|
207
|
+
const issue = await authorizeAllocation(
|
|
208
|
+
api,
|
|
209
|
+
proposal,
|
|
210
|
+
original,
|
|
211
|
+
policy,
|
|
212
|
+
config
|
|
213
|
+
);
|
|
214
|
+
const inputs = {
|
|
215
|
+
api,
|
|
216
|
+
cwd,
|
|
217
|
+
output,
|
|
218
|
+
proposal,
|
|
219
|
+
allocation,
|
|
220
|
+
original,
|
|
221
|
+
policy,
|
|
222
|
+
config,
|
|
223
|
+
issue,
|
|
224
|
+
};
|
|
225
|
+
const preview = allocation.origin
|
|
226
|
+
? await recoveryPreview(inputs)
|
|
227
|
+
: await originPreview(inputs);
|
|
228
|
+
writeJson(join(output, "preview.json"), preview);
|
|
229
|
+
writeJson(join(output, "allocation.json"), original);
|
|
230
|
+
const mode = allocation.origin ? "recovery" : "origin";
|
|
231
|
+
writeJson(join(output, "authorization.json"), { mode });
|
|
232
|
+
return { mode, allocation: original };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export { finalizeAuthorization } from "./npm-update-recovery.mjs";
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// This file is managed by Lisa and IS replaced on each `lisa` run.
|
|
2
|
+
// Do not edit directly — durable changes belong upstream in Lisa.
|
|
3
|
+
|
|
4
|
+
/** Bounded native socket transport supplies a fixed recipe ID, never executable authority or credentials. */
|
|
5
|
+
import { createConnection } from "node:net";
|
|
6
|
+
import { lstatSync, realpathSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { required, keys } from "./npm-update-contract.mjs";
|
|
9
|
+
|
|
10
|
+
function brokerPath(context, recipe) {
|
|
11
|
+
keys(context, ["root", "path", "deadline"]);
|
|
12
|
+
const root = lstatSync(context.root);
|
|
13
|
+
const socket = lstatSync(context.path);
|
|
14
|
+
required(
|
|
15
|
+
root.isDirectory() &&
|
|
16
|
+
!root.isSymbolicLink() &&
|
|
17
|
+
realpathSync(context.root) === context.root &&
|
|
18
|
+
root.uid === process.getuid() &&
|
|
19
|
+
(root.mode & 0o077) === 0 &&
|
|
20
|
+
context.path === join(context.root, "controller.sock") &&
|
|
21
|
+
socket.isSocket() &&
|
|
22
|
+
socket.uid === process.getuid() &&
|
|
23
|
+
(socket.mode & 0o077) === 0,
|
|
24
|
+
"broker pathname ownership differs"
|
|
25
|
+
);
|
|
26
|
+
required(
|
|
27
|
+
typeof recipe === "string" &&
|
|
28
|
+
/^[a-zA-Z0-9-]{1,128}$/.test(recipe) &&
|
|
29
|
+
Number.isSafeInteger(context.deadline) &&
|
|
30
|
+
context.deadline > Date.now() &&
|
|
31
|
+
context.deadline <= Date.now() + 1_800_000,
|
|
32
|
+
"invalid broker request or lifetime"
|
|
33
|
+
);
|
|
34
|
+
return context.path;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function nativeResult(bytes) {
|
|
38
|
+
const text = new TextDecoder("utf8", { fatal: true }).decode(bytes);
|
|
39
|
+
const result = JSON.parse(text);
|
|
40
|
+
required(text === `${JSON.stringify(result)}\n`, "ambiguous broker response");
|
|
41
|
+
required(result.ok === true, "controller request refused");
|
|
42
|
+
keys(result, ["ok", "code", "stdout", "stderr"]);
|
|
43
|
+
required(
|
|
44
|
+
Number.isInteger(result.code) &&
|
|
45
|
+
result.code >= 0 &&
|
|
46
|
+
result.code <= 255 &&
|
|
47
|
+
[result.stdout, result.stderr].every(
|
|
48
|
+
value =>
|
|
49
|
+
typeof value === "string" && /^[A-Za-z0-9+/]*={0,2}$/.test(value)
|
|
50
|
+
),
|
|
51
|
+
"invalid broker native result"
|
|
52
|
+
);
|
|
53
|
+
const stdout = Buffer.from(result.stdout, "base64");
|
|
54
|
+
const stderr = Buffer.from(result.stderr, "base64");
|
|
55
|
+
required(
|
|
56
|
+
stdout.toString("base64") === result.stdout &&
|
|
57
|
+
stderr.toString("base64") === result.stderr &&
|
|
58
|
+
stdout.length + stderr.length <= 8_388_608,
|
|
59
|
+
"broker capture differs or exceeds bound"
|
|
60
|
+
);
|
|
61
|
+
return { code: result.code, stdout, stderr };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The server's immutable recipe owns full stdin and all subprocess options. */
|
|
65
|
+
export async function invokeControllerRecipe(context, recipe) {
|
|
66
|
+
const path = brokerPath(context, recipe);
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
const socket = createConnection(path);
|
|
69
|
+
const chunks = [];
|
|
70
|
+
let size = 0;
|
|
71
|
+
const timer = setTimeout(
|
|
72
|
+
() => socket.destroy(new Error("controller phase expired")),
|
|
73
|
+
context.deadline - Date.now()
|
|
74
|
+
);
|
|
75
|
+
socket.on("connect", () => socket.end(`${JSON.stringify({ recipe })}\n`));
|
|
76
|
+
socket.on("data", chunk => {
|
|
77
|
+
size += chunk.length;
|
|
78
|
+
if (size > 11_185_000)
|
|
79
|
+
socket.destroy(new Error("broker response exceeds capture bound"));
|
|
80
|
+
else chunks.push(chunk);
|
|
81
|
+
});
|
|
82
|
+
socket.on("error", reject);
|
|
83
|
+
socket.on("close", () => clearTimeout(timer));
|
|
84
|
+
socket.on("end", () => {
|
|
85
|
+
try {
|
|
86
|
+
resolve(nativeResult(Buffer.concat(chunks)));
|
|
87
|
+
} catch (error) {
|
|
88
|
+
reject(error);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
@@ -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
|
+
/** Bounded public checkpoint storage is data only, never provenance authority. */
|
|
5
|
+
import { TextDecoder } from "node:util";
|
|
6
|
+
import { canonicalJson } from "../lisa-automation-provenance.mjs";
|
|
7
|
+
import { keys, required } from "./npm-update-contract.mjs";
|
|
8
|
+
import { sha256 } from "./github-attestation-verifier.mjs";
|
|
9
|
+
|
|
10
|
+
const MAXIMUM = 4_194_304;
|
|
11
|
+
const CHUNK = 16_384;
|
|
12
|
+
const PREFIX = "[lisa-npm-checkpoint]";
|
|
13
|
+
const HEX = /^[a-f0-9]{64}$/;
|
|
14
|
+
|
|
15
|
+
/** Closed public payloads contain no bearer token or executable extension field. */
|
|
16
|
+
function payloadBytes(payload) {
|
|
17
|
+
keys(payload, ["version", "proposal", "allocation", "preview", "bundle"]);
|
|
18
|
+
required(
|
|
19
|
+
payload.version === 1 && typeof payload.bundle === "string",
|
|
20
|
+
"invalid public checkpoint payload"
|
|
21
|
+
);
|
|
22
|
+
const bytes = Buffer.from(`${canonicalJson(payload)}\n`);
|
|
23
|
+
required(bytes.length <= MAXIMUM, "checkpoint payload exceeds bound");
|
|
24
|
+
required(
|
|
25
|
+
!/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/.test(
|
|
26
|
+
payload.bundle
|
|
27
|
+
),
|
|
28
|
+
"bearer JWT cannot be retained"
|
|
29
|
+
);
|
|
30
|
+
return bytes;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Numbered digest-checked chunks precede one exact completion manifest. */
|
|
34
|
+
export function checkpointComments(payload, originDigest) {
|
|
35
|
+
required(
|
|
36
|
+
typeof originDigest === "string" && HEX.test(originDigest),
|
|
37
|
+
"invalid checkpoint origin"
|
|
38
|
+
);
|
|
39
|
+
const bytes = payloadBytes(payload);
|
|
40
|
+
const count = Math.ceil(bytes.length / CHUNK);
|
|
41
|
+
required(count > 0 && count <= 256, "checkpoint chunk count exceeds bound");
|
|
42
|
+
const chunks = Array.from({ length: count }, (_, index) =>
|
|
43
|
+
bytes.subarray(index * CHUNK, (index + 1) * CHUNK)
|
|
44
|
+
);
|
|
45
|
+
const manifest = {
|
|
46
|
+
version: 1,
|
|
47
|
+
originDescriptorSha256: originDigest,
|
|
48
|
+
payloadSha256: sha256(bytes),
|
|
49
|
+
bytes: bytes.length,
|
|
50
|
+
chunks: chunks.map((chunk, index) => ({ index, sha256: sha256(chunk) })),
|
|
51
|
+
};
|
|
52
|
+
return [
|
|
53
|
+
...chunks.map(
|
|
54
|
+
(chunk, index) =>
|
|
55
|
+
`${PREFIX} v1 ${originDigest} chunk ${index}/${count} ${sha256(chunk)}\n${chunk.toString("base64")}`
|
|
56
|
+
),
|
|
57
|
+
`${PREFIX} v1 ${originDigest} complete\n${canonicalJson(manifest)}`,
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** A strict marker locates data only; signatures are still required separately. */
|
|
62
|
+
function relevant(comments, digest) {
|
|
63
|
+
required(
|
|
64
|
+
Array.isArray(comments) && comments.length <= 2000,
|
|
65
|
+
"checkpoint comment inventory exceeds bound"
|
|
66
|
+
);
|
|
67
|
+
return comments
|
|
68
|
+
.filter(
|
|
69
|
+
comment =>
|
|
70
|
+
typeof comment.body === "string" &&
|
|
71
|
+
comment.body.startsWith(`${PREFIX} v1 ${digest} `)
|
|
72
|
+
)
|
|
73
|
+
.map(comment => {
|
|
74
|
+
required(
|
|
75
|
+
Buffer.byteLength(comment.body) <= 24_576,
|
|
76
|
+
"checkpoint comment exceeds bound"
|
|
77
|
+
);
|
|
78
|
+
return comment.body;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The unique completion record independently bounds the declared aggregate. */
|
|
83
|
+
function completionManifest(body, digest) {
|
|
84
|
+
const text = body.split("\n").slice(1).join("\n");
|
|
85
|
+
const manifest = JSON.parse(text);
|
|
86
|
+
keys(manifest, [
|
|
87
|
+
"version",
|
|
88
|
+
"originDescriptorSha256",
|
|
89
|
+
"payloadSha256",
|
|
90
|
+
"bytes",
|
|
91
|
+
"chunks",
|
|
92
|
+
]);
|
|
93
|
+
required(
|
|
94
|
+
canonicalJson(manifest) === text &&
|
|
95
|
+
manifest.version === 1 &&
|
|
96
|
+
manifest.originDescriptorSha256 === digest &&
|
|
97
|
+
HEX.test(manifest.payloadSha256),
|
|
98
|
+
"checkpoint manifest differs"
|
|
99
|
+
);
|
|
100
|
+
required(
|
|
101
|
+
Number.isSafeInteger(manifest.bytes) &&
|
|
102
|
+
manifest.bytes > 0 &&
|
|
103
|
+
manifest.bytes <= MAXIMUM &&
|
|
104
|
+
Array.isArray(manifest.chunks) &&
|
|
105
|
+
manifest.chunks.length > 0 &&
|
|
106
|
+
manifest.chunks.length <= 256,
|
|
107
|
+
"checkpoint manifest bounds differ"
|
|
108
|
+
);
|
|
109
|
+
return manifest;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Each numbered chunk is canonical, bounded and byte-bound before aggregation. */
|
|
113
|
+
function decodedChunks(bodies, digest, manifest) {
|
|
114
|
+
const chunks = new Map();
|
|
115
|
+
for (const body of bodies) {
|
|
116
|
+
const match =
|
|
117
|
+
/^\[lisa-npm-checkpoint\] v1 ([a-f0-9]{64}) chunk (0|[1-9]\d*)\/([1-9]\d*) ([a-f0-9]{64})\n([A-Za-z0-9+/]+=*)$/.exec(
|
|
118
|
+
body
|
|
119
|
+
);
|
|
120
|
+
required(
|
|
121
|
+
match &&
|
|
122
|
+
match[1] === digest &&
|
|
123
|
+
Number(match[3]) === manifest.chunks.length,
|
|
124
|
+
"checkpoint chunk shape differs"
|
|
125
|
+
);
|
|
126
|
+
const index = Number(match[2]);
|
|
127
|
+
const bytes = Buffer.from(match[5], "base64");
|
|
128
|
+
required(
|
|
129
|
+
index < manifest.chunks.length &&
|
|
130
|
+
bytes.length <= CHUNK &&
|
|
131
|
+
bytes.toString("base64") === match[5] &&
|
|
132
|
+
sha256(bytes) === match[4],
|
|
133
|
+
"checkpoint chunk bytes differ"
|
|
134
|
+
);
|
|
135
|
+
required(
|
|
136
|
+
!chunks.has(index) || chunks.get(index).equals(bytes),
|
|
137
|
+
"differing duplicate checkpoint chunk"
|
|
138
|
+
);
|
|
139
|
+
chunks.set(index, bytes);
|
|
140
|
+
}
|
|
141
|
+
return chunks;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Decode bounded canonical public storage; this function never authorizes recovery. */
|
|
145
|
+
export function decodeCheckpoint(comments, digest) {
|
|
146
|
+
required(
|
|
147
|
+
typeof digest === "string" && HEX.test(digest),
|
|
148
|
+
"invalid checkpoint digest"
|
|
149
|
+
);
|
|
150
|
+
const bodies = relevant(comments, digest);
|
|
151
|
+
if (!bodies.length) return undefined;
|
|
152
|
+
const complete = bodies.filter(body =>
|
|
153
|
+
body.startsWith(`${PREFIX} v1 ${digest} complete\n`)
|
|
154
|
+
);
|
|
155
|
+
required(complete.length === 1, "checkpoint incomplete or ambiguous");
|
|
156
|
+
const manifest = completionManifest(complete[0], digest);
|
|
157
|
+
const chunks = decodedChunks(
|
|
158
|
+
bodies.filter(body => body !== complete[0]),
|
|
159
|
+
digest,
|
|
160
|
+
manifest
|
|
161
|
+
);
|
|
162
|
+
const ordered = manifest.chunks.map((entry, index) => {
|
|
163
|
+
keys(entry, ["index", "sha256"]);
|
|
164
|
+
const chunk = chunks.get(index);
|
|
165
|
+
required(
|
|
166
|
+
entry.index === index && chunk && sha256(chunk) === entry.sha256,
|
|
167
|
+
"checkpoint missing or reordered chunk"
|
|
168
|
+
);
|
|
169
|
+
return chunk;
|
|
170
|
+
});
|
|
171
|
+
const bytes = Buffer.concat(ordered);
|
|
172
|
+
required(
|
|
173
|
+
bytes.length === manifest.bytes && sha256(bytes) === manifest.payloadSha256,
|
|
174
|
+
"checkpoint aggregate differs"
|
|
175
|
+
);
|
|
176
|
+
const payload = JSON.parse(
|
|
177
|
+
new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes)
|
|
178
|
+
);
|
|
179
|
+
required(
|
|
180
|
+
payloadBytes(payload).equals(bytes),
|
|
181
|
+
"checkpoint is noncanonical or duplicate-keyed"
|
|
182
|
+
);
|
|
183
|
+
return payload;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Readback before completion prevents a stored marker blessing missing bytes. */
|
|
187
|
+
export async function persistCheckpoint(
|
|
188
|
+
api,
|
|
189
|
+
number,
|
|
190
|
+
payload,
|
|
191
|
+
digest,
|
|
192
|
+
authorize
|
|
193
|
+
) {
|
|
194
|
+
const bodies = checkpointComments(payload, digest);
|
|
195
|
+
const endpoint = `repos/${api.policy.repository}/issues/${number}/comments`;
|
|
196
|
+
const issueUrl = `https://api.github.com/repos/${api.policy.repository}/issues/${number}`;
|
|
197
|
+
for (const body of bodies) {
|
|
198
|
+
await authorize();
|
|
199
|
+
const existing = await api.list(endpoint);
|
|
200
|
+
const matches = existing.filter(comment => comment.body === body);
|
|
201
|
+
if (body.startsWith(`${PREFIX} v1 ${digest} complete\n`)) {
|
|
202
|
+
const projected = matches.length ? existing : [...existing, { body }];
|
|
203
|
+
required(
|
|
204
|
+
canonicalJson(decodeCheckpoint(projected, digest)) ===
|
|
205
|
+
canonicalJson(payload),
|
|
206
|
+
"checkpoint chunks must read back before completion"
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
required(matches.length <= 1, "duplicate checkpoint write state");
|
|
210
|
+
if (!matches.length) {
|
|
211
|
+
const created = await api.request(endpoint, "POST", { body });
|
|
212
|
+
required(
|
|
213
|
+
created.issue_url === issueUrl,
|
|
214
|
+
"checkpoint write scope differs"
|
|
215
|
+
);
|
|
216
|
+
required(
|
|
217
|
+
created.body === body &&
|
|
218
|
+
Number.isSafeInteger(created.id) &&
|
|
219
|
+
created.id > 0,
|
|
220
|
+
"checkpoint write readback differs"
|
|
221
|
+
);
|
|
222
|
+
const read = await api.request(
|
|
223
|
+
`repos/${api.policy.repository}/issues/comments/${created.id}`
|
|
224
|
+
);
|
|
225
|
+
required(
|
|
226
|
+
read.issue_url === issueUrl,
|
|
227
|
+
"checkpoint immediate readback scope differs"
|
|
228
|
+
);
|
|
229
|
+
required(
|
|
230
|
+
read.id === created.id && read.body === body,
|
|
231
|
+
"checkpoint immediate readback differs"
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const restored = decodeCheckpoint(await api.list(endpoint), digest);
|
|
236
|
+
required(
|
|
237
|
+
canonicalJson(restored) === canonicalJson(payload),
|
|
238
|
+
"durable complete checkpoint differs"
|
|
239
|
+
);
|
|
240
|
+
return { originDescriptorSha256: digest, complete: true };
|
|
241
|
+
}
|