@codyswann/lisa 4.70.3 → 4.71.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/history-secret-evidence-shape.mjs +188 -0
- package/all/copy-overwrite/scripts/lib/history-secret-evidence.mjs +271 -0
- package/all/copy-overwrite/scripts/lib/history-secret-scanner.mjs +6 -296
- 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 +272 -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 +258 -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-history-secrets.mjs +8 -1
- 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 +620 -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 +165 -13
- 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
|
@@ -152,6 +152,7 @@ fi
|
|
|
152
152
|
|
|
153
153
|
if ! BLOCK_NO_VERIFY_COMMAND="$command_str" python3 - <<'PY'
|
|
154
154
|
import os
|
|
155
|
+
import io
|
|
155
156
|
import re
|
|
156
157
|
import shlex
|
|
157
158
|
import sys
|
|
@@ -1284,6 +1285,57 @@ def git_skips_verification(text, depth=0):
|
|
|
1284
1285
|
return False
|
|
1285
1286
|
|
|
1286
1287
|
|
|
1288
|
+
def git_config_reads_key(tokens, index):
|
|
1289
|
+
"""Whether this key is a read operand of this exact Git config invocation.
|
|
1290
|
+
|
|
1291
|
+
Keep the command boundary: a query cannot authorize the next command or
|
|
1292
|
+
turn a later setting into a read. Unknown options remain conservative.
|
|
1293
|
+
"""
|
|
1294
|
+
start = index
|
|
1295
|
+
while start > 0 and tokens[start - 1] not in COMMAND_SEPARATORS:
|
|
1296
|
+
start -= 1
|
|
1297
|
+
end = index + 1
|
|
1298
|
+
while end < len(tokens) and tokens[end] not in COMMAND_SEPARATORS:
|
|
1299
|
+
end += 1
|
|
1300
|
+
program, args, opaque = command_word(tokens[start:end])
|
|
1301
|
+
if opaque or program != "git":
|
|
1302
|
+
return False
|
|
1303
|
+
found = subcommand_after_git(args, 0)
|
|
1304
|
+
if not found or found[0] != "config":
|
|
1305
|
+
return False
|
|
1306
|
+
argv = found[1]
|
|
1307
|
+
key_offset = index - (end - len(argv))
|
|
1308
|
+
read = bool(argv and argv[0] == "get")
|
|
1309
|
+
cursor = 1 if read else 0
|
|
1310
|
+
operands = []
|
|
1311
|
+
read_actions = {"--get", "--get-all", "--get-regexp", "--get-urlmatch"}
|
|
1312
|
+
modifiers = {
|
|
1313
|
+
"--local", "--global", "--system", "--worktree", "--includes",
|
|
1314
|
+
"--no-includes", "--show-origin", "--show-scope", "--null", "-z",
|
|
1315
|
+
"--name-only", "--all", "--regexp",
|
|
1316
|
+
}
|
|
1317
|
+
value_options = {"--file", "-f", "--blob", "--type", "--default", "--value"}
|
|
1318
|
+
while cursor < len(argv):
|
|
1319
|
+
token = argv[cursor]
|
|
1320
|
+
if token in read_actions:
|
|
1321
|
+
if read:
|
|
1322
|
+
return False
|
|
1323
|
+
read = True
|
|
1324
|
+
elif token in value_options:
|
|
1325
|
+
if cursor + 1 >= len(argv):
|
|
1326
|
+
return False
|
|
1327
|
+
cursor += 1
|
|
1328
|
+
elif token in modifiers:
|
|
1329
|
+
pass
|
|
1330
|
+
elif token.startswith("-"):
|
|
1331
|
+
if not any(token.startswith(option + "=") for option in value_options):
|
|
1332
|
+
return False
|
|
1333
|
+
else:
|
|
1334
|
+
operands.append(cursor)
|
|
1335
|
+
cursor += 1
|
|
1336
|
+
return bool(operands and operands[0] == key_offset and (read or len(operands) == 1))
|
|
1337
|
+
|
|
1338
|
+
|
|
1287
1339
|
def token_bypass(tokens):
|
|
1288
1340
|
"""Whether any token disables hooks by environment or by git config.
|
|
1289
1341
|
|
|
@@ -1295,7 +1347,7 @@ def token_bypass(tokens):
|
|
|
1295
1347
|
option, and an option belongs to an argv.
|
|
1296
1348
|
|
|
1297
1349
|
Args:
|
|
1298
|
-
tokens:
|
|
1350
|
+
tokens: Operator-aware tokens of one command or script.
|
|
1299
1351
|
|
|
1300
1352
|
Returns:
|
|
1301
1353
|
True when a token disables verification.
|
|
@@ -1321,7 +1373,9 @@ def token_bypass(tokens):
|
|
|
1321
1373
|
if lowered.startswith("core.hookspath="):
|
|
1322
1374
|
if not is_permitted_hooks_path(token.split("=", 1)[1]):
|
|
1323
1375
|
return True
|
|
1324
|
-
if lowered == "core.hookspath" and i + 1 < len(tokens)
|
|
1376
|
+
if (lowered == "core.hookspath" and i + 1 < len(tokens)
|
|
1377
|
+
and tokens[i + 1] not in COMMAND_SEPARATORS
|
|
1378
|
+
and not git_config_reads_key(tokens, i)):
|
|
1325
1379
|
if not is_permitted_hooks_path(tokens[i + 1]):
|
|
1326
1380
|
return True
|
|
1327
1381
|
# `git --config-env=core.hooksPath=SOMEVAR` sets the same config, reading
|
|
@@ -1388,8 +1442,22 @@ def token_bypass(tokens):
|
|
|
1388
1442
|
return False
|
|
1389
1443
|
|
|
1390
1444
|
|
|
1445
|
+
class OperatorStream(io.StringIO):
|
|
1446
|
+
"""Remember punctuation read while shlex is inside a quote or escape."""
|
|
1447
|
+
|
|
1448
|
+
lexer = None
|
|
1449
|
+
literal_operator = False
|
|
1450
|
+
|
|
1451
|
+
def read(self, size=-1):
|
|
1452
|
+
value = super().read(size)
|
|
1453
|
+
if (self.lexer is not None and self.lexer.state in {"'", '"', "\\"}
|
|
1454
|
+
and any(char in "();|&<>" for char in value)):
|
|
1455
|
+
self.literal_operator = True
|
|
1456
|
+
return value
|
|
1457
|
+
|
|
1458
|
+
|
|
1391
1459
|
def flat_tokens(text):
|
|
1392
|
-
"""
|
|
1460
|
+
"""Operator-aware tokens without printed data, or None on a lex failure.
|
|
1393
1461
|
|
|
1394
1462
|
Args:
|
|
1395
1463
|
text: A command line or a script's contents.
|
|
@@ -1398,9 +1466,25 @@ def flat_tokens(text):
|
|
|
1398
1466
|
The token list, or None.
|
|
1399
1467
|
"""
|
|
1400
1468
|
try:
|
|
1401
|
-
|
|
1469
|
+
stream = OperatorStream(line_boundaries_as_separators(text))
|
|
1470
|
+
lexer = shlex.shlex(stream, posix=True, punctuation_chars=True)
|
|
1471
|
+
lexer.whitespace_split = True
|
|
1472
|
+
lexer.commenters = ""
|
|
1473
|
+
stream.lexer = lexer
|
|
1474
|
+
tokens = []
|
|
1475
|
+
while True:
|
|
1476
|
+
stream.literal_operator = False
|
|
1477
|
+
token = lexer.get_token()
|
|
1478
|
+
if token is None:
|
|
1479
|
+
break
|
|
1480
|
+
# shlex otherwise makes quoted ";" indistinguishable from a real
|
|
1481
|
+
# command boundary. Keep literal destinations as data, so a setter
|
|
1482
|
+
# cannot masquerade as a one-operand configuration read.
|
|
1483
|
+
if token in COMMAND_SEPARATORS and stream.literal_operator:
|
|
1484
|
+
token = "\x00" + token
|
|
1485
|
+
tokens.append(token)
|
|
1402
1486
|
return [
|
|
1403
|
-
token
|
|
1487
|
+
token
|
|
1404
1488
|
for index, token in enumerate(tokens)
|
|
1405
1489
|
if not printed_argument(tokens, index)
|
|
1406
1490
|
]
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// This file is managed by Lisa and IS replaced on each `lisa` run.
|
|
3
|
+
// Do not edit directly — durable changes belong upstream in Lisa.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file lisa-npm-updater.mjs
|
|
7
|
+
* @description Four phase entry points consume bounded data from a committed trusted host policy.
|
|
8
|
+
* @module npm-updater
|
|
9
|
+
*/
|
|
10
|
+
import { join, resolve } from "node:path";
|
|
11
|
+
import { invokedAsScript } from "./lib/invoked-as-script.mjs";
|
|
12
|
+
import {
|
|
13
|
+
required,
|
|
14
|
+
UpdaterError,
|
|
15
|
+
keys,
|
|
16
|
+
validatePolicy,
|
|
17
|
+
validateProposal,
|
|
18
|
+
} from "./lib/npm-update-contract.mjs";
|
|
19
|
+
import {
|
|
20
|
+
readJson,
|
|
21
|
+
writeJson,
|
|
22
|
+
runProcess,
|
|
23
|
+
phaseDirectory,
|
|
24
|
+
} from "./lib/npm-update-process.mjs";
|
|
25
|
+
import { prepareUpdate } from "./lib/npm-update-prepare.mjs";
|
|
26
|
+
import { GitHub } from "./lib/npm-update-github.mjs";
|
|
27
|
+
import { allocateLeaf } from "./lib/npm-update-allocate.mjs";
|
|
28
|
+
import { publishProposal } from "./lib/npm-update-publish.mjs";
|
|
29
|
+
import {
|
|
30
|
+
prepareAuthorization,
|
|
31
|
+
finalizeAuthorization,
|
|
32
|
+
} from "./lib/npm-update-authorization.mjs";
|
|
33
|
+
import { gateProposal } from "./lib/npm-update-gate.mjs";
|
|
34
|
+
import { readGateProof } from "./lib/npm-update-gate-proof.mjs";
|
|
35
|
+
import { sha256 } from "./lib/github-attestation-verifier.mjs";
|
|
36
|
+
|
|
37
|
+
/** Only committed policy grants authority; ignored/local config cannot activate Actions. */
|
|
38
|
+
async function configuration(cwd) {
|
|
39
|
+
const result = await runProcess("git", ["show", "HEAD:.lisa.config.json"], {
|
|
40
|
+
cwd,
|
|
41
|
+
env: { PATH: process.env.PATH, HOME: "/nonexistent" },
|
|
42
|
+
maximum: 1_048_576,
|
|
43
|
+
});
|
|
44
|
+
const config = JSON.parse(result.stdout.toString());
|
|
45
|
+
required(
|
|
46
|
+
config.automationProvenance?.enabled === true,
|
|
47
|
+
"automation provenance must be explicitly configured on committed main"
|
|
48
|
+
);
|
|
49
|
+
const policy = validatePolicy(config.npmUpdater, config);
|
|
50
|
+
required(
|
|
51
|
+
config.automationProvenance.repository === policy.repository,
|
|
52
|
+
"committed authority repository differs"
|
|
53
|
+
);
|
|
54
|
+
return { config, policy };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Allocation creates its canonical leaf before previewing any attributed commit. */
|
|
58
|
+
async function allocatePhase(cwd, output, proposal, policy, config) {
|
|
59
|
+
const api = new GitHub(config.automationProvenance, process.env.GH_TOKEN);
|
|
60
|
+
const allocation = await allocateLeaf({
|
|
61
|
+
api,
|
|
62
|
+
proposal,
|
|
63
|
+
policy,
|
|
64
|
+
config,
|
|
65
|
+
cwd,
|
|
66
|
+
});
|
|
67
|
+
const result = await prepareAuthorization({
|
|
68
|
+
api,
|
|
69
|
+
cwd,
|
|
70
|
+
output,
|
|
71
|
+
proposal,
|
|
72
|
+
allocation,
|
|
73
|
+
policy,
|
|
74
|
+
config,
|
|
75
|
+
});
|
|
76
|
+
if (result.mode === "stale-outstanding")
|
|
77
|
+
writeJson(join(output, "authorization.json"), {
|
|
78
|
+
mode: result.mode,
|
|
79
|
+
workItem: allocation.workItem,
|
|
80
|
+
});
|
|
81
|
+
process.stdout.write(`${result.mode}\n`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Publication consumes the fixed gated object and proof slots without changing authority order. */
|
|
85
|
+
async function publishPhase(
|
|
86
|
+
cwd,
|
|
87
|
+
output,
|
|
88
|
+
proposal,
|
|
89
|
+
policy,
|
|
90
|
+
config,
|
|
91
|
+
allocation,
|
|
92
|
+
preview
|
|
93
|
+
) {
|
|
94
|
+
const gated = readJson(join(output, "gated.json"));
|
|
95
|
+
keys(gated, ["raw", "receipt"]);
|
|
96
|
+
required(
|
|
97
|
+
typeof gated.raw === "string" && gated.raw.length <= 90_000,
|
|
98
|
+
"bounded raw commit envelope required"
|
|
99
|
+
);
|
|
100
|
+
const api = new GitHub(config.automationProvenance, process.env.GH_TOKEN);
|
|
101
|
+
const result = await publishProposal({
|
|
102
|
+
api,
|
|
103
|
+
cwd,
|
|
104
|
+
proposal,
|
|
105
|
+
policy,
|
|
106
|
+
config,
|
|
107
|
+
allocation,
|
|
108
|
+
descriptor: preview.descriptor,
|
|
109
|
+
proof: {
|
|
110
|
+
descriptor: join(output, "descriptor.json"),
|
|
111
|
+
bundle: join(output, "bundle.json"),
|
|
112
|
+
recovery: join(output, "recovery.json"),
|
|
113
|
+
recoveryBundle: join(output, "recovery-bundle.json"),
|
|
114
|
+
},
|
|
115
|
+
receipt: gated.receipt,
|
|
116
|
+
raw: Buffer.from(gated.raw, "base64"),
|
|
117
|
+
});
|
|
118
|
+
writeJson(join(output, "published.json"), result);
|
|
119
|
+
process.stdout.write(`${result.status} ${result.url}\n`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Strict phase and fixed directory arguments are data, never a caller recipe. */
|
|
123
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
124
|
+
required(
|
|
125
|
+
argv.length === 3 &&
|
|
126
|
+
["prepare", "allocate", "checkpoint", "gate", "publish"].includes(
|
|
127
|
+
argv[0]
|
|
128
|
+
),
|
|
129
|
+
"usage: lisa-npm-updater.mjs <prepare|allocate|checkpoint|gate|publish> <owned-checkout> <private-phase-directory>"
|
|
130
|
+
);
|
|
131
|
+
const [phase, checkout, directory] = argv;
|
|
132
|
+
const cwd = resolve(checkout);
|
|
133
|
+
const output = resolve(directory);
|
|
134
|
+
const { config, policy } = await configuration(cwd);
|
|
135
|
+
phaseDirectory(output);
|
|
136
|
+
if (phase === "prepare") {
|
|
137
|
+
const result = await prepareUpdate({ cwd, policy, config });
|
|
138
|
+
writeJson(join(output, "prepare.json"), result);
|
|
139
|
+
if (result.status === "prepared")
|
|
140
|
+
writeJson(join(output, "proposal.json"), result.proposal);
|
|
141
|
+
process.stdout.write(`${result.status}\n`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const proposal = validateProposal(
|
|
145
|
+
readJson(join(output, "proposal.json")),
|
|
146
|
+
policy
|
|
147
|
+
);
|
|
148
|
+
if (phase === "allocate")
|
|
149
|
+
return allocatePhase(cwd, output, proposal, policy, config);
|
|
150
|
+
const allocation = readJson(join(output, "allocation.json"));
|
|
151
|
+
const preview = readJson(join(output, "preview.json"));
|
|
152
|
+
keys(preview, ["descriptor", "message", "epoch"]);
|
|
153
|
+
required(
|
|
154
|
+
sha256(preview.message) === preview.descriptor.messageSha256,
|
|
155
|
+
"preview final message differs"
|
|
156
|
+
);
|
|
157
|
+
if (phase === "checkpoint") {
|
|
158
|
+
const api = new GitHub(config.automationProvenance, process.env.GH_TOKEN);
|
|
159
|
+
const result = await finalizeAuthorization({
|
|
160
|
+
api,
|
|
161
|
+
output,
|
|
162
|
+
proposal,
|
|
163
|
+
allocation,
|
|
164
|
+
preview,
|
|
165
|
+
policy,
|
|
166
|
+
config,
|
|
167
|
+
});
|
|
168
|
+
process.stdout.write(`${result.mode} checkpoint complete\n`);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (phase === "gate") {
|
|
172
|
+
const result = await gateProposal({
|
|
173
|
+
cwd,
|
|
174
|
+
proposal,
|
|
175
|
+
policy,
|
|
176
|
+
allocation,
|
|
177
|
+
preview,
|
|
178
|
+
...readGateProof(output),
|
|
179
|
+
token: process.env.GH_TOKEN,
|
|
180
|
+
config,
|
|
181
|
+
});
|
|
182
|
+
writeJson(join(output, "gated.json"), result);
|
|
183
|
+
process.stdout.write(`gated ${result.receipt.commit}\n`);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
return publishPhase(
|
|
187
|
+
cwd,
|
|
188
|
+
output,
|
|
189
|
+
proposal,
|
|
190
|
+
policy,
|
|
191
|
+
config,
|
|
192
|
+
allocation,
|
|
193
|
+
preview
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (invokedAsScript(import.meta.url))
|
|
198
|
+
main().catch(error => {
|
|
199
|
+
// Never print candidate/provider payloads or child output that may contain credentials.
|
|
200
|
+
process.stderr.write(
|
|
201
|
+
`${error instanceof UpdaterError ? error.message : "npm updater failed: required policy, authorization, installation, gate or exact publication evidence was unavailable"}\n`
|
|
202
|
+
);
|
|
203
|
+
process.exitCode = 1;
|
|
204
|
+
});
|
|
@@ -41,15 +41,7 @@
|
|
|
41
41
|
* @module lisa-run-gates
|
|
42
42
|
*/
|
|
43
43
|
|
|
44
|
-
import {
|
|
45
|
-
existsSync,
|
|
46
|
-
mkdirSync,
|
|
47
|
-
mkdtempSync,
|
|
48
|
-
readFileSync,
|
|
49
|
-
rmSync,
|
|
50
|
-
writeFileSync,
|
|
51
|
-
} from "node:fs";
|
|
52
|
-
import { tmpdir } from "node:os";
|
|
44
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
53
45
|
import { dirname, join, relative } from "node:path";
|
|
54
46
|
import { fileURLToPath } from "node:url";
|
|
55
47
|
|
|
@@ -323,20 +315,21 @@ function formatLine(state, gate, detail) {
|
|
|
323
315
|
/**
|
|
324
316
|
* Read an executor's answer, whichever of the two shapes it returned.
|
|
325
317
|
*
|
|
326
|
-
* An executor may answer with a bare exit code or with `{code, output}
|
|
327
|
-
*
|
|
328
|
-
*
|
|
318
|
+
* An executor may answer with a bare exit code or with `{code, output}` and an
|
|
319
|
+
* optional OS-observed signal. Both retain the actual exit status; output and
|
|
320
|
+
* signal identity only EXPLAIN a verdict already reached. An executor that
|
|
329
321
|
* cannot capture output — a stub, a Windows shell, a run with capture switched
|
|
330
322
|
* off — therefore loses the diagnosis and nothing else.
|
|
331
|
-
* @param {number|null|undefined|{code: number|null, output: string|null}} raw
|
|
323
|
+
* @param {number|null|undefined|{code: number|null, output: string|null, signal?: string}} raw
|
|
332
324
|
* Whatever the executor returned.
|
|
333
|
-
* @returns {{code: number|null, output: string|null}} The normalised answer.
|
|
325
|
+
* @returns {{code: number|null, output: string|null, signal?: string}} The normalised answer.
|
|
334
326
|
*/
|
|
335
327
|
function normaliseExec(raw) {
|
|
336
328
|
if (raw !== null && typeof raw === "object") {
|
|
337
329
|
return {
|
|
338
330
|
code: typeof raw.code === "number" ? raw.code : null,
|
|
339
331
|
output: typeof raw.output === "string" ? raw.output : null,
|
|
332
|
+
...(typeof raw.signal === "string" ? { signal: raw.signal } : {}),
|
|
340
333
|
};
|
|
341
334
|
}
|
|
342
335
|
return { code: typeof raw === "number" ? raw : null, output: null };
|
|
@@ -440,7 +433,7 @@ function stateFor(kind) {
|
|
|
440
433
|
}
|
|
441
434
|
|
|
442
435
|
function execute(gate, exec, declarations) {
|
|
443
|
-
const { code, output } = normaliseExec(exec(gate.command, gate));
|
|
436
|
+
const { code, output, signal } = normaliseExec(exec(gate.command, gate));
|
|
444
437
|
if (code === 0) {
|
|
445
438
|
// Exit 0 is ADJACENT to "the property holds"; it is not the observation.
|
|
446
439
|
// A command that exited 0 while its own output says it collected zero
|
|
@@ -472,14 +465,14 @@ function execute(gate, exec, declarations) {
|
|
|
472
465
|
};
|
|
473
466
|
}
|
|
474
467
|
const shown = typeof code === "number" ? code : "terminated";
|
|
475
|
-
// The
|
|
476
|
-
//
|
|
477
|
-
//
|
|
468
|
+
// The actual status and signal go in as well as the output: a native kill
|
|
469
|
+
// retains null status, while a shell's `exit 143` is `128 + 15`. Either can
|
|
470
|
+
// arrive carrying a truncated transcript that
|
|
478
471
|
// reads exactly like a real gate failure — which is how one `exit 1` came to
|
|
479
472
|
// have three distinct causes with re-running a rational response to all of
|
|
480
473
|
// them (CodySwannGT/lisa#2897).
|
|
481
474
|
const diagnosis = withDeclarations(
|
|
482
|
-
diagnoseFailure(output, code),
|
|
475
|
+
diagnoseFailure(output, code, undefined, undefined, undefined, signal),
|
|
483
476
|
output,
|
|
484
477
|
declarations
|
|
485
478
|
);
|
|
@@ -1141,12 +1134,6 @@ function readPackageScripts(cwd) {
|
|
|
1141
1134
|
}
|
|
1142
1135
|
}
|
|
1143
1136
|
|
|
1144
|
-
/**
|
|
1145
|
-
* How much of a gate's output is kept for diagnosis. A suite of 14,000 tests
|
|
1146
|
-
* prints megabytes; the signatures that matter are all near the end.
|
|
1147
|
-
*/
|
|
1148
|
-
const CAPTURE_TAIL_BYTES = 512 * 1024;
|
|
1149
|
-
|
|
1150
1137
|
/**
|
|
1151
1138
|
* Hang-detector deadline for one gate command, in milliseconds.
|
|
1152
1139
|
*
|
|
@@ -1218,7 +1205,7 @@ function runInterruption() {
|
|
|
1218
1205
|
*/
|
|
1219
1206
|
function runProcessTree(command, options = {}) {
|
|
1220
1207
|
const timeout = options.timeout ?? GATE_COMMAND_BUDGET_MS;
|
|
1221
|
-
const { shell: _shell, ...childOptions } = options;
|
|
1208
|
+
const { shell: _shell, capture = false, ...childOptions } = options;
|
|
1222
1209
|
// The supervisor already ends itself when THIS process dies; naming the
|
|
1223
1210
|
// process above it is what covers the measured incident, where the hook was
|
|
1224
1211
|
// killed and the runner — not the supervisor — was the orphan that kept the
|
|
@@ -1228,7 +1215,14 @@ function runProcessTree(command, options = {}) {
|
|
|
1228
1215
|
: [];
|
|
1229
1216
|
return boundedSpawnSync(
|
|
1230
1217
|
process.execPath,
|
|
1231
|
-
[
|
|
1218
|
+
[
|
|
1219
|
+
PROCESS_TREE_RUNNER,
|
|
1220
|
+
`--timeout-ms=${timeout}`,
|
|
1221
|
+
...watch,
|
|
1222
|
+
...(capture ? ["--capture-fd=3"] : []),
|
|
1223
|
+
"--",
|
|
1224
|
+
command,
|
|
1225
|
+
],
|
|
1232
1226
|
{
|
|
1233
1227
|
...childOptions,
|
|
1234
1228
|
timeout: timeout + PROCESS_TREE_REAP_BUDGET_MS,
|
|
@@ -1260,7 +1254,7 @@ function killedOrRethrow(error) {
|
|
|
1260
1254
|
* everything about capture is additive: if capture is unavailable or refused,
|
|
1261
1255
|
* the exit code is produced by exactly the code path that produced it before.
|
|
1262
1256
|
* @param {string} command The command line to run.
|
|
1263
|
-
* @returns {{code: number|null, output: null}} Exit code; null when killed.
|
|
1257
|
+
* @returns {{code: number|null, output: null, signal?: string}} Exit code; null when killed.
|
|
1264
1258
|
*/
|
|
1265
1259
|
function plainExec(command) {
|
|
1266
1260
|
try {
|
|
@@ -1271,117 +1265,48 @@ function plainExec(command) {
|
|
|
1271
1265
|
// Still reachable: a non-timeout `spawnSync` failure arrives on the result,
|
|
1272
1266
|
// and it means nothing ran, which is the same "no verdict" answer.
|
|
1273
1267
|
if (child.error) return { code: null, output: null };
|
|
1274
|
-
return {
|
|
1268
|
+
return {
|
|
1269
|
+
code: child.status,
|
|
1270
|
+
output: null,
|
|
1271
|
+
...(child.signal ? { signal: child.signal } : {}),
|
|
1272
|
+
};
|
|
1275
1273
|
} catch (error) {
|
|
1276
1274
|
return killedOrRethrow(error);
|
|
1277
1275
|
}
|
|
1278
1276
|
}
|
|
1279
1277
|
|
|
1280
1278
|
/**
|
|
1281
|
-
*
|
|
1282
|
-
*
|
|
1283
|
-
*
|
|
1284
|
-
*
|
|
1285
|
-
*
|
|
1286
|
-
*
|
|
1287
|
-
*
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
// The one child in this file that is NOT a project gate command, so it takes
|
|
1292
|
-
// the shared default rather than the two-hour ceiling: `command -v tee`
|
|
1293
|
-
// answers immediately or the shell is broken.
|
|
1294
|
-
try {
|
|
1295
|
-
const probe = boundedSpawnSync("sh", ["-c", "command -v tee"], {
|
|
1296
|
-
stdio: "ignore",
|
|
1297
|
-
});
|
|
1298
|
-
return !probe.error && probe.status === 0;
|
|
1299
|
-
} catch (error) {
|
|
1300
|
-
// A probe killed at its deadline has not confirmed anything, and this
|
|
1301
|
-
// function's whole contract is that an unconfirmed capability goes unused.
|
|
1302
|
-
// Letting the throw escape would convert "cannot tell" into a crash, and
|
|
1303
|
-
// the fallback path it guards answers the question perfectly well.
|
|
1304
|
-
if (isChildTimeout(error)) return false;
|
|
1305
|
-
throw error;
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
/**
|
|
1310
|
-
* Read back what the wrapper recorded, keeping only the tail of the log.
|
|
1311
|
-
* @param {string} statusPath File the wrapper wrote the exit code into.
|
|
1312
|
-
* @param {string} logPath File the wrapper tee'd the output into.
|
|
1313
|
-
* @returns {{code: number|null, output: string|null}} The recorded answer.
|
|
1314
|
-
*/
|
|
1315
|
-
function readCaptured(statusPath, logPath) {
|
|
1316
|
-
let output = null;
|
|
1317
|
-
try {
|
|
1318
|
-
output = readFileSync(logPath, "utf8").slice(-CAPTURE_TAIL_BYTES);
|
|
1319
|
-
} catch {
|
|
1320
|
-
output = null;
|
|
1321
|
-
}
|
|
1322
|
-
try {
|
|
1323
|
-
const code = Number.parseInt(readFileSync(statusPath, "utf8").trim(), 10);
|
|
1324
|
-
// Fail closed. An unreadable status is not a zero: the one thing this
|
|
1325
|
-
// runner may never do is turn "I do not know" into "it passed".
|
|
1326
|
-
return { code: Number.isInteger(code) ? code : null, output };
|
|
1327
|
-
} catch {
|
|
1328
|
-
return { code: null, output };
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
/**
|
|
1333
|
-
* The default executor: run the command in a shell, streaming AND recording.
|
|
1334
|
-
*
|
|
1335
|
-
* stdio is inherited so a failing gate's own output reaches the operator
|
|
1336
|
-
* unaltered and as it happens — a push gate runs for minutes and an operator
|
|
1337
|
-
* needs to watch it, not receive it in a lump at the end. `tee` is what lets
|
|
1338
|
-
* both be true: the operator sees the stream, and the runner keeps a copy to
|
|
1339
|
-
* say WHICH failure it was rather than only that there was one.
|
|
1340
|
-
*
|
|
1341
|
-
* The price is that the command's stdout is a pipe rather than a terminal, so
|
|
1342
|
-
* tools that colourise or animate for a TTY print their plain form. That is a
|
|
1343
|
-
* deliberate trade: a plain, diagnosable failure beats a coloured, mute one.
|
|
1344
|
-
* `LISA_GATES_CAPTURE=0` buys the colour back at the cost of the diagnosis.
|
|
1345
|
-
*
|
|
1346
|
-
* The exit code comes from a status file written INSIDE the pipeline, never
|
|
1347
|
-
* from the pipeline itself — a pipeline reports `tee`'s status, which is
|
|
1348
|
-
* almost always zero, and reading it would report every failing gate as
|
|
1349
|
-
* passing. That is the single most dangerous mistake available here.
|
|
1350
|
-
* @param {string} command The command line to run.
|
|
1351
|
-
* @returns {{code: number|null, output: string|null}} Exit code and output.
|
|
1279
|
+
* Run a gate with live output and a supervisor-owned diagnostic tail.
|
|
1280
|
+
*
|
|
1281
|
+
* The extra pipe belongs only to the supervisor and its caller: the command
|
|
1282
|
+
* inherits stdin and two output pipes, never the diagnostic descriptor. No
|
|
1283
|
+
* child-writable status record participates in the verdict. Capture failures
|
|
1284
|
+
* retain the supervised OS result and never execute the command a second time.
|
|
1285
|
+
* Windows retains its existing native-job/plain route. Capture remains opt-out
|
|
1286
|
+
* for commands that require inherited terminal handles.
|
|
1287
|
+
* @param {string} command Shell source to run once.
|
|
1288
|
+
* @returns {{code: number|null, output: string|null, signal?: string}} OS status and diagnostic tail.
|
|
1352
1289
|
*/
|
|
1353
1290
|
export function spawnExec(command) {
|
|
1354
|
-
if (
|
|
1355
|
-
let dir;
|
|
1356
|
-
try {
|
|
1357
|
-
dir = mkdtempSync(join(tmpdir(), "lisa-gate-run-"));
|
|
1358
|
-
} catch {
|
|
1291
|
+
if (process.env.LISA_GATES_CAPTURE === "0" || process.platform === "win32") {
|
|
1359
1292
|
return plainExec(command);
|
|
1360
1293
|
}
|
|
1361
|
-
const logPath = join(dir, "output.log");
|
|
1362
|
-
const statusPath = join(dir, "status");
|
|
1363
|
-
// Newline-separated inside a group, so a command ending in a trailing
|
|
1364
|
-
// comment or redirection still has `echo` run as its own statement.
|
|
1365
|
-
// Paths travel as data, never as shell source. A TMPDIR containing a quote,
|
|
1366
|
-
// newline, dollar sign, or command substitution must not be able to break
|
|
1367
|
-
// this wrapper or execute content merely because mkdtemp inherited it.
|
|
1368
|
-
const script = `{\n${command}\necho $? > "$LISA_GATE_STATUS_PATH"\n} 2>&1 | tee "$LISA_GATE_LOG_PATH"\n`;
|
|
1369
1294
|
try {
|
|
1370
|
-
const child = runProcessTree(
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
LISA_GATE_LOG_PATH: logPath,
|
|
1374
|
-
LISA_GATE_STATUS_PATH: statusPath,
|
|
1375
|
-
},
|
|
1376
|
-
stdio: "inherit",
|
|
1295
|
+
const child = runProcessTree(command, {
|
|
1296
|
+
capture: true,
|
|
1297
|
+
stdio: ["inherit", "inherit", "inherit", "pipe"],
|
|
1377
1298
|
timeout: GATE_COMMAND_BUDGET_MS,
|
|
1378
1299
|
});
|
|
1379
1300
|
if (child.error) return { code: null, output: null };
|
|
1380
|
-
|
|
1301
|
+
const captured = child.output?.[3];
|
|
1302
|
+
return {
|
|
1303
|
+
code: child.status,
|
|
1304
|
+
...(child.signal ? { signal: child.signal } : {}),
|
|
1305
|
+
output:
|
|
1306
|
+
captured === null || captured === undefined ? null : String(captured),
|
|
1307
|
+
};
|
|
1381
1308
|
} catch (error) {
|
|
1382
1309
|
return killedOrRethrow(error);
|
|
1383
|
-
} finally {
|
|
1384
|
-
rmSync(dir, { force: true, recursive: true });
|
|
1385
1310
|
}
|
|
1386
1311
|
}
|
|
1387
1312
|
|