@codyswann/lisa 4.72.6 → 4.73.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.
- package/all/copy-overwrite/scripts/lib/automation-provenance-contract.mjs +19 -0
- package/all/copy-overwrite/scripts/lib/automation-provenance-local.mjs +4 -2
- package/all/copy-overwrite/scripts/lib/history-secret-evidence-shape.mjs +398 -6
- package/all/copy-overwrite/scripts/lib/history-secret-evidence.mjs +175 -7
- package/all/copy-overwrite/scripts/lib/npm-update-cancel-origin.mjs +7 -1
- package/all/copy-overwrite/scripts/lib/npm-update-contract.mjs +11 -66
- package/all/copy-overwrite/scripts/lib/npm-update-gate.mjs +3 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper-inventory.mjs +10 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hosted-gate.mjs +162 -121
- package/all/copy-overwrite/scripts/lib/npm-update-hosted-hook.mjs +8 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hosted-scope.mjs +177 -0
- package/all/copy-overwrite/scripts/lib/npm-update-policy.mjs +73 -0
- package/all/copy-overwrite/scripts/lib/npm-update-publish.mjs +1 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-mysql-daemon.mjs +227 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-mysql-resource.mjs +176 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-mysql-storage.mjs +201 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-mysql.mjs +220 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-runtime-contract.mjs +75 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-tool-downloads.mjs +187 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-tool-identity.mjs +288 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-tools.mjs +292 -0
- package/all/copy-overwrite/scripts/lisa-automation-provenance.mjs +3 -0
- package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +1 -1
- package/all/copy-overwrite/scripts/lisa-scratch-run.sh +7 -4
- package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +148 -20
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +126 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/lisa.d.ts.map +1 -1
- package/dist/core/lisa.js +2 -1
- package/dist/core/lisa.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 +79 -17
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/parity-safety-net.sh +1 -1
- 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/parity-safety-net.sh +1 -1
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +1 -1
- 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/parity-safety-net.sh +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +1 -1
- 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/parity-safety-net.sh +1 -1
- package/scripts/two-channel-couplings.json +1 -1
|
@@ -2,24 +2,30 @@
|
|
|
2
2
|
// Do not edit directly — durable changes belong upstream in Lisa.
|
|
3
3
|
|
|
4
4
|
/** Supported disposable Actions Linux gates use original native tools; publisher authority stays in another job. */
|
|
5
|
-
import { existsSync, readFileSync
|
|
6
|
-
import { join
|
|
7
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
8
7
|
import { required, proposalFileNames } from "./npm-update-contract.mjs";
|
|
9
8
|
import { qualifiedBun, frozenBun } from "./npm-update-bun.mjs";
|
|
10
|
-
import { runProcess
|
|
9
|
+
import { runProcess } from "./npm-update-process.mjs";
|
|
11
10
|
import { qualifiedControllerGraph } from "./npm-update-helper.mjs";
|
|
11
|
+
import { controllerTools } from "./npm-update-controller-factory.mjs";
|
|
12
12
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from "./npm-update-
|
|
16
|
-
import {
|
|
13
|
+
openHostedReadScope,
|
|
14
|
+
authenticateHostedRuntime,
|
|
15
|
+
} from "./npm-update-hosted-scope.mjs";
|
|
16
|
+
import { affirmRuntimeProfile } from "./npm-update-rails-runtime-contract.mjs";
|
|
17
|
+
import { prepareRailsTools } from "./npm-update-rails-tools.mjs";
|
|
18
|
+
import { openRailsMysqlRuntime } from "./npm-update-rails-mysql.mjs";
|
|
17
19
|
import { installOriginalManager } from "./npm-update-hook-installation.mjs";
|
|
18
20
|
import { withStage } from "./npm-update-invariants.mjs";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
|
|
22
|
+
const INSTALL_STAGE = "gate-install";
|
|
23
|
+
const VALIDATE_STAGE = "gate-validate";
|
|
24
|
+
|
|
25
|
+
/** Only prepared browser hooks use a short supervisor base; candidate HOME and TMPDIR stay private. */
|
|
26
|
+
export function originalHookEnvironment(env, runtime) {
|
|
27
|
+
return runtime?.browser ? { ...env, LISA_SCRATCH_BASE: "/tmp" } : env;
|
|
28
|
+
}
|
|
23
29
|
|
|
24
30
|
/** This diagnoses the supported caller; attestation/provider validation independently establishes actual authority. */
|
|
25
31
|
export function assertHostedGate(
|
|
@@ -111,88 +117,113 @@ async function installHostedDependencies(context, root, env) {
|
|
|
111
117
|
return installed;
|
|
112
118
|
}
|
|
113
119
|
|
|
114
|
-
/**
|
|
115
|
-
async function
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
/** Authenticate before tools; keep frozen application setup separate from hook/tool authority. */
|
|
121
|
+
export async function prepareRailsApplication(
|
|
122
|
+
context,
|
|
123
|
+
root,
|
|
124
|
+
env,
|
|
125
|
+
scope,
|
|
126
|
+
profile
|
|
127
|
+
) {
|
|
128
|
+
await withStage(VALIDATE_STAGE, () =>
|
|
129
|
+
authenticateHostedRuntime(context, root, scope, nativeStep)
|
|
124
130
|
);
|
|
125
|
-
const
|
|
126
|
-
context
|
|
127
|
-
text.slice(0, -1),
|
|
128
|
-
"lisa/automation-provenance"
|
|
131
|
+
const tools = await withStage("gate-tools", () =>
|
|
132
|
+
prepareRailsTools(context, root, env, profile)
|
|
129
133
|
);
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
file: join(control, file),
|
|
133
|
-
bundle: join(control, bundle),
|
|
134
|
-
predicate,
|
|
135
|
-
signerWorkflow: policy.signerWorkflow,
|
|
136
|
-
signerDigest: policy.signerDigest,
|
|
137
|
-
});
|
|
138
|
-
const proofs = [record("descriptor.json", "bundle.json", PROPOSAL_PREDICATE)];
|
|
139
|
-
if (context.recovery !== undefined)
|
|
140
|
-
proofs.push(
|
|
141
|
-
record("recovery.json", "recovery-bundle.json", RECOVERY_PREDICATE)
|
|
142
|
-
);
|
|
143
|
-
const recoveryDescriptor =
|
|
144
|
-
context.recovery === undefined
|
|
145
|
-
? undefined
|
|
146
|
-
: JSON.parse(context.recovery.toString());
|
|
147
|
-
return { proofs, recoveryDescriptor };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/** PATH preserves original native commands; the sole Node gateway carries no provider token. */
|
|
151
|
-
function nodeGateway(root, context) {
|
|
152
|
-
const bin = join(root, "hook-bin");
|
|
153
|
-
mkdirSync(bin, { mode: 0o700 });
|
|
154
|
-
const entry = fileURLToPath(
|
|
155
|
-
new URL("./npm-update-hosted-hook.mjs", import.meta.url)
|
|
134
|
+
const installed = await withStage(INSTALL_STAGE, () =>
|
|
135
|
+
installHostedDependencies(context, root, tools.env)
|
|
156
136
|
);
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
137
|
+
const application = Object.fromEntries(
|
|
138
|
+
[
|
|
139
|
+
"PATH",
|
|
140
|
+
"HOME",
|
|
141
|
+
"LANG",
|
|
142
|
+
"LC_ALL",
|
|
143
|
+
"TZ",
|
|
144
|
+
"TMPDIR",
|
|
145
|
+
"BUNDLE_PATH",
|
|
146
|
+
"BUNDLE_APP_CONFIG",
|
|
147
|
+
"BUNDLE_FROZEN",
|
|
148
|
+
"BUNDLE_SILENCE_ROOT_WARNING",
|
|
149
|
+
"BUNDLER_VERSION",
|
|
150
|
+
]
|
|
151
|
+
.filter(name => installed[name] !== undefined)
|
|
152
|
+
.map(name => [name, installed[name]])
|
|
167
153
|
);
|
|
168
|
-
|
|
154
|
+
const runtime = await withStage(INSTALL_STAGE, () =>
|
|
155
|
+
openRailsMysqlRuntime(
|
|
156
|
+
{
|
|
157
|
+
cwd: context.cwd,
|
|
158
|
+
root,
|
|
159
|
+
deadline: context.deadline,
|
|
160
|
+
profile,
|
|
161
|
+
docker: tools.docker,
|
|
162
|
+
},
|
|
163
|
+
application
|
|
164
|
+
)
|
|
165
|
+
);
|
|
166
|
+
try {
|
|
167
|
+
await withStage(INSTALL_STAGE, () => runtime.prepareSchemas());
|
|
168
|
+
const databases = Object.fromEntries(
|
|
169
|
+
[
|
|
170
|
+
"DATABASE_NAME",
|
|
171
|
+
"DATABASE_USER",
|
|
172
|
+
"DATABASE_PASSWORD",
|
|
173
|
+
"DATABASE_PORT",
|
|
174
|
+
"PRIMARY_DB_HOST",
|
|
175
|
+
"DATABASE_REPLICA_HOST",
|
|
176
|
+
"DATABASE_SSL",
|
|
177
|
+
"DATABASE_IAM_AUTH",
|
|
178
|
+
"RAILS_ENV",
|
|
179
|
+
"RACK_ENV",
|
|
180
|
+
].map(name => [name, runtime.env[name]])
|
|
181
|
+
);
|
|
182
|
+
required(
|
|
183
|
+
Object.values(databases).every(value => typeof value === "string"),
|
|
184
|
+
"prepared database environment differs"
|
|
185
|
+
);
|
|
186
|
+
return { env: { ...installed, ...databases }, close: runtime.close };
|
|
187
|
+
} catch (error) {
|
|
188
|
+
try {
|
|
189
|
+
await runtime.close();
|
|
190
|
+
} catch (cleanup) {
|
|
191
|
+
throw new AggregateError(
|
|
192
|
+
[error, cleanup],
|
|
193
|
+
"hosted schema preparation and cleanup failed",
|
|
194
|
+
{ cause: error }
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
169
199
|
}
|
|
170
200
|
|
|
171
|
-
/**
|
|
172
|
-
function
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
{ ...context, recoveryDescriptor: scope.recoveryDescriptor },
|
|
186
|
-
"hook-read",
|
|
187
|
-
null,
|
|
188
|
-
scope.proofs
|
|
189
|
-
),
|
|
190
|
-
};
|
|
201
|
+
/** Attempt every owned close within its original deadline, preserving all actual failures. */
|
|
202
|
+
async function closeHostedResources(...resources) {
|
|
203
|
+
const errors = [];
|
|
204
|
+
for (const resource of resources) {
|
|
205
|
+
if (!resource) continue;
|
|
206
|
+
try {
|
|
207
|
+
await resource.close();
|
|
208
|
+
} catch (error) {
|
|
209
|
+
errors.push(error);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (errors.length === 1) throw errors[0];
|
|
213
|
+
if (errors.length)
|
|
214
|
+
throw new AggregateError(errors, "hosted resources cleanup failed");
|
|
191
215
|
}
|
|
192
216
|
|
|
193
217
|
/** A separate read-only capability is available during original gates; it cannot acquire publisher/issuer permissions. */
|
|
194
218
|
export async function createHostedGate(context, root, env) {
|
|
195
|
-
await withStage(
|
|
219
|
+
await withStage(VALIDATE_STAGE, () => assertHostedGate(context));
|
|
220
|
+
const runtime = await withStage(VALIDATE_STAGE, () =>
|
|
221
|
+
affirmRuntimeProfile(
|
|
222
|
+
context.proposal,
|
|
223
|
+
context.policy,
|
|
224
|
+
process.env.LISA_NPM_RUNTIME_PROFILE ?? "none"
|
|
225
|
+
)
|
|
226
|
+
);
|
|
196
227
|
const native = await withStage("gate-tools", () =>
|
|
197
228
|
controllerTools(context.config.automationProvenance)
|
|
198
229
|
);
|
|
@@ -205,51 +236,61 @@ export async function createHostedGate(context, root, env) {
|
|
|
205
236
|
"lib/npm-update-hook-preload.mjs",
|
|
206
237
|
])
|
|
207
238
|
);
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
);
|
|
211
|
-
const installation = await withStage("gate-hooks", () =>
|
|
212
|
-
installOriginalManager(context, installed, nativeStep)
|
|
213
|
-
);
|
|
214
|
-
const scope = await withStage("gate-scope", () =>
|
|
215
|
-
proofScope(context, installed)
|
|
216
|
-
);
|
|
217
|
-
const profile = await withStage("gate-scope", () => {
|
|
218
|
-
mkdirSync(join(root, "gh-config"), { mode: 0o700 });
|
|
219
|
-
return hookProfile(context, root, native, scope);
|
|
220
|
-
});
|
|
221
|
-
const broker = await withStage("gate-broker", () =>
|
|
222
|
-
startHookReadBroker(profile, context.token)
|
|
223
|
-
);
|
|
239
|
+
let scope;
|
|
240
|
+
let application;
|
|
224
241
|
try {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
242
|
+
if (runtime) {
|
|
243
|
+
scope = await openHostedReadScope(
|
|
244
|
+
context,
|
|
228
245
|
root,
|
|
229
|
-
|
|
230
|
-
graph,
|
|
246
|
+
env,
|
|
231
247
|
native,
|
|
232
|
-
|
|
233
|
-
|
|
248
|
+
graph,
|
|
249
|
+
nativeStep
|
|
250
|
+
);
|
|
251
|
+
application = await prepareRailsApplication(
|
|
252
|
+
context,
|
|
253
|
+
root,
|
|
254
|
+
env,
|
|
255
|
+
scope,
|
|
256
|
+
runtime
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
const installed =
|
|
260
|
+
application?.env ??
|
|
261
|
+
(await withStage(INSTALL_STAGE, () =>
|
|
262
|
+
installHostedDependencies(context, root, env)
|
|
263
|
+
));
|
|
264
|
+
const installation = await withStage("gate-hooks", () =>
|
|
265
|
+
installOriginalManager(context, installed, nativeStep)
|
|
266
|
+
);
|
|
267
|
+
scope ??= await openHostedReadScope(
|
|
268
|
+
context,
|
|
269
|
+
root,
|
|
270
|
+
installed,
|
|
271
|
+
native,
|
|
272
|
+
graph,
|
|
273
|
+
nativeStep
|
|
234
274
|
);
|
|
235
275
|
return {
|
|
236
276
|
installation,
|
|
237
|
-
env:
|
|
238
|
-
|
|
239
|
-
PATH: `${bin}:${installed.PATH}`,
|
|
240
|
-
CI: "true",
|
|
241
|
-
GITHUB_ACTIONS: "true",
|
|
242
|
-
GITHUB_REPOSITORY: context.policy.repository,
|
|
243
|
-
GITHUB_RUN_ID: process.env.GITHUB_RUN_ID,
|
|
244
|
-
GITHUB_RUN_ATTEMPT: process.env.GITHUB_RUN_ATTEMPT,
|
|
245
|
-
GITHUB_SHA: context.proposal.parent,
|
|
246
|
-
GITHUB_REF: "refs/heads/main",
|
|
247
|
-
GITHUB_EVENT_NAME: process.env.GITHUB_EVENT_NAME,
|
|
248
|
-
},
|
|
249
|
-
close: broker.close,
|
|
277
|
+
env: originalHookEnvironment(scope.environment(installed), runtime),
|
|
278
|
+
close: () => closeHostedResources(application, scope),
|
|
250
279
|
};
|
|
251
280
|
} catch (error) {
|
|
252
|
-
|
|
281
|
+
if (scope) {
|
|
282
|
+
try {
|
|
283
|
+
await withStage("gate-close", () =>
|
|
284
|
+
closeHostedResources(application, scope)
|
|
285
|
+
);
|
|
286
|
+
} catch (cleanup) {
|
|
287
|
+
throw new AggregateError(
|
|
288
|
+
[error, cleanup],
|
|
289
|
+
"hosted preparation and scope cleanup failed",
|
|
290
|
+
{ cause: error }
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
253
294
|
throw error;
|
|
254
295
|
}
|
|
255
296
|
}
|
|
@@ -83,6 +83,14 @@ export function hookReadSlot(entry, args, role) {
|
|
|
83
83
|
return `${role}-work-item`;
|
|
84
84
|
}
|
|
85
85
|
if (name === "lisa-automation-provenance.mjs") {
|
|
86
|
+
if (
|
|
87
|
+
role === "commit" &&
|
|
88
|
+
args.length === 2 &&
|
|
89
|
+
typeof args[1] === "string" &&
|
|
90
|
+
args[1].length > 0 &&
|
|
91
|
+
!args[1].startsWith("-")
|
|
92
|
+
)
|
|
93
|
+
return "commit-provenance";
|
|
86
94
|
if (args[1] === "check-commit") return "commit-provenance";
|
|
87
95
|
if (args[1] === "check-push" && ["audit", "destination"].includes(role))
|
|
88
96
|
return `${role}-provenance`;
|
|
@@ -0,0 +1,177 @@
|
|
|
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
|
+
/** Early signed verification uses the original read-only broker, never application credentials. */
|
|
5
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { join, resolve } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { required } from "./npm-update-contract.mjs";
|
|
9
|
+
import { withStage } from "./npm-update-invariants.mjs";
|
|
10
|
+
import { writeJson } from "./npm-update-process.mjs";
|
|
11
|
+
import { controllerSubject } from "./npm-update-controller-factory.mjs";
|
|
12
|
+
import { startHookReadBroker } from "./npm-update-hook-provider.mjs";
|
|
13
|
+
import {
|
|
14
|
+
PROPOSAL_PREDICATE,
|
|
15
|
+
RECOVERY_PREDICATE,
|
|
16
|
+
} from "./github-attestation-verifier.mjs";
|
|
17
|
+
|
|
18
|
+
/** Proof paths remain the fixed slots installed for the unchanged canonical verifier. */
|
|
19
|
+
async function proofScope(context, env, step) {
|
|
20
|
+
const result = await step(context, env, "/usr/bin/git", [
|
|
21
|
+
"rev-parse",
|
|
22
|
+
"--git-common-dir",
|
|
23
|
+
]);
|
|
24
|
+
const text = result.stdout.toString();
|
|
25
|
+
required(
|
|
26
|
+
text.endsWith("\n") && !/[\n\r\0]/.test(text.slice(0, -1)),
|
|
27
|
+
"original Git control path is unsupported"
|
|
28
|
+
);
|
|
29
|
+
const control = resolve(
|
|
30
|
+
context.cwd,
|
|
31
|
+
text.slice(0, -1),
|
|
32
|
+
"lisa/automation-provenance"
|
|
33
|
+
);
|
|
34
|
+
const policy = context.config.automationProvenance;
|
|
35
|
+
const record = (file, bundle, predicate) => ({
|
|
36
|
+
file: join(control, file),
|
|
37
|
+
bundle: join(control, bundle),
|
|
38
|
+
predicate,
|
|
39
|
+
signerWorkflow: policy.signerWorkflow,
|
|
40
|
+
signerDigest: policy.signerDigest,
|
|
41
|
+
});
|
|
42
|
+
const proofs = [record("descriptor.json", "bundle.json", PROPOSAL_PREDICATE)];
|
|
43
|
+
if (context.recovery !== undefined)
|
|
44
|
+
proofs.push(
|
|
45
|
+
record("recovery.json", "recovery-bundle.json", RECOVERY_PREDICATE)
|
|
46
|
+
);
|
|
47
|
+
const recoveryDescriptor =
|
|
48
|
+
context.recovery === undefined
|
|
49
|
+
? undefined
|
|
50
|
+
: JSON.parse(context.recovery.toString());
|
|
51
|
+
return { proofs, recoveryDescriptor };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** PATH preserves original commands; the fixed Node gateway carries no provider token. */
|
|
55
|
+
function nodeGateway(root, context) {
|
|
56
|
+
const bin = join(root, "hook-bin");
|
|
57
|
+
mkdirSync(bin, { mode: 0o700 });
|
|
58
|
+
const entry = fileURLToPath(
|
|
59
|
+
new URL("./npm-update-hosted-hook.mjs", import.meta.url)
|
|
60
|
+
);
|
|
61
|
+
const quote = text => {
|
|
62
|
+
required(!/[\0\n\r]/.test(text), "unsupported hosted gateway path");
|
|
63
|
+
return `'${text.replaceAll("'", "'\\''")}'`;
|
|
64
|
+
};
|
|
65
|
+
const file = join(root, "hosted-hooks.json");
|
|
66
|
+
writeJson(file, context);
|
|
67
|
+
writeFileSync(
|
|
68
|
+
join(bin, "node"),
|
|
69
|
+
`#!/bin/sh\nexec ${quote(context.native.node.path)} ${quote(entry)} --context ${quote(file)} -- "$@"\n`,
|
|
70
|
+
{ flag: "wx", mode: 0o700 }
|
|
71
|
+
);
|
|
72
|
+
return bin;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Scope construction uses the same original allocation and proof subject as ordinary hooks. */
|
|
76
|
+
function hookProfile(context, root, native, scope) {
|
|
77
|
+
return {
|
|
78
|
+
version: 1,
|
|
79
|
+
deadline: context.deadline,
|
|
80
|
+
cwd: context.cwd,
|
|
81
|
+
home: root,
|
|
82
|
+
nativeGh: native.gh,
|
|
83
|
+
invocation: {
|
|
84
|
+
entry: join(context.cwd, "scripts/lisa-npm-updater.mjs"),
|
|
85
|
+
args: [join(context.cwd, "scripts/lisa-npm-updater.mjs"), "gate"],
|
|
86
|
+
cwd: context.cwd,
|
|
87
|
+
},
|
|
88
|
+
subject: controllerSubject(
|
|
89
|
+
{ ...context, recoveryDescriptor: scope.recoveryDescriptor },
|
|
90
|
+
"hook-read",
|
|
91
|
+
null,
|
|
92
|
+
scope.proofs
|
|
93
|
+
),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Exit ten means no proof; only the real signed verifier's zero permits runtime side effects. */
|
|
98
|
+
export async function authenticateHostedRuntime(context, root, scope, step) {
|
|
99
|
+
const result = await step(
|
|
100
|
+
context,
|
|
101
|
+
{ ...scope.env, LISA_NPM_HOOK_ROLE: "commit" },
|
|
102
|
+
"node",
|
|
103
|
+
["scripts/lisa-automation-provenance.mjs", join(root, "message")]
|
|
104
|
+
);
|
|
105
|
+
required(result.code === 0, "canonical runtime proof was not authenticated");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Retain qualified install paths while adding only the existing gateway and hosted identity. */
|
|
109
|
+
function hostedEnvironment(context, root, bin, installed) {
|
|
110
|
+
required(
|
|
111
|
+
installed.HOME === root && typeof installed.PATH === "string",
|
|
112
|
+
"installed hosted environment differs"
|
|
113
|
+
);
|
|
114
|
+
return {
|
|
115
|
+
...installed,
|
|
116
|
+
PATH: `${bin}:${installed.PATH}`,
|
|
117
|
+
CI: "true",
|
|
118
|
+
GITHUB_ACTIONS: "true",
|
|
119
|
+
GITHUB_REPOSITORY: context.policy.repository,
|
|
120
|
+
GITHUB_RUN_ID: process.env.GITHUB_RUN_ID,
|
|
121
|
+
GITHUB_RUN_ATTEMPT: process.env.GITHUB_RUN_ATTEMPT,
|
|
122
|
+
GITHUB_SHA: context.proposal.parent,
|
|
123
|
+
GITHUB_REF: "refs/heads/main",
|
|
124
|
+
GITHUB_EVENT_NAME: process.env.GITHUB_EVENT_NAME,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Gateway publication failures positively close an already allocated original listener. */
|
|
129
|
+
export async function openHostedReadScope(
|
|
130
|
+
context,
|
|
131
|
+
root,
|
|
132
|
+
env,
|
|
133
|
+
native,
|
|
134
|
+
graph,
|
|
135
|
+
step
|
|
136
|
+
) {
|
|
137
|
+
const scope = await withStage("gate-scope", () =>
|
|
138
|
+
proofScope(context, env, step)
|
|
139
|
+
);
|
|
140
|
+
const profile = await withStage("gate-scope", () => {
|
|
141
|
+
mkdirSync(join(root, "gh-config"), { mode: 0o700 });
|
|
142
|
+
return hookProfile(context, root, native, scope);
|
|
143
|
+
});
|
|
144
|
+
const broker = await withStage("gate-broker", () =>
|
|
145
|
+
startHookReadBroker(profile, context.token)
|
|
146
|
+
);
|
|
147
|
+
try {
|
|
148
|
+
const bin = await withStage("gate-gateway", () =>
|
|
149
|
+
nodeGateway(root, {
|
|
150
|
+
version: 1,
|
|
151
|
+
root,
|
|
152
|
+
cwd: context.cwd,
|
|
153
|
+
graph,
|
|
154
|
+
native,
|
|
155
|
+
reader: { root, path: broker.path, deadline: context.deadline },
|
|
156
|
+
})
|
|
157
|
+
);
|
|
158
|
+
const environment = installed =>
|
|
159
|
+
hostedEnvironment(context, root, bin, installed);
|
|
160
|
+
return {
|
|
161
|
+
env: environment(env),
|
|
162
|
+
environment,
|
|
163
|
+
close: broker.close,
|
|
164
|
+
};
|
|
165
|
+
} catch (error) {
|
|
166
|
+
try {
|
|
167
|
+
await withStage("gate-close", () => broker.close());
|
|
168
|
+
} catch (cleanup) {
|
|
169
|
+
throw new AggregateError(
|
|
170
|
+
[error, cleanup],
|
|
171
|
+
"hosted gateway publication and scope cleanup failed",
|
|
172
|
+
{ cause: error }
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
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 npm policy validation shares the optional signed runtime contract without cycles. */
|
|
5
|
+
import { keys, required } from "./npm-update-invariants.mjs";
|
|
6
|
+
import { railsRuntime } from "./npm-update-rails-runtime-contract.mjs";
|
|
7
|
+
|
|
8
|
+
export const NAME = /^(?:@[a-z0-9][a-z0-9_.-]*\/)?[a-z0-9][a-z0-9_.-]*$/;
|
|
9
|
+
export const VERSION =
|
|
10
|
+
/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[A-Za-z0-9.-]+)?$/;
|
|
11
|
+
|
|
12
|
+
/** Configured tracking and an explicit owner are prerequisites, never inferred. */
|
|
13
|
+
export function validatePolicy(value, config) {
|
|
14
|
+
keys(value, [
|
|
15
|
+
"version",
|
|
16
|
+
"repository",
|
|
17
|
+
"directory",
|
|
18
|
+
"target",
|
|
19
|
+
"maintainer",
|
|
20
|
+
"packages",
|
|
21
|
+
"lisaOwner",
|
|
22
|
+
...(Object.hasOwn(value ?? {}, "runtime") ? ["runtime"] : []),
|
|
23
|
+
]);
|
|
24
|
+
railsRuntime(value);
|
|
25
|
+
required(
|
|
26
|
+
value.version === 1 && value.directory === "." && value.target === "main",
|
|
27
|
+
"only root npm on main is supported"
|
|
28
|
+
);
|
|
29
|
+
required(
|
|
30
|
+
config.tracker === "github" &&
|
|
31
|
+
value.repository === `${config.github?.org}/${config.github?.repo}`,
|
|
32
|
+
"configured repository/tracker differs"
|
|
33
|
+
);
|
|
34
|
+
required(
|
|
35
|
+
/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(value.repository),
|
|
36
|
+
"invalid repository"
|
|
37
|
+
);
|
|
38
|
+
required(
|
|
39
|
+
typeof value.maintainer === "string" &&
|
|
40
|
+
/^[A-Za-z0-9][A-Za-z0-9-]{0,38}$/.test(value.maintainer),
|
|
41
|
+
"explicit assignable maintainer required"
|
|
42
|
+
);
|
|
43
|
+
required(
|
|
44
|
+
["absent", "verified-local-full-apply"].includes(value.lisaOwner),
|
|
45
|
+
"Lisa requires a verified local full-apply owner"
|
|
46
|
+
);
|
|
47
|
+
required(
|
|
48
|
+
Array.isArray(value.packages) &&
|
|
49
|
+
value.packages.length > 0 &&
|
|
50
|
+
value.packages.length <= 64,
|
|
51
|
+
"invalid package selection"
|
|
52
|
+
);
|
|
53
|
+
const names = new Set();
|
|
54
|
+
for (const selection of value.packages) {
|
|
55
|
+
keys(selection, ["name", "version"]);
|
|
56
|
+
required(
|
|
57
|
+
typeof selection.name === "string" &&
|
|
58
|
+
selection.name.length <= 214 &&
|
|
59
|
+
NAME.test(selection.name),
|
|
60
|
+
"invalid npm name"
|
|
61
|
+
);
|
|
62
|
+
required(
|
|
63
|
+
typeof selection.version === "string" && VERSION.test(selection.version),
|
|
64
|
+
"exact registry version required"
|
|
65
|
+
);
|
|
66
|
+
required(
|
|
67
|
+
!names.has(selection.name) && selection.name !== "@codyswann/lisa",
|
|
68
|
+
"duplicate selection or Lisa version-only update"
|
|
69
|
+
);
|
|
70
|
+
names.add(selection.name);
|
|
71
|
+
}
|
|
72
|
+
return structuredClone(value);
|
|
73
|
+
}
|
|
@@ -146,6 +146,7 @@ export async function publishProposal({
|
|
|
146
146
|
required(
|
|
147
147
|
descriptor.parent === proposal.parent &&
|
|
148
148
|
descriptor.proposalKey === proposal.bindingKey &&
|
|
149
|
+
descriptor.runtimeSha256 === proposal.runtimeSha256 &&
|
|
149
150
|
canonicalJson(descriptor.files) === canonicalJson(proposal.hashes) &&
|
|
150
151
|
canonicalJson(descriptor.updates) === canonicalJson(proposal.updates) &&
|
|
151
152
|
descriptor.workItem === allocation.workItem &&
|