betrusted-ed25519-verified/verification/lift-guard.sh

162 lines
8.5 KiB
Bash
Raw Normal View History

verification: derive lift dependencies instead of hand-keeping them Round-9 swept all ten instruments per fork instead of only the two buttons, and found selftest-axgate.sh red in all four. Root cause, identical to the KERNLOG defect fixed the day before: a self-test lifts one phase out of check.sh and supplies its variables from a HAND-WRITTEN preamble, and the phase later grew a dependency nobody told the preamble about. Phase 2b stopped globbing Proofs/*.olean and started reading $PROOFS by membership. Bash does not error on an unset array under `set -u` — it expands to nothing — so the gate silently received expected := [".olean"] one entry with an empty name, and rejected the baseline via its own fail-closed absence check. Exit code right, reason wrong. · lift-guard.sh (new, pinned): derives every variable the lifted payload reads, subtracts what the driver defines, and fails AT LIFT TIME naming the remainder. Wired into all five lifting self-tests; selftest-shapes.sh drops its inline copy. One implementation, not five, since drifting out of sync is the entire failure mode. · selftest-axgate.sh: lifts PROOFS verbatim, and its case 2 now poisons a MANIFESTED leaf module rather than adding a stray file. The stray-file attack was a no-op against membership semantics — that is the dead-file gate's job, proven by selftest-harness case 8 — and it is also the weaker attack, since adding files to Proofs/ must clear a gate that editing an already-manifested module does not. · Phase 2c's accounting identity moves behind its own marker with its own ACCTFAIL verdict, so the phase is liftable by construction rather than by a self-test knowing where to stop. Truncating the lift range alone would have dropped Phase 2c's own verdict and left the test unable to fail. · The other four lifting self-tests assert on the PAYLOAD rather than the assembled driver, so a marker in the preamble cannot satisfy a check meant to prove the lift landed. Certified: 8/8 self-tests green in all four forks (~212 assertions, none failing); check.sh bytes unchanged and still matching the pin written before the 3h08m round-9 sweep, whose 36 GREEN button rows therefore stand. --audit-only re-run against the new HARNESS.sha256 in every fork. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 11:07:28 +00:00
#!/usr/bin/env bash
# lift-guard.sh <payload> <driver> [<phase-label>]
#
lift-guard: close all nine classes the reviewer demonstrated Round-8 review (Claude, register key `lift-guard-regex-both-directions`). Every class reproduced here before fixing, and re-tested after. THREE FALSE NEGATIVES — the payload reads a name and the guard stayed silent, which is the direction that costs something, because silence is what the tool exists to prevent: echo $((X + 1)) arithmetic expansion reads X without a `$` before the (( Y > 0 )) name, and the read pattern cannot match it: the character after `$` is `(`. Both contexts are now tokenised. `if [ $((inm + ins)) -eq 0 ]` is already live at check.sh:464 — not lifted today, so latent, not absent. n=Q; ${!n} indirect expansion defeats text analysis outright. The guard now REFUSES the lift rather than passing it. Its contract is "does not miss a dependency"; where it cannot honour that it must say so, not shrug. SIX FALSE POSITIVES — the driver defines the name and the guard cried wolf. This direction matters too: a guard that raises false alarms gets edited away, and then it guards nothing. case x in a) FOO=1 ;; `)` added to the assignment delimiters if …; else FOO=1; fi `else` added ! FOO=1 `!` added mapfile -t FOO binds a name with no `=` at all readarray -t FOO likewise printf -v FOO "x" likewise The banner also over-claimed. It read as a completeness statement about LIFTING; it is a completeness statement about VARIABLES. A lifted payload also inherits functions, shell options, traps and a working directory, and this tool models none of them — loud failures under `set -e`, but the header now says so rather than implying otherwise. Verified: all nine classes behave correctly, a genuine missing variable is still caught by name, and the seven lifting self-tests pass in dalek plus the four fast ones in each ported fork. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-03 11:14:20 +00:00
# Every VARIABLE the LIFTED PAYLOAD reads must be one the DRIVER defines.
#
# VARIABLES ONLY — and the emphasis is a round-8 correction (Claude, N1). A
# lifted payload also inherits FUNCTIONS, shell options, traps and a working
# directory from the script it was cut out of. This tool models none of those.
# A lifted phase calling a function defined in a neighbouring phase fails with
# `command not found`, loud under `set -e`, which is why it is not urgent; but
# the banner used to read as a completeness claim about lifting and it is a
# completeness claim about variables.
#
verification: derive lift dependencies instead of hand-keeping them Round-9 swept all ten instruments per fork instead of only the two buttons, and found selftest-axgate.sh red in all four. Root cause, identical to the KERNLOG defect fixed the day before: a self-test lifts one phase out of check.sh and supplies its variables from a HAND-WRITTEN preamble, and the phase later grew a dependency nobody told the preamble about. Phase 2b stopped globbing Proofs/*.olean and started reading $PROOFS by membership. Bash does not error on an unset array under `set -u` — it expands to nothing — so the gate silently received expected := [".olean"] one entry with an empty name, and rejected the baseline via its own fail-closed absence check. Exit code right, reason wrong. · lift-guard.sh (new, pinned): derives every variable the lifted payload reads, subtracts what the driver defines, and fails AT LIFT TIME naming the remainder. Wired into all five lifting self-tests; selftest-shapes.sh drops its inline copy. One implementation, not five, since drifting out of sync is the entire failure mode. · selftest-axgate.sh: lifts PROOFS verbatim, and its case 2 now poisons a MANIFESTED leaf module rather than adding a stray file. The stray-file attack was a no-op against membership semantics — that is the dead-file gate's job, proven by selftest-harness case 8 — and it is also the weaker attack, since adding files to Proofs/ must clear a gate that editing an already-manifested module does not. · Phase 2c's accounting identity moves behind its own marker with its own ACCTFAIL verdict, so the phase is liftable by construction rather than by a self-test knowing where to stop. Truncating the lift range alone would have dropped Phase 2c's own verdict and left the test unable to fail. · The other four lifting self-tests assert on the PAYLOAD rather than the assembled driver, so a marker in the preamble cannot satisfy a check meant to prove the lift landed. Certified: 8/8 self-tests green in all four forks (~212 assertions, none failing); check.sh bytes unchanged and still matching the pin written before the 3h08m round-9 sweep, whose 36 GREEN button rows therefore stand. --audit-only re-run against the new HARNESS.sha256 in every fork. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 11:07:28 +00:00
# Prints the offending names and exits 1 if any are missing.
#
# ───────────────────────────────────────────────────────────────────────────
# WHY THIS EXISTS — 2026-08-02
#
# Five of this repository's self-tests work by lifting one phase out of
# check.sh and running it standalone against a deliberately corrupted tree.
# That is the right design: the test then attacks the SHIPPING gate rather
# than a re-implementation of it. But a lifted phase is a fragment, and it
# reads variables its neighbours defined. Each self-test therefore carries a
# hand-written preamble supplying them.
#
# A hand-written preamble is a hand-kept list, and hand-kept lists drift. Twice
# in two days a phase grew a dependency and no preamble was told:
#
# · Phase 2c grew an accounting block reading $KERNLOG, a file Phase 2b
# creates. selftest-shapes.sh died on its first expansion under `set -u`.
# It could not pass on any fork from the moment that block was added.
#
# · Phase 2b changed from globbing Proofs/*.lean to reading the $PROOFS
# membership manifest — the spelling-versus-ownership fix ScalarPackSpec
# forced. selftest-axgate.sh's preamble was never told. Bash does NOT
# error on an unset array expansion under `set -u`; it expands to nothing,
# so `printf '"%s.olean", ' "${PROOFS[@]}"` silently produced
# expected := [".olean"]
# — one entry, empty name — and the gate's own fail-closed absence check
# rejected it. The baseline went red and both attack cases were then
# rejected for the WRONG REASON.
#
# Both failed loudly rather than passing vacuously, which is the only reason
# they were not false assurance. That is luck, not design: a missing variable
# that happens to make an ATTACK case die still looks like the attack being
# caught, and only the substring assertions in each `expect` helper stand
# between that and a green test measuring nothing.
#
# The fix for the CLASS is to stop maintaining the list by hand. This tool
# derives the requirement from the two artifacts themselves, so a phase that
# grows a new dependency fails AT LIFT TIME, naming it, instead of dying
# mid-run or — worse — passing for the wrong reason.
#
lift-guard: close all nine classes the reviewer demonstrated Round-8 review (Claude, register key `lift-guard-regex-both-directions`). Every class reproduced here before fixing, and re-tested after. THREE FALSE NEGATIVES — the payload reads a name and the guard stayed silent, which is the direction that costs something, because silence is what the tool exists to prevent: echo $((X + 1)) arithmetic expansion reads X without a `$` before the (( Y > 0 )) name, and the read pattern cannot match it: the character after `$` is `(`. Both contexts are now tokenised. `if [ $((inm + ins)) -eq 0 ]` is already live at check.sh:464 — not lifted today, so latent, not absent. n=Q; ${!n} indirect expansion defeats text analysis outright. The guard now REFUSES the lift rather than passing it. Its contract is "does not miss a dependency"; where it cannot honour that it must say so, not shrug. SIX FALSE POSITIVES — the driver defines the name and the guard cried wolf. This direction matters too: a guard that raises false alarms gets edited away, and then it guards nothing. case x in a) FOO=1 ;; `)` added to the assignment delimiters if …; else FOO=1; fi `else` added ! FOO=1 `!` added mapfile -t FOO binds a name with no `=` at all readarray -t FOO likewise printf -v FOO "x" likewise The banner also over-claimed. It read as a completeness statement about LIFTING; it is a completeness statement about VARIABLES. A lifted payload also inherits functions, shell options, traps and a working directory, and this tool models none of them — loud failures under `set -e`, but the header now says so rather than implying otherwise. Verified: all nine classes behave correctly, a genuine missing variable is still caught by name, and the seven lifting self-tests pass in dalek plus the four fast ones in each ported fork. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-03 11:14:20 +00:00
# WHAT IT IS NOT. This is a shell-text approximation, not a bash parser. It
# still cannot see a name built at runtime or passed through `eval`, and it
# models variables only — not functions, shell options, traps or the working
# directory a lifted phase also inherits. It is a tripwire on failure modes
# that actually occurred, not a proof of closure.
#
# Where it CANNOT bound the reads it refuses rather than staying silent:
# indirect expansion (`${!name}`) is detected and fails the lift. That is the
# round-8 correction — a guard whose contract is "does not miss a dependency"
# must say so when it cannot honour it, instead of shrugging.
verification: derive lift dependencies instead of hand-keeping them Round-9 swept all ten instruments per fork instead of only the two buttons, and found selftest-axgate.sh red in all four. Root cause, identical to the KERNLOG defect fixed the day before: a self-test lifts one phase out of check.sh and supplies its variables from a HAND-WRITTEN preamble, and the phase later grew a dependency nobody told the preamble about. Phase 2b stopped globbing Proofs/*.olean and started reading $PROOFS by membership. Bash does not error on an unset array under `set -u` — it expands to nothing — so the gate silently received expected := [".olean"] one entry with an empty name, and rejected the baseline via its own fail-closed absence check. Exit code right, reason wrong. · lift-guard.sh (new, pinned): derives every variable the lifted payload reads, subtracts what the driver defines, and fails AT LIFT TIME naming the remainder. Wired into all five lifting self-tests; selftest-shapes.sh drops its inline copy. One implementation, not five, since drifting out of sync is the entire failure mode. · selftest-axgate.sh: lifts PROOFS verbatim, and its case 2 now poisons a MANIFESTED leaf module rather than adding a stray file. The stray-file attack was a no-op against membership semantics — that is the dead-file gate's job, proven by selftest-harness case 8 — and it is also the weaker attack, since adding files to Proofs/ must clear a gate that editing an already-manifested module does not. · Phase 2c's accounting identity moves behind its own marker with its own ACCTFAIL verdict, so the phase is liftable by construction rather than by a self-test knowing where to stop. Truncating the lift range alone would have dropped Phase 2c's own verdict and left the test unable to fail. · The other four lifting self-tests assert on the PAYLOAD rather than the assembled driver, so a marker in the preamble cannot satisfy a check meant to prove the lift landed. Certified: 8/8 self-tests green in all four forks (~212 assertions, none failing); check.sh bytes unchanged and still matching the pin written before the 3h08m round-9 sweep, whose 36 GREEN button rows therefore stand. --audit-only re-run against the new HARNESS.sha256 in every fork. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 11:07:28 +00:00
# ───────────────────────────────────────────────────────────────────────────
set -euo pipefail
PAYLOAD="${1:?usage: lift-guard.sh <payload> <driver> [phase-label]}"
DRIVER="${2:?usage: lift-guard.sh <payload> <driver> [phase-label]}"
LABEL="${3:-the lifted phase}"
for f in "$PAYLOAD" "$DRIVER"; do
[ -s "$f" ] || { echo "FATAL: lift-guard: '$f' is missing or empty."; exit 1; }
done
UNBOUND=$(python3 - "$PAYLOAD" "$DRIVER" <<'PYGUARD'
import re, sys
payload = open(sys.argv[1]).read()
driver = open(sys.argv[2]).read()
# What the payload READS. Deliberately over-approximates: a name mentioned in a
# comment costs one lifted definition, a name missed costs a broken self-test.
reads = set(re.findall(r'\$\{?([A-Za-z_][A-Za-z0-9_]*)', payload))
lift-guard: close all nine classes the reviewer demonstrated Round-8 review (Claude, register key `lift-guard-regex-both-directions`). Every class reproduced here before fixing, and re-tested after. THREE FALSE NEGATIVES — the payload reads a name and the guard stayed silent, which is the direction that costs something, because silence is what the tool exists to prevent: echo $((X + 1)) arithmetic expansion reads X without a `$` before the (( Y > 0 )) name, and the read pattern cannot match it: the character after `$` is `(`. Both contexts are now tokenised. `if [ $((inm + ins)) -eq 0 ]` is already live at check.sh:464 — not lifted today, so latent, not absent. n=Q; ${!n} indirect expansion defeats text analysis outright. The guard now REFUSES the lift rather than passing it. Its contract is "does not miss a dependency"; where it cannot honour that it must say so, not shrug. SIX FALSE POSITIVES — the driver defines the name and the guard cried wolf. This direction matters too: a guard that raises false alarms gets edited away, and then it guards nothing. case x in a) FOO=1 ;; `)` added to the assignment delimiters if …; else FOO=1; fi `else` added ! FOO=1 `!` added mapfile -t FOO binds a name with no `=` at all readarray -t FOO likewise printf -v FOO "x" likewise The banner also over-claimed. It read as a completeness statement about LIFTING; it is a completeness statement about VARIABLES. A lifted payload also inherits functions, shell options, traps and a working directory, and this tool models none of them — loud failures under `set -e`, but the header now says so rather than implying otherwise. Verified: all nine classes behave correctly, a genuine missing variable is still caught by name, and the seven lifting self-tests pass in dalek plus the four fast ones in each ported fork. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-03 11:14:20 +00:00
# ARITHMETIC CONTEXTS READ NAMES WITHOUT A `$`. Round-8 review (Claude, N1):
# echo $((X + 1)) reads X
# (( Y > 0 )) && ... reads Y
# and the pattern above cannot see either, because the character after `$` is
# `(`. This is the guard's own failure mode — a phase growing a dependency the
# guard is blind to — and `if [ $((inm + ins)) -eq 0 ]` is already live in
# check.sh's Phase 1b. Not lifted today, which made it latent, not absent.
for expr in (re.findall(r'\$\(\((.*?)\)\)', payload, re.S)
+ re.findall(r'(?<!\$)\(\((.*?)\)\)', payload, re.S)):
for tok in re.findall(r'[A-Za-z_][A-Za-z0-9_]*', expr):
reads.add(tok)
verification: derive lift dependencies instead of hand-keeping them Round-9 swept all ten instruments per fork instead of only the two buttons, and found selftest-axgate.sh red in all four. Root cause, identical to the KERNLOG defect fixed the day before: a self-test lifts one phase out of check.sh and supplies its variables from a HAND-WRITTEN preamble, and the phase later grew a dependency nobody told the preamble about. Phase 2b stopped globbing Proofs/*.olean and started reading $PROOFS by membership. Bash does not error on an unset array under `set -u` — it expands to nothing — so the gate silently received expected := [".olean"] one entry with an empty name, and rejected the baseline via its own fail-closed absence check. Exit code right, reason wrong. · lift-guard.sh (new, pinned): derives every variable the lifted payload reads, subtracts what the driver defines, and fails AT LIFT TIME naming the remainder. Wired into all five lifting self-tests; selftest-shapes.sh drops its inline copy. One implementation, not five, since drifting out of sync is the entire failure mode. · selftest-axgate.sh: lifts PROOFS verbatim, and its case 2 now poisons a MANIFESTED leaf module rather than adding a stray file. The stray-file attack was a no-op against membership semantics — that is the dead-file gate's job, proven by selftest-harness case 8 — and it is also the weaker attack, since adding files to Proofs/ must clear a gate that editing an already-manifested module does not. · Phase 2c's accounting identity moves behind its own marker with its own ACCTFAIL verdict, so the phase is liftable by construction rather than by a self-test knowing where to stop. Truncating the lift range alone would have dropped Phase 2c's own verdict and left the test unable to fail. · The other four lifting self-tests assert on the PAYLOAD rather than the assembled driver, so a marker in the preamble cannot satisfy a check meant to prove the lift landed. Certified: 8/8 self-tests green in all four forks (~212 assertions, none failing); check.sh bytes unchanged and still matching the pin written before the 3h08m round-9 sweep, whose 36 GREEN button rows therefore stand. --audit-only re-run against the new HARNESS.sha256 in every fork. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 11:07:28 +00:00
# What the DRIVER defines, in every form these scripts actually use.
lift-guard: close all nine classes the reviewer demonstrated Round-8 review (Claude, register key `lift-guard-regex-both-directions`). Every class reproduced here before fixing, and re-tested after. THREE FALSE NEGATIVES — the payload reads a name and the guard stayed silent, which is the direction that costs something, because silence is what the tool exists to prevent: echo $((X + 1)) arithmetic expansion reads X without a `$` before the (( Y > 0 )) name, and the read pattern cannot match it: the character after `$` is `(`. Both contexts are now tokenised. `if [ $((inm + ins)) -eq 0 ]` is already live at check.sh:464 — not lifted today, so latent, not absent. n=Q; ${!n} indirect expansion defeats text analysis outright. The guard now REFUSES the lift rather than passing it. Its contract is "does not miss a dependency"; where it cannot honour that it must say so, not shrug. SIX FALSE POSITIVES — the driver defines the name and the guard cried wolf. This direction matters too: a guard that raises false alarms gets edited away, and then it guards nothing. case x in a) FOO=1 ;; `)` added to the assignment delimiters if …; else FOO=1; fi `else` added ! FOO=1 `!` added mapfile -t FOO binds a name with no `=` at all readarray -t FOO likewise printf -v FOO "x" likewise The banner also over-claimed. It read as a completeness statement about LIFTING; it is a completeness statement about VARIABLES. A lifted payload also inherits functions, shell options, traps and a working directory, and this tool models none of them — loud failures under `set -e`, but the header now says so rather than implying otherwise. Verified: all nine classes behave correctly, a genuine missing variable is still caught by name, and the seven lifting self-tests pass in dalek plus the four fast ones in each ported fork. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-03 11:14:20 +00:00
# `TIMEOUT=$T; CORES="$C"` is one line with two assignments, and a
# start-anchored pattern sees only the first.
# An assignment may open a line or follow `;`, `&&`, `||`, `then`, `do`, `{`,
# and — round-8 review (Claude, N1) — `else`, a `case` branch's `)`, and `!`.
# Six false-positive classes were demonstrated. A guard that cries wolf gets
# edited away, so over-strictness here is not the safe direction.
verification: derive lift dependencies instead of hand-keeping them Round-9 swept all ten instruments per fork instead of only the two buttons, and found selftest-axgate.sh red in all four. Root cause, identical to the KERNLOG defect fixed the day before: a self-test lifts one phase out of check.sh and supplies its variables from a HAND-WRITTEN preamble, and the phase later grew a dependency nobody told the preamble about. Phase 2b stopped globbing Proofs/*.olean and started reading $PROOFS by membership. Bash does not error on an unset array under `set -u` — it expands to nothing — so the gate silently received expected := [".olean"] one entry with an empty name, and rejected the baseline via its own fail-closed absence check. Exit code right, reason wrong. · lift-guard.sh (new, pinned): derives every variable the lifted payload reads, subtracts what the driver defines, and fails AT LIFT TIME naming the remainder. Wired into all five lifting self-tests; selftest-shapes.sh drops its inline copy. One implementation, not five, since drifting out of sync is the entire failure mode. · selftest-axgate.sh: lifts PROOFS verbatim, and its case 2 now poisons a MANIFESTED leaf module rather than adding a stray file. The stray-file attack was a no-op against membership semantics — that is the dead-file gate's job, proven by selftest-harness case 8 — and it is also the weaker attack, since adding files to Proofs/ must clear a gate that editing an already-manifested module does not. · Phase 2c's accounting identity moves behind its own marker with its own ACCTFAIL verdict, so the phase is liftable by construction rather than by a self-test knowing where to stop. Truncating the lift range alone would have dropped Phase 2c's own verdict and left the test unable to fail. · The other four lifting self-tests assert on the PAYLOAD rather than the assembled driver, so a marker in the preamble cannot satisfy a check meant to prove the lift landed. Certified: 8/8 self-tests green in all four forks (~212 assertions, none failing); check.sh bytes unchanged and still matching the pin written before the 3h08m round-9 sweep, whose 36 GREEN button rows therefore stand. --audit-only re-run against the new HARNESS.sha256 in every fork. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 11:07:28 +00:00
assigns = set(re.findall(
lift-guard: close all nine classes the reviewer demonstrated Round-8 review (Claude, register key `lift-guard-regex-both-directions`). Every class reproduced here before fixing, and re-tested after. THREE FALSE NEGATIVES — the payload reads a name and the guard stayed silent, which is the direction that costs something, because silence is what the tool exists to prevent: echo $((X + 1)) arithmetic expansion reads X without a `$` before the (( Y > 0 )) name, and the read pattern cannot match it: the character after `$` is `(`. Both contexts are now tokenised. `if [ $((inm + ins)) -eq 0 ]` is already live at check.sh:464 — not lifted today, so latent, not absent. n=Q; ${!n} indirect expansion defeats text analysis outright. The guard now REFUSES the lift rather than passing it. Its contract is "does not miss a dependency"; where it cannot honour that it must say so, not shrug. SIX FALSE POSITIVES — the driver defines the name and the guard cried wolf. This direction matters too: a guard that raises false alarms gets edited away, and then it guards nothing. case x in a) FOO=1 ;; `)` added to the assignment delimiters if …; else FOO=1; fi `else` added ! FOO=1 `!` added mapfile -t FOO binds a name with no `=` at all readarray -t FOO likewise printf -v FOO "x" likewise The banner also over-claimed. It read as a completeness statement about LIFTING; it is a completeness statement about VARIABLES. A lifted payload also inherits functions, shell options, traps and a working directory, and this tool models none of them — loud failures under `set -e`, but the header now says so rather than implying otherwise. Verified: all nine classes behave correctly, a genuine missing variable is still caught by name, and the seven lifting self-tests pass in dalek plus the four fast ones in each ported fork. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-03 11:14:20 +00:00
r'(?:^|;|&&|\|\||\)|!|\bthen\b|\bdo\b|\belse\b|\{)\s*'
r'([A-Za-z_][A-Za-z0-9_]*)=', driver, re.M))
# `mapfile`/`readarray` and `printf -v` bind a name without an `=` at all.
assigns |= set(re.findall(
r'\b(?:mapfile|readarray)\b(?:\s+-[A-Za-z]\s*\S*)*\s+([A-Za-z_][A-Za-z0-9_]*)',
driver))
assigns |= set(re.findall(r'\bprintf\b[^\n]*?\s-v\s+([A-Za-z_][A-Za-z0-9_]*)', driver))
verification: derive lift dependencies instead of hand-keeping them Round-9 swept all ten instruments per fork instead of only the two buttons, and found selftest-axgate.sh red in all four. Root cause, identical to the KERNLOG defect fixed the day before: a self-test lifts one phase out of check.sh and supplies its variables from a HAND-WRITTEN preamble, and the phase later grew a dependency nobody told the preamble about. Phase 2b stopped globbing Proofs/*.olean and started reading $PROOFS by membership. Bash does not error on an unset array under `set -u` — it expands to nothing — so the gate silently received expected := [".olean"] one entry with an empty name, and rejected the baseline via its own fail-closed absence check. Exit code right, reason wrong. · lift-guard.sh (new, pinned): derives every variable the lifted payload reads, subtracts what the driver defines, and fails AT LIFT TIME naming the remainder. Wired into all five lifting self-tests; selftest-shapes.sh drops its inline copy. One implementation, not five, since drifting out of sync is the entire failure mode. · selftest-axgate.sh: lifts PROOFS verbatim, and its case 2 now poisons a MANIFESTED leaf module rather than adding a stray file. The stray-file attack was a no-op against membership semantics — that is the dead-file gate's job, proven by selftest-harness case 8 — and it is also the weaker attack, since adding files to Proofs/ must clear a gate that editing an already-manifested module does not. · Phase 2c's accounting identity moves behind its own marker with its own ACCTFAIL verdict, so the phase is liftable by construction rather than by a self-test knowing where to stop. Truncating the lift range alone would have dropped Phase 2c's own verdict and left the test unable to fail. · The other four lifting self-tests assert on the PAYLOAD rather than the assembled driver, so a marker in the preamble cannot satisfy a check meant to prove the lift landed. Certified: 8/8 self-tests green in all four forks (~212 assertions, none failing); check.sh bytes unchanged and still matching the pin written before the 3h08m round-9 sweep, whose 36 GREEN button rows therefore stand. --audit-only re-run against the new HARNESS.sha256 in every fork. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 11:07:28 +00:00
assigns |= set(re.findall(r'\b(?:export|declare|local|readonly)\s+(?:-\w+\s+)*'
r'([A-Za-z_][A-Za-z0-9_]*)', driver))
assigns |= set(re.findall(r'\bfor\s+([A-Za-z_][A-Za-z0-9_]*)\s+in\b', driver))
# `read` binds names too, and it is almost never at end of line: the shape that
# matters here is `while read -r n; do`. An end-anchored pattern misses it and
# the guard then demands a definition for a loop variable the payload binds
# itself — a false alarm, which is the one failure a guard cannot afford.
for m in re.finditer(r'\bread\b((?:\s+-\w+)*(?:\s+[A-Za-z_][A-Za-z0-9_]*)+)', driver):
assigns |= set(re.findall(r'[A-Za-z_][A-Za-z0-9_]*', m.group(1)))
# Names the driver INHERITS rather than defines: the shell's own, and the ones
# `source ~/aeneas-toolchain/env.sh` puts in the environment. Keep this list
# short and justified — every entry is a hole in the guard.
ENV = {'PWD', 'HOME', 'PATH', 'IFS', 'PIPESTATUS', 'BASH_SOURCE', 'FUNCNAME',
'LINENO', 'RANDOM', 'SECONDS', 'OSTYPE', 'HOSTNAME', 'USER', 'SHELL',
'TMPDIR', 'LC_ALL', 'LANG', 'BASH_REMATCH', 'REPLY', 'PS4',
'AENEAS_HOME', 'LEAN_PATH', 'LEAN_MEM_MB', 'LEAN_TIMEOUT',
'LEAN_MAX_CORES'}
lift-guard: close all nine classes the reviewer demonstrated Round-8 review (Claude, register key `lift-guard-regex-both-directions`). Every class reproduced here before fixing, and re-tested after. THREE FALSE NEGATIVES — the payload reads a name and the guard stayed silent, which is the direction that costs something, because silence is what the tool exists to prevent: echo $((X + 1)) arithmetic expansion reads X without a `$` before the (( Y > 0 )) name, and the read pattern cannot match it: the character after `$` is `(`. Both contexts are now tokenised. `if [ $((inm + ins)) -eq 0 ]` is already live at check.sh:464 — not lifted today, so latent, not absent. n=Q; ${!n} indirect expansion defeats text analysis outright. The guard now REFUSES the lift rather than passing it. Its contract is "does not miss a dependency"; where it cannot honour that it must say so, not shrug. SIX FALSE POSITIVES — the driver defines the name and the guard cried wolf. This direction matters too: a guard that raises false alarms gets edited away, and then it guards nothing. case x in a) FOO=1 ;; `)` added to the assignment delimiters if …; else FOO=1; fi `else` added ! FOO=1 `!` added mapfile -t FOO binds a name with no `=` at all readarray -t FOO likewise printf -v FOO "x" likewise The banner also over-claimed. It read as a completeness statement about LIFTING; it is a completeness statement about VARIABLES. A lifted payload also inherits functions, shell options, traps and a working directory, and this tool models none of them — loud failures under `set -e`, but the header now says so rather than implying otherwise. Verified: all nine classes behave correctly, a genuine missing variable is still caught by name, and the seven lifting self-tests pass in dalek plus the four fast ones in each ported fork. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-03 11:14:20 +00:00
# INDIRECT EXPANSION DEFEATS TEXT ANALYSIS, so say so instead of staying
# silent. `n=Q; echo "${!n}"` reads Q, and no amount of pattern-matching
# recovers that from the source. The guard's contract is that it does not miss
# a dependency; where it cannot honour that it must refuse, not shrug.
if re.search(r'\$\{!', payload):
print('INDIRECT-EXPANSION')
else:
print(' '.join(sorted(n for n in reads - assigns - ENV if not n.isdigit())))
verification: derive lift dependencies instead of hand-keeping them Round-9 swept all ten instruments per fork instead of only the two buttons, and found selftest-axgate.sh red in all four. Root cause, identical to the KERNLOG defect fixed the day before: a self-test lifts one phase out of check.sh and supplies its variables from a HAND-WRITTEN preamble, and the phase later grew a dependency nobody told the preamble about. Phase 2b stopped globbing Proofs/*.olean and started reading $PROOFS by membership. Bash does not error on an unset array under `set -u` — it expands to nothing — so the gate silently received expected := [".olean"] one entry with an empty name, and rejected the baseline via its own fail-closed absence check. Exit code right, reason wrong. · lift-guard.sh (new, pinned): derives every variable the lifted payload reads, subtracts what the driver defines, and fails AT LIFT TIME naming the remainder. Wired into all five lifting self-tests; selftest-shapes.sh drops its inline copy. One implementation, not five, since drifting out of sync is the entire failure mode. · selftest-axgate.sh: lifts PROOFS verbatim, and its case 2 now poisons a MANIFESTED leaf module rather than adding a stray file. The stray-file attack was a no-op against membership semantics — that is the dead-file gate's job, proven by selftest-harness case 8 — and it is also the weaker attack, since adding files to Proofs/ must clear a gate that editing an already-manifested module does not. · Phase 2c's accounting identity moves behind its own marker with its own ACCTFAIL verdict, so the phase is liftable by construction rather than by a self-test knowing where to stop. Truncating the lift range alone would have dropped Phase 2c's own verdict and left the test unable to fail. · The other four lifting self-tests assert on the PAYLOAD rather than the assembled driver, so a marker in the preamble cannot satisfy a check meant to prove the lift landed. Certified: 8/8 self-tests green in all four forks (~212 assertions, none failing); check.sh bytes unchanged and still matching the pin written before the 3h08m round-9 sweep, whose 36 GREEN button rows therefore stand. --audit-only re-run against the new HARNESS.sha256 in every fork. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 11:07:28 +00:00
PYGUARD
)
lift-guard: close all nine classes the reviewer demonstrated Round-8 review (Claude, register key `lift-guard-regex-both-directions`). Every class reproduced here before fixing, and re-tested after. THREE FALSE NEGATIVES — the payload reads a name and the guard stayed silent, which is the direction that costs something, because silence is what the tool exists to prevent: echo $((X + 1)) arithmetic expansion reads X without a `$` before the (( Y > 0 )) name, and the read pattern cannot match it: the character after `$` is `(`. Both contexts are now tokenised. `if [ $((inm + ins)) -eq 0 ]` is already live at check.sh:464 — not lifted today, so latent, not absent. n=Q; ${!n} indirect expansion defeats text analysis outright. The guard now REFUSES the lift rather than passing it. Its contract is "does not miss a dependency"; where it cannot honour that it must say so, not shrug. SIX FALSE POSITIVES — the driver defines the name and the guard cried wolf. This direction matters too: a guard that raises false alarms gets edited away, and then it guards nothing. case x in a) FOO=1 ;; `)` added to the assignment delimiters if …; else FOO=1; fi `else` added ! FOO=1 `!` added mapfile -t FOO binds a name with no `=` at all readarray -t FOO likewise printf -v FOO "x" likewise The banner also over-claimed. It read as a completeness statement about LIFTING; it is a completeness statement about VARIABLES. A lifted payload also inherits functions, shell options, traps and a working directory, and this tool models none of them — loud failures under `set -e`, but the header now says so rather than implying otherwise. Verified: all nine classes behave correctly, a genuine missing variable is still caught by name, and the seven lifting self-tests pass in dalek plus the four fast ones in each ported fork. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-03 11:14:20 +00:00
if [ "$UNBOUND" = "INDIRECT-EXPANSION" ]; then
cat <<EOF
FATAL: $LABEL uses indirect expansion (\${!name}).
The set of variables it reads cannot be derived from its text, so this
guard cannot certify that the lift carries them. Rewrite the phase
without indirection, or lift it with a driver that is known-complete by
other means and say so in the self-test.
EOF
exit 1
fi
verification: derive lift dependencies instead of hand-keeping them Round-9 swept all ten instruments per fork instead of only the two buttons, and found selftest-axgate.sh red in all four. Root cause, identical to the KERNLOG defect fixed the day before: a self-test lifts one phase out of check.sh and supplies its variables from a HAND-WRITTEN preamble, and the phase later grew a dependency nobody told the preamble about. Phase 2b stopped globbing Proofs/*.olean and started reading $PROOFS by membership. Bash does not error on an unset array under `set -u` — it expands to nothing — so the gate silently received expected := [".olean"] one entry with an empty name, and rejected the baseline via its own fail-closed absence check. Exit code right, reason wrong. · lift-guard.sh (new, pinned): derives every variable the lifted payload reads, subtracts what the driver defines, and fails AT LIFT TIME naming the remainder. Wired into all five lifting self-tests; selftest-shapes.sh drops its inline copy. One implementation, not five, since drifting out of sync is the entire failure mode. · selftest-axgate.sh: lifts PROOFS verbatim, and its case 2 now poisons a MANIFESTED leaf module rather than adding a stray file. The stray-file attack was a no-op against membership semantics — that is the dead-file gate's job, proven by selftest-harness case 8 — and it is also the weaker attack, since adding files to Proofs/ must clear a gate that editing an already-manifested module does not. · Phase 2c's accounting identity moves behind its own marker with its own ACCTFAIL verdict, so the phase is liftable by construction rather than by a self-test knowing where to stop. Truncating the lift range alone would have dropped Phase 2c's own verdict and left the test unable to fail. · The other four lifting self-tests assert on the PAYLOAD rather than the assembled driver, so a marker in the preamble cannot satisfy a check meant to prove the lift landed. Certified: 8/8 self-tests green in all four forks (~212 assertions, none failing); check.sh bytes unchanged and still matching the pin written before the 3h08m round-9 sweep, whose 36 GREEN button rows therefore stand. --audit-only re-run against the new HARNESS.sha256 in every fork. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 11:07:28 +00:00
if [ -n "$UNBOUND" ]; then
cat <<EOF
FATAL: $LABEL reads variables this lift does not define: $UNBOUND
Either lift their definitions too — VERBATIM from check.sh, with a
sed range, so this test cannot drift away from the button's idea of
them — or end the lift range before the block that uses them.
Do NOT stub them: a stub makes the test measure something the button
never runs, which is how a self-test becomes decoration.
EOF
exit 1
fi