@codyswann/lisa 4.71.3 → 4.71.5
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/npm-update-classifier-cache.mjs +94 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper-inventory.mjs +1 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper.mjs +9 -2
- package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +122 -8
- package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +12 -3
- package/dist/cli/hook-refresh-files.d.ts +86 -0
- package/dist/cli/hook-refresh-files.d.ts.map +1 -0
- package/dist/cli/hook-refresh-files.js +209 -0
- package/dist/cli/hook-refresh-files.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +3 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/refresh-hooks-cmd.d.ts +27 -0
- package/dist/cli/refresh-hooks-cmd.d.ts.map +1 -0
- package/dist/cli/refresh-hooks-cmd.js +156 -0
- package/dist/cli/refresh-hooks-cmd.js.map +1 -0
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +20 -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 +21 -9
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/parity-safety-net.sh +122 -8
- 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 +122 -8
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +122 -8
- 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 +122 -8
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +122 -8
- 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 +122 -8
- package/rails/copy-overwrite/scripts/lisa-scratch-run.sh +54 -9
- package/scripts/build-plugins.sh +3 -0
- package/scripts/generate-scratch-supervisor-profile.mjs +75 -0
- package/scripts/two-channel-couplings.json +1 -1
- package/typescript/copy-contents/.husky/commit-msg +28 -31
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
/** One invocation reuses authenticated code bytes, never mutable provider authorization. */
|
|
5
|
+
import { required, OBJECT } from "./npm-update-contract.mjs";
|
|
6
|
+
|
|
7
|
+
/** Fresh owner selection and regular-file reads precede every cached use. */
|
|
8
|
+
function currentBinding(config, owner, read, members) {
|
|
9
|
+
const selected = owner();
|
|
10
|
+
return {
|
|
11
|
+
root: selected.root,
|
|
12
|
+
source: selected.source,
|
|
13
|
+
version: selected.metadata.version,
|
|
14
|
+
signerDigest: config.automationProvenance?.signerDigest,
|
|
15
|
+
bytes: read(selected.root, members),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** A cached import URL cannot authorize changed files or a replacement owner. */
|
|
20
|
+
function assertBinding(expected, actual) {
|
|
21
|
+
required(
|
|
22
|
+
["root", "source", "version", "signerDigest"].every(
|
|
23
|
+
field => expected[field] === actual[field]
|
|
24
|
+
) &&
|
|
25
|
+
expected.bytes.length === actual.bytes.length &&
|
|
26
|
+
expected.bytes.every((bytes, index) => bytes.equals(actual.bytes[index])),
|
|
27
|
+
"authenticated classifier identity or bytes changed"
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A sibling awaiting the same import may have invalidated its URL meanwhile. */
|
|
32
|
+
function assertUsable(poisoned, ...roots) {
|
|
33
|
+
required(
|
|
34
|
+
roots.every(root => !poisoned.has(root)),
|
|
35
|
+
"authenticated classifier import lifetime is uncertain"
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Each factory owns its inaccessible cache; another caller cannot preseed this instance. */
|
|
40
|
+
export function classifierMemo(owner, read, members) {
|
|
41
|
+
const entries = new WeakMap();
|
|
42
|
+
const poisoned = new Set();
|
|
43
|
+
const fixed = [...members];
|
|
44
|
+
return async (config, authenticate) => {
|
|
45
|
+
const current = currentBinding(config, owner, read, fixed);
|
|
46
|
+
assertUsable(poisoned, current.root);
|
|
47
|
+
// Without a source pin, the original qualifier must observe current HEAD each time.
|
|
48
|
+
if (
|
|
49
|
+
current.source &&
|
|
50
|
+
(typeof current.signerDigest !== "string" ||
|
|
51
|
+
!OBJECT.test(current.signerDigest))
|
|
52
|
+
) {
|
|
53
|
+
const imported = await authenticate();
|
|
54
|
+
const observed = currentBinding(config, owner, read, fixed);
|
|
55
|
+
assertUsable(poisoned, current.root, imported.root, observed.root);
|
|
56
|
+
return imported.verdict;
|
|
57
|
+
}
|
|
58
|
+
let entry = entries.get(config);
|
|
59
|
+
if (entry) assertBinding(entry.binding, current);
|
|
60
|
+
else {
|
|
61
|
+
entry = {
|
|
62
|
+
binding: {
|
|
63
|
+
...current,
|
|
64
|
+
bytes: current.bytes.map(bytes => Buffer.from(bytes)),
|
|
65
|
+
},
|
|
66
|
+
promise: Promise.resolve().then(authenticate),
|
|
67
|
+
};
|
|
68
|
+
entries.set(config, entry);
|
|
69
|
+
const pending = entry;
|
|
70
|
+
pending.promise.catch(() => {
|
|
71
|
+
if (entries.get(config) === pending) entries.delete(config);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const imported = await entry.promise;
|
|
75
|
+
let observed;
|
|
76
|
+
try {
|
|
77
|
+
// Authentication/import may yield; detect changes during that interval too.
|
|
78
|
+
observed = currentBinding(config, owner, read, fixed);
|
|
79
|
+
assertBinding(entry.binding, observed);
|
|
80
|
+
required(
|
|
81
|
+
imported.root === entry.binding.root,
|
|
82
|
+
"classifier import owner changed"
|
|
83
|
+
);
|
|
84
|
+
assertUsable(poisoned, entry.binding.root, imported.root, observed.root);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
// ESM caches this URL: restoring files or another config cannot requalify it.
|
|
87
|
+
poisoned.add(entry.binding.root);
|
|
88
|
+
poisoned.add(imported.root);
|
|
89
|
+
if (observed) poisoned.add(observed.root);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
return imported.verdict;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -8,6 +8,7 @@ import { join } from "node:path";
|
|
|
8
8
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
9
9
|
import { existsSync, lstatSync, realpathSync } from "node:fs";
|
|
10
10
|
import { required, VERSION, OBJECT } from "./npm-update-contract.mjs";
|
|
11
|
+
import { classifierMemo } from "./npm-update-classifier-cache.mjs";
|
|
11
12
|
import {
|
|
12
13
|
readBytes,
|
|
13
14
|
runProcess,
|
|
@@ -33,6 +34,7 @@ import {
|
|
|
33
34
|
} from "./npm-update-helper-graph.mjs";
|
|
34
35
|
|
|
35
36
|
const PACKAGE = "@codyswann/lisa";
|
|
37
|
+
const classifier = classifierMemo(helperOwner, helperBytes, MEMBERS);
|
|
36
38
|
|
|
37
39
|
/** Native package resolution supports both upstream and actual emitted managed layouts. */
|
|
38
40
|
export function helperOwner() {
|
|
@@ -277,7 +279,7 @@ export async function qualifiedControllerGraph(cwd, config, entries) {
|
|
|
277
279
|
}
|
|
278
280
|
|
|
279
281
|
/** No canonical package code is imported until independent authority qualifies its fixed graph. */
|
|
280
|
-
|
|
282
|
+
async function authenticatedClassifier(config) {
|
|
281
283
|
const owner = await qualifiedHelperFiles(config, MEMBERS);
|
|
282
284
|
required(
|
|
283
285
|
classifierGraphMatches(MEMBERS.map(member => owner.bytes.get(member))),
|
|
@@ -288,5 +290,10 @@ export async function canonicalClassifier(config) {
|
|
|
288
290
|
typeof module.humanGateVerdict === "function",
|
|
289
291
|
"canonical hold classifier is unavailable"
|
|
290
292
|
);
|
|
291
|
-
return module.humanGateVerdict;
|
|
293
|
+
return { root: owner.root, verdict: module.humanGateVerdict };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Current bytes must still match the independently authenticated imported closure. */
|
|
297
|
+
export function canonicalClassifier(config) {
|
|
298
|
+
return classifier(config, () => authenticatedClassifier(config));
|
|
292
299
|
}
|
|
@@ -527,6 +527,94 @@ follow_names=""
|
|
|
527
527
|
follow_count=0
|
|
528
528
|
follow_bytes=0
|
|
529
529
|
followed_text=""
|
|
530
|
+
followed_rm_text=""
|
|
531
|
+
# Generated from the canonical reviewed supervisor, never from a host manifest.
|
|
532
|
+
# Regenerate through scripts/generate-scratch-supervisor-profile.mjs.
|
|
533
|
+
readonly SCRATCH_SUPERVISOR_SHA256='6fdec1ad0441d7636dc6749c44a23167baf7b75004f156b8a7d4ce1c18c790b7'
|
|
534
|
+
readonly SCRATCH_SUPERVISOR_CLEANUP_COUNT='7'
|
|
535
|
+
|
|
536
|
+
# An authenticated public supervisor owns its fixed cleanup sites. Its original
|
|
537
|
+
# source remains visible to every other policy; its actual payload is followed.
|
|
538
|
+
follow_supervisor() {
|
|
539
|
+
local token="$1" depth="$2" statement="$3" path="" profile="" payload="" rm_source="" original=""
|
|
540
|
+
case "$token" in *lisa-scratch-run.sh) ;; *) return 1 ;; esac
|
|
541
|
+
follow_locate "$token" || follow_refuse "$token" "supervisor source could not be resolved"
|
|
542
|
+
path="$follow_located"
|
|
543
|
+
profile="$(python3 - "$path" "$SCRATCH_SUPERVISOR_SHA256" "$statement" "$token" "$SCRATCH_SUPERVISOR_CLEANUP_COUNT" <<'PY'
|
|
544
|
+
import hashlib
|
|
545
|
+
import json
|
|
546
|
+
import os
|
|
547
|
+
import re
|
|
548
|
+
import shlex
|
|
549
|
+
import stat
|
|
550
|
+
import sys
|
|
551
|
+
|
|
552
|
+
path, digest, statement, operand, expected_count = sys.argv[1:]
|
|
553
|
+
def refuse():
|
|
554
|
+
sys.exit(2)
|
|
555
|
+
|
|
556
|
+
try:
|
|
557
|
+
info = os.lstat(path)
|
|
558
|
+
if not stat.S_ISREG(info.st_mode) or info.st_size > 262144:
|
|
559
|
+
refuse()
|
|
560
|
+
raw = open(path, 'rb').read(262145)
|
|
561
|
+
if len(raw) != info.st_size or not re.fullmatch(r'[0-9a-f]{64}', digest):
|
|
562
|
+
refuse()
|
|
563
|
+
if hashlib.sha256(raw).hexdigest() != digest:
|
|
564
|
+
refuse()
|
|
565
|
+
tokens = shlex.split(statement)
|
|
566
|
+
indices = [index for index, token in enumerate(tokens) if token == operand]
|
|
567
|
+
if len(indices) != 1 or '$' in operand or '`' in operand:
|
|
568
|
+
refuse()
|
|
569
|
+
index = indices[0]
|
|
570
|
+
if index < 1 or tokens[index - 1] not in ('sh', 'bash', '/bin/sh', '/bin/bash'):
|
|
571
|
+
refuse()
|
|
572
|
+
prefix = tokens[:index - 1]
|
|
573
|
+
if prefix[:1] == ['env']:
|
|
574
|
+
prefix = prefix[1:]
|
|
575
|
+
assignments = {}
|
|
576
|
+
for token in prefix:
|
|
577
|
+
if '=' not in token or re.search(r'[$`\r\n]', token):
|
|
578
|
+
refuse()
|
|
579
|
+
key, value = token.split('=', 1)
|
|
580
|
+
if key not in ('LISA_SCRATCH_BASE', 'LISA_SCRATCH_TRACE', 'TMPDIR') or key in assignments:
|
|
581
|
+
refuse()
|
|
582
|
+
assignments[key] = value
|
|
583
|
+
# Match actual shell :- precedence, including inherited and
|
|
584
|
+
# explicitly empty values. TMPDIR cannot override a nonempty inherited base.
|
|
585
|
+
effective = dict(os.environ, **assignments)
|
|
586
|
+
base = effective.get('LISA_SCRATCH_BASE') or effective.get('TMPDIR') or '/tmp'
|
|
587
|
+
if not base or not os.path.isabs(base) or re.search(r'[$`\r\n]', base):
|
|
588
|
+
refuse()
|
|
589
|
+
real_base = os.path.realpath(base)
|
|
590
|
+
roots = ['/tmp', '/var/tmp', os.environ.get('TMPDIR', '/tmp')]
|
|
591
|
+
if not any(os.path.commonpath([real_base, os.path.realpath(root)]) == os.path.realpath(root) for root in roots if root and os.path.isabs(root)):
|
|
592
|
+
refuse()
|
|
593
|
+
args = tokens[index + 1:]
|
|
594
|
+
if len(args) < 4 or args[0] != '--suite' or args[2] != '--':
|
|
595
|
+
refuse()
|
|
596
|
+
if not re.fullmatch(r'[A-Za-z0-9._-]{1,64}', args[1]):
|
|
597
|
+
refuse()
|
|
598
|
+
payload = args[3:]
|
|
599
|
+
if not payload[0] or payload[0].startswith('-') or any(re.search(r'[$`\x00\r\n]', value) for value in payload):
|
|
600
|
+
refuse()
|
|
601
|
+
source = raw.decode('utf8')
|
|
602
|
+
cleaned, count = re.subn(r'(?m)^(\s*)rm -rf "(\$_quarantine|\$_root|\$_entry|\$LISA_SCRATCH_ROOT)"', r'\1:', source)
|
|
603
|
+
if not re.fullmatch(r'[1-9][0-9]*', expected_count) or count != int(expected_count):
|
|
604
|
+
refuse()
|
|
605
|
+
print(json.dumps({'source': cleaned, 'original': source, 'payload': shlex.join(payload)}))
|
|
606
|
+
except (OSError, UnicodeError, ValueError):
|
|
607
|
+
refuse()
|
|
608
|
+
PY
|
|
609
|
+
)" || follow_refuse "$path" "supervisor source or public entry authority could not be authenticated"
|
|
610
|
+
rm_source="$(printf '%s' "$profile" | jq -er '.source')" || follow_refuse "$path" "invalid supervisor profile"
|
|
611
|
+
original="$(printf '%s' "$profile" | jq -er '.original')" || follow_refuse "$path" "invalid supervisor source"
|
|
612
|
+
payload="$(printf '%s' "$profile" | jq -er '.payload')" || follow_refuse "$path" "invalid supervisor payload"
|
|
613
|
+
follow_take "$path" "$depth" "$rm_source" "$original"
|
|
614
|
+
[ "$depth" -lt "$FOLLOW_MAX_DEPTH" ] || follow_refuse "$path" "supervisor payload exceeds inspection depth"
|
|
615
|
+
follow_scan "$payload" "$((depth + 1))"
|
|
616
|
+
return 0
|
|
617
|
+
}
|
|
530
618
|
|
|
531
619
|
FOLLOW_GUIDANCE="This command EXECUTES a file. These guards classify shell text, so the script an
|
|
532
620
|
invocation runs is read and scanned as part of the command — a destructive line
|
|
@@ -813,7 +901,7 @@ follow_locate() {
|
|
|
813
901
|
# executes, to a bounded depth. Full-line comments are dropped: a commented line
|
|
814
902
|
# is never executed, so scanning it can only manufacture a false refusal.
|
|
815
903
|
follow_take() {
|
|
816
|
-
local path="$1" depth="$2" size="" content=""
|
|
904
|
+
local path="$1" depth="$2" size="" content="" rm_content="${3-}"
|
|
817
905
|
case "$follow_paths" in
|
|
818
906
|
*$'\n'"$path"$'\n'*) return 0 ;;
|
|
819
907
|
esac
|
|
@@ -833,10 +921,22 @@ follow_take() {
|
|
|
833
921
|
if [ "$follow_bytes" -gt "$FOLLOW_MAX_TOTAL_BYTES" ]; then
|
|
834
922
|
follow_refuse "$path" "the files this command executes total more than the ${FOLLOW_MAX_TOTAL_BYTES}-byte inspection budget"
|
|
835
923
|
fi
|
|
836
|
-
|
|
837
|
-
|
|
924
|
+
if [ "$#" -eq 4 ]; then
|
|
925
|
+
# Scan the same complete bytes whose digest was authenticated, not a
|
|
926
|
+
# second file read that could pair changed source with the cleanup view.
|
|
927
|
+
content="$(printf '%s' "$4" | sed 's/^[[:space:]]*#.*$//')" || follow_refuse "$path" "authenticated source could not be read"
|
|
928
|
+
else
|
|
929
|
+
content="$(LC_ALL=C tr -d '\000' <"$path" | sed 's/^[[:space:]]*#.*$//')" \
|
|
930
|
+
|| follow_refuse "$path" "it could not be read"
|
|
931
|
+
fi
|
|
838
932
|
follow_names="$follow_names $path"
|
|
839
933
|
followed_text="$followed_text"$'\n'"$content"
|
|
934
|
+
if [ "$#" -lt 3 ]; then
|
|
935
|
+
rm_content="$content"
|
|
936
|
+
else
|
|
937
|
+
rm_content="$(printf '%s' "$rm_content" | sed 's/^[[:space:]]*#.*$//')" || follow_refuse "$path" "supervisor cleanup view could not be read"
|
|
938
|
+
fi
|
|
939
|
+
followed_rm_text="$followed_rm_text"$'\n'"$rm_content"
|
|
840
940
|
if [ "$depth" -lt "$FOLLOW_MAX_DEPTH" ]; then
|
|
841
941
|
# A `cd` inside the followed script governs what THAT script goes on to
|
|
842
942
|
# execute, and nothing after it in the command that ran the script — the
|
|
@@ -855,6 +955,11 @@ follow_take() {
|
|
|
855
955
|
follow_target() {
|
|
856
956
|
local token="$1" depth="$2" path=""
|
|
857
957
|
[ -n "$token" ] || return 0
|
|
958
|
+
# Entry authority belongs to each invocation, not the file-read cache. Only
|
|
959
|
+
# follow_supervisor may admit the exact public interpreter/argv profile.
|
|
960
|
+
case "$token" in
|
|
961
|
+
*lisa-scratch-run.sh) follow_refuse "$token" "supervisor requires its authenticated public interpreter entry" ;;
|
|
962
|
+
esac
|
|
858
963
|
if ! follow_locate "$token"; then
|
|
859
964
|
if [ "$depth" -eq 0 ]; then
|
|
860
965
|
follow_refuse "$token" "no readable file could be resolved for it"
|
|
@@ -888,6 +993,9 @@ follow_direct() {
|
|
|
888
993
|
return 0
|
|
889
994
|
fi
|
|
890
995
|
path="$follow_located"
|
|
996
|
+
case "$path" in
|
|
997
|
+
*lisa-scratch-run.sh) follow_refuse "$path" "supervisor requires its authenticated public interpreter entry" ;;
|
|
998
|
+
esac
|
|
891
999
|
IFS= read -r -n 256 raw <"$path" 2>/dev/null || true
|
|
892
1000
|
while [ "$k" -lt "${#raw}" ]; do
|
|
893
1001
|
char="${raw:k:1}"
|
|
@@ -1219,7 +1327,9 @@ follow_scan() {
|
|
|
1219
1327
|
fi
|
|
1220
1328
|
follow_unwrap "${toks[j]}"
|
|
1221
1329
|
tj="$follow_unwrapped"
|
|
1222
|
-
|
|
1330
|
+
if ! follow_supervisor "$tj" "$depth" "$stmt"; then
|
|
1331
|
+
follow_target "$tj" "$depth"
|
|
1332
|
+
fi
|
|
1223
1333
|
cmd_pos=0
|
|
1224
1334
|
i=$((j + 1))
|
|
1225
1335
|
continue
|
|
@@ -1248,6 +1358,7 @@ follow_scan() {
|
|
|
1248
1358
|
}
|
|
1249
1359
|
|
|
1250
1360
|
follow_scan "$command_for_guards" 0
|
|
1361
|
+
rm_command_for_guards="$command_for_guards"$'\n'"$followed_rm_text"
|
|
1251
1362
|
if [ -n "$follow_names" ]; then
|
|
1252
1363
|
command_for_guards="$command_for_guards"$'\n'"$followed_text"
|
|
1253
1364
|
# A refusal must say WHERE the match is. Without this the operator reads
|
|
@@ -1266,6 +1377,8 @@ fi
|
|
|
1266
1377
|
# `sed ':a;N;$!ba;…'`, which errors on BSD sed (macOS) and there silently no-ops.
|
|
1267
1378
|
normalized_command_str="$(printf '%s' "$command_for_guards" \
|
|
1268
1379
|
| awk '{ if (sub(/\\$/, "")) printf "%s ", $0; else print }')"
|
|
1380
|
+
rm_normalized_command_str="$(printf '%s' "$rm_command_for_guards" \
|
|
1381
|
+
| awk '{ if (sub(/\\$/, "")) printf "%s ", $0; else print }')"
|
|
1269
1382
|
|
|
1270
1383
|
# matches / matches_cs run an ERE against the guarded command text. matches is
|
|
1271
1384
|
# case-insensitive (the default for these guards); matches_cs is case-SENSITIVE
|
|
@@ -1570,7 +1683,7 @@ rm_assignment_value() {
|
|
|
1570
1683
|
# small lexer below preserves quoted right-hand sides such as V="/tmp/x"
|
|
1571
1684
|
# while excluding an argument such as echo "V=/tmp/x". Any ambiguity fails
|
|
1572
1685
|
# closed.
|
|
1573
|
-
RM_ASSIGNMENT_NAME="$name" RM_ASSIGNMENT_COMMAND="$
|
|
1686
|
+
RM_ASSIGNMENT_NAME="$name" RM_ASSIGNMENT_COMMAND="$rm_normalized_command_str" \
|
|
1574
1687
|
RM_ASSIGNMENT_STATEMENT="$rm_assignment_stmt" \
|
|
1575
1688
|
python3 - <<'PY'
|
|
1576
1689
|
import os
|
|
@@ -1769,7 +1882,7 @@ classify_rm_target() {
|
|
|
1769
1882
|
# process substitution cannot deny anything either — it ends that subshell, not
|
|
1770
1883
|
# this hook.
|
|
1771
1884
|
rm_segments_status=0
|
|
1772
|
-
rm_segments="$(printf '%s' "$
|
|
1885
|
+
rm_segments="$(printf '%s' "$rm_normalized_command_str" | tr '&|;' '\n' \
|
|
1773
1886
|
| grep -Ei -- "$RM_CMD"'([[:space:]]|$)')" || rm_segments_status=$?
|
|
1774
1887
|
case "$rm_segments_status" in
|
|
1775
1888
|
0 | 1) ;;
|
|
@@ -2281,9 +2394,10 @@ git_segments="$(printf '%s' "$normalized_command_str" \
|
|
|
2281
2394
|
-e 's/[0-9]*>&[0-9-]*/>/g' \
|
|
2282
2395
|
-e 's/[0-9]*<&[0-9-]*/</g' \
|
|
2283
2396
|
-e 's/||/;/g' \
|
|
2284
|
-
| tr '&;' '\n'
|
|
2397
|
+
| tr '&;' '\n' \
|
|
2398
|
+
| grep -E -- "$RM_RF_CLUSTER|$RM_RF_SPLIT")" || git_segments_status=$?
|
|
2285
2399
|
case "$git_segments_status" in
|
|
2286
|
-
0) ;;
|
|
2400
|
+
0 | 1) ;;
|
|
2287
2401
|
*) scan_failed "$git_segments_status" ;;
|
|
2288
2402
|
esac
|
|
2289
2403
|
while IFS= read -r git_stmt; do
|
|
@@ -280,6 +280,14 @@
|
|
|
280
280
|
"dynamicImports": [],
|
|
281
281
|
"children": []
|
|
282
282
|
},
|
|
283
|
+
"lib/npm-update-classifier-cache.mjs": {
|
|
284
|
+
"sha256": "92272e40617ca9d620ff47e3ffe07a0e5bc1a3949d139752aa2675398ab49f9b",
|
|
285
|
+
"staticImports": [
|
|
286
|
+
"lib/npm-update-contract.mjs"
|
|
287
|
+
],
|
|
288
|
+
"dynamicImports": [],
|
|
289
|
+
"children": []
|
|
290
|
+
},
|
|
283
291
|
"lib/npm-update-contract.mjs": {
|
|
284
292
|
"sha256": "6905dd9d5747702ab502b66bcb38b6d4dcb30127f02466d29b3905f53c27981d",
|
|
285
293
|
"staticImports": [
|
|
@@ -472,14 +480,15 @@
|
|
|
472
480
|
"children": []
|
|
473
481
|
},
|
|
474
482
|
"lib/npm-update-helper-inventory.mjs": {
|
|
475
|
-
"sha256": "
|
|
483
|
+
"sha256": "3561c20458d74eff34650d6b4366f4f03ed4e7825ae886ea78394f63f98a18bb",
|
|
476
484
|
"staticImports": [],
|
|
477
485
|
"dynamicImports": [],
|
|
478
486
|
"children": []
|
|
479
487
|
},
|
|
480
488
|
"lib/npm-update-helper.mjs": {
|
|
481
|
-
"sha256": "
|
|
489
|
+
"sha256": "41e5a37769ec3737829ed1948d0a1442792b25d120f0fb4f4b5e424a50f34705",
|
|
482
490
|
"staticImports": [
|
|
491
|
+
"lib/npm-update-classifier-cache.mjs",
|
|
483
492
|
"lib/npm-update-contract.mjs",
|
|
484
493
|
"lib/npm-update-helper-graph.mjs",
|
|
485
494
|
"lib/npm-update-helper-inventory.mjs",
|
|
@@ -1076,7 +1085,7 @@
|
|
|
1076
1085
|
]
|
|
1077
1086
|
},
|
|
1078
1087
|
"lisa-scratch-run.sh": {
|
|
1079
|
-
"sha256": "
|
|
1088
|
+
"sha256": "6fdec1ad0441d7636dc6749c44a23167baf7b75004f156b8a7d4ce1c18c790b7",
|
|
1080
1089
|
"staticImports": [],
|
|
1081
1090
|
"dynamicImports": [],
|
|
1082
1091
|
"children": []
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** Confine refresh IO and rollback to held native file identities. */
|
|
2
|
+
import { type BigIntStats } from "node:fs";
|
|
3
|
+
import { type FileHandle } from "node:fs/promises";
|
|
4
|
+
/** Retained object; pathname replacement never supplies write authority. */
|
|
5
|
+
export interface Pin {
|
|
6
|
+
readonly filename: string;
|
|
7
|
+
readonly handle: FileHandle;
|
|
8
|
+
readonly identity: BigIntStats;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Require the pathname still selects its retained object.
|
|
12
|
+
* @param pin - Original native object.
|
|
13
|
+
*/
|
|
14
|
+
export declare function checkPath(pin: Pin): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Read a bounded regular descriptor, detecting growth or torn observations.
|
|
17
|
+
* @param pin - Retained file.
|
|
18
|
+
* @returns Complete bytes.
|
|
19
|
+
*/
|
|
20
|
+
export declare function bytes(pin: Pin): Promise<Buffer>;
|
|
21
|
+
/** Bounded original bytes and acknowledged writes on one retained destination. */
|
|
22
|
+
export declare class HookPair {
|
|
23
|
+
readonly source: Pin;
|
|
24
|
+
readonly host: Pin;
|
|
25
|
+
readonly sourceBytes: Buffer;
|
|
26
|
+
readonly hostBytes: Buffer;
|
|
27
|
+
private expected;
|
|
28
|
+
private expectedMode;
|
|
29
|
+
/**
|
|
30
|
+
* Keep source and destination authority for one operation.
|
|
31
|
+
* @param source - Read-only package descriptor.
|
|
32
|
+
* @param host - Original writable host descriptor.
|
|
33
|
+
* @param sourceBytes - Authenticated release bytes.
|
|
34
|
+
* @param hostBytes - Bounded original backup.
|
|
35
|
+
*/
|
|
36
|
+
constructor(source: Pin, host: Pin, sourceBytes: Buffer, hostBytes: Buffer);
|
|
37
|
+
/**
|
|
38
|
+
* Write only the held inode; verify each acknowledged write before truncation.
|
|
39
|
+
* @param content - Complete replacement or backup bytes.
|
|
40
|
+
*/
|
|
41
|
+
write(content: Buffer): Promise<void>;
|
|
42
|
+
/** Reject shared inodes or concurrent bytes before any further mutation. */
|
|
43
|
+
private assertOwnWrites;
|
|
44
|
+
/**
|
|
45
|
+
* Change permissions only on the retained inode containing our writes.
|
|
46
|
+
* @param mode - Original authenticated source or backup permissions.
|
|
47
|
+
*/
|
|
48
|
+
chmod(mode: number): Promise<void>;
|
|
49
|
+
/** Restore only this original inode while it contains our acknowledged writes. */
|
|
50
|
+
restore(): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
/** Operation-local native resources and error accumulation, always finally-closed. */
|
|
53
|
+
export declare class HookFiles {
|
|
54
|
+
private readonly pins;
|
|
55
|
+
private readonly changed;
|
|
56
|
+
private readonly failures;
|
|
57
|
+
/**
|
|
58
|
+
* Retain a no-follow object before subsequent validation can fail.
|
|
59
|
+
* @param filename - Confined absolute path.
|
|
60
|
+
* @param flags - Native access flags.
|
|
61
|
+
* @returns Original object.
|
|
62
|
+
*/
|
|
63
|
+
retain(filename: string, flags: number): Promise<Pin>;
|
|
64
|
+
/**
|
|
65
|
+
* Hold every closed directory component including the resolved root.
|
|
66
|
+
* @param root - Canonical package or host root.
|
|
67
|
+
* @param relative - Closed directory suffix.
|
|
68
|
+
* @returns Pinned ancestors.
|
|
69
|
+
*/
|
|
70
|
+
directories(root: string, relative: string): Promise<Pin[]>;
|
|
71
|
+
/**
|
|
72
|
+
* Register a backup immediately before the first possible write.
|
|
73
|
+
* @param pair - Original descriptor and bytes.
|
|
74
|
+
*/
|
|
75
|
+
mark(pair: HookPair): void;
|
|
76
|
+
/**
|
|
77
|
+
* Preserve the original failure and attempt every descriptor-bound rollback.
|
|
78
|
+
* @param original - Original refresh failure.
|
|
79
|
+
*/
|
|
80
|
+
rollback(original: unknown): Promise<void>;
|
|
81
|
+
/** Close every retained handle even after preflight, write or rollback errors. */
|
|
82
|
+
close(): Promise<void>;
|
|
83
|
+
/** Surface all native failures, preserving the original error object first. */
|
|
84
|
+
assertSuccess(): void;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=hook-refresh-files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-refresh-files.d.ts","sourceRoot":"","sources":["../../src/cli/hook-refresh-files.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKhE,4EAA4E;AAC5E,MAAM,WAAW,GAAG;IAClB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;CAChC;AAYD;;;GAGG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAIvD;AAED;;;;GAIG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CA+BrD;AAED,kFAAkF;AAClF,qBAAa,QAAQ;IAYjB,QAAQ,CAAC,MAAM,EAAE,GAAG;IACpB,QAAQ,CAAC,IAAI,EAAE,GAAG;IAClB,QAAQ,CAAC,WAAW,EAAE,MAAM;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM;IAd5B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,YAAY,CAAS;IAE7B;;;;;;OAMG;gBAEQ,MAAM,EAAE,GAAG,EACX,IAAI,EAAE,GAAG,EACT,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM;IAM5B;;;OAGG;IACG,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+B3C,4EAA4E;YAC9D,eAAe;IAY7B;;;OAGG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMxC,kFAAkF;IAC5E,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAO/B;AAED,sFAAsF;AACtF,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;IAC1C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;IAE1C;;;;;OAKG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAe3D;;;;;OAKG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAqBjE;;;OAGG;IACH,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI;IAI1B;;;OAGG;IACG,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAWhD,kFAAkF;IAC5E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAU5B,+EAA+E;IAC/E,aAAa,IAAI,IAAI;CAQtB"}
|