mirror of
https://github.com/saymrwulf/ltl-accumulator-verified.git
synced 2026-09-03 19:53:48 +00:00
verification: guard the accumulator's two lifts (port lift-guard, assert on the payload)
The four forks ship lift-guard.sh and call it from eight files each. This
repository had no lift-guard.sh at all and called it zero times, while lifting
Phase 0c (selftest-harness.sh) and Phase 3d (selftest_statements.sh). Its lifts
got a line-count sanity check and nothing else — no analysis of which variables
the payload READS against which the driver DEFINES, and after yesterday's fix
no errexit check either. It was the one place that class could still recur.
A SECOND defect surfaced while porting, and it is the more interesting one.
Both lifts assembled the driver inline, with no payload file anywhere:
{ echo 'set -euo pipefail'; echo "HERE=..."; sed -n '...' check.sh } > DRIVER
if [ "$(wc -l < "$DRIVER")" -lt 40 ]; then ... refuse ...
so the size check measured the CONCATENATED DRIVER — bindings included. A lift
that recovered almost nothing still cleared the threshold on the strength of
the lines the test itself prepended. That is exactly the defect round-7
lift-guard-payload-assert reported and that the forks closed with the words
"all six lift sites now assert on payload.sh". Six counted the forks. These two
were never in the count, so a finding recorded as closed was measuring the
wrong population — the same error this estate keeps finding in new costumes.
Both sites now write payload.sh / payload3d.sh and assert on those.
lift-guard.sh is byte-identical to the forks' (4f61c113). It is executable, so
this repository's own self-deriving Phase 0c rule required a pin before it
would run at all: required set == pinned set, verified.
WIRING PROVEN RATHER THAN ASSUMED. With lift-guard.sh stubbed to exit 1, each
self-test halts AT the guard, naming its phase, before a single attack case
runs. Guard restored, pins re-verified.
Certified: button + all three self-tests GREEN (check.sh 86s, selftest_audit
258s, selftest-harness 1s, selftest_statements 34s). The other five
repositories are unchanged from the tree round-15 certified 50/50 and were not
re-run; this commit does not claim they were.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
a0d11b44d9
commit
be4f742b29
4 changed files with 248 additions and 7 deletions
|
|
@ -14,5 +14,6 @@ eda93f520546a692926b2a46bcb79332e1795879e5083327a8bf2404aca5cf87 Proofs/AxiomCh
|
||||||
82d41c634a2e9adfc2cc74c202d025e57411687796f820765d45608f3fb4cb20 Proofs/Inventory.lean
|
82d41c634a2e9adfc2cc74c202d025e57411687796f820765d45608f3fb4cb20 Proofs/Inventory.lean
|
||||||
0b8a0fc6947af1d1e600a756eb2b07dc88d189b21df3220c4501a41be8b33f1e run_bare.sh
|
0b8a0fc6947af1d1e600a756eb2b07dc88d189b21df3220c4501a41be8b33f1e run_bare.sh
|
||||||
473e2463d9c26653c8435ad6758044742f200eb13ea0db4b8f076466c08bd87a selftest_audit.sh
|
473e2463d9c26653c8435ad6758044742f200eb13ea0db4b8f076466c08bd87a selftest_audit.sh
|
||||||
5d2792996d5164022591f116ce9cbd03cdee8c9b886eb4aac6fa49c9de2bac32 selftest-harness.sh
|
733eb2cf4eaf9101433d75dbedfc45681cff44e1dbfdc841e6b9dba350ef1570 selftest-harness.sh
|
||||||
b4a3ea633ac8a38c699e10b55b040718567cb60c5f7c7439c0c9720d4c9f8554 selftest_statements.sh
|
c85d7a0fc39fbf06ec2c85d2f42a65813c475cae7cf60e6d140948215c62eabc selftest_statements.sh
|
||||||
|
4f61c1135f8afc615e686bd267a062229bf349416caebe070eafca510036d1a4 lift-guard.sh
|
||||||
|
|
|
||||||
221
verification/lift-guard.sh
Executable file
221
verification/lift-guard.sh
Executable file
|
|
@ -0,0 +1,221 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# lift-guard.sh <payload> <driver> [<phase-label>]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
# ───────────────────────────────────────────────────────────────────────────
|
||||||
|
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
|
||||||
|
|
||||||
|
# ── The driver must run the phase under the SAME shell options as the button ──
|
||||||
|
# A lift is only evidence about the shipping gate if it executes the way the
|
||||||
|
# shipping gate executes. Every button in this estate runs `set -euo pipefail`.
|
||||||
|
# Eighteen lift sites prefixed their driver with `set -uo pipefail` and no -e
|
||||||
|
# (four per fork, two in the accumulator) while sixteen others got it right, so
|
||||||
|
# the estate did it both ways and the self-tests silently ran a more permissive
|
||||||
|
# shell than the phase they claim to test: without -e a failing command does not
|
||||||
|
# abort, execution continues, and the driver returns the LAST command's status.
|
||||||
|
# A lifted phase can therefore reach a verdict the shipping phase would never
|
||||||
|
# reach, while the self-test reports the gate "works".
|
||||||
|
#
|
||||||
|
# This lives here rather than in each self-test because the same defect appeared
|
||||||
|
# in eighteen places and would return the nineteenth time someone writes a lift.
|
||||||
|
# Checked on the DRIVER, which is what bash actually executes; the payload is
|
||||||
|
# lifted verbatim and carries no `set` line of its own.
|
||||||
|
if ! grep -qE '^[[:space:]]*set[[:space:]]+-[a-z]*e' "$DRIVER"; then
|
||||||
|
echo "FATAL: lift-guard: the driver for $LABEL does not enable errexit."
|
||||||
|
echo " The button runs 'set -euo pipefail'; this driver does not set -e, so"
|
||||||
|
echo " the lifted phase would run past a failure the shipping phase aborts on"
|
||||||
|
echo " and the test would report a verdict the button cannot produce."
|
||||||
|
echo " Driver's shell options:"
|
||||||
|
grep -nE '^[[:space:]]*set[[:space:]]+-' "$DRIVER" | sed 's/^/ /' || echo " (none)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
# 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)):
|
||||||
|
# NOT PRECEDED BY A DIGIT OR WORD CHARACTER. Round-9 review (Claude, N1):
|
||||||
|
# `$((0x1F))` was read as a variable `x1F`, and `$((1e3))` as `e3`, because
|
||||||
|
# the pattern happily starts matching at the letter-bearing tail of a
|
||||||
|
# numeric literal. Two false alarms introduced by the round-8 fix for a
|
||||||
|
# false NEGATIVE — the guard was made to see more and started seeing things
|
||||||
|
# that are not there, which is the failure mode that gets a guard deleted.
|
||||||
|
for tok in re.findall(r'(?<![0-9A-Za-z_])[A-Za-z_][A-Za-z0-9_]*', expr):
|
||||||
|
reads.add(tok)
|
||||||
|
|
||||||
|
# What the DRIVER defines, in every form these scripts actually use.
|
||||||
|
# `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.
|
||||||
|
assigns = set(re.findall(
|
||||||
|
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))
|
||||||
|
# SEVEN MORE BINDING FORMS — round-9 review (Claude, N1). Each was a false
|
||||||
|
# alarm: the driver binds the name and the guard demanded it anyway. Listed in
|
||||||
|
# the order reported, so the next reader can check the list against that report.
|
||||||
|
assigns |= set(re.findall(r'\blet\s+([A-Za-z_][A-Za-z0-9_]*)\s*=', driver))
|
||||||
|
assigns |= set(re.findall(r'\bselect\s+([A-Za-z_][A-Za-z0-9_]*)\s+in\b', driver))
|
||||||
|
assigns |= set(re.findall(r'\bgetopts\b\s+\S+\s+([A-Za-z_][A-Za-z0-9_]*)', driver))
|
||||||
|
assigns |= set(re.findall(r'(?:^|;|&&|\|\||\)|!|\bthen\b|\bdo\b|\belse\b|\{)\s*'
|
||||||
|
r'([A-Za-z_][A-Za-z0-9_]*)\+=', driver, re.M)) # BAR+=b
|
||||||
|
assigns |= set(re.findall(r'(?:^|;|&&|\|\||\)|!|\bthen\b|\bdo\b|\belse\b|\{)\s*'
|
||||||
|
r'([A-Za-z_][A-Za-z0-9_]*)\[[^]]*\]=', driver, re.M)) # FOO[0]=x
|
||||||
|
# Arithmetic CONTEXTS BIND TOO — `(( FOO = 1 ))`, `(( i++ ))`, and the C-style
|
||||||
|
# `for (( i=0; i<3; i++ ))`. The reads-extraction above adds every identifier it
|
||||||
|
# finds inside `(( ))`, so without this the guard demands the very names those
|
||||||
|
# expressions assign.
|
||||||
|
for expr in (re.findall(r'\$\(\((.*?)\)\)', driver, re.S)
|
||||||
|
+ re.findall(r'(?<!\$)\(\((.*?)\)\)', driver, re.S)):
|
||||||
|
assigns |= set(re.findall(r'(?<![0-9A-Za-z_])([A-Za-z_][A-Za-z0-9_]*)\s*(?:=[^=]|\+\+|--)', expr))
|
||||||
|
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'}
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
# `${!...}` HAS THREE MEANINGS IN BASH and only one of them is indirection:
|
||||||
|
# ${!name} indirect expansion — genuinely unanalysable
|
||||||
|
# ${!arr[@]} ${!arr[*]} array KEY expansion — ordinary, and LIVE at
|
||||||
|
# ltl-accumulator check.sh:274, `for cert in
|
||||||
|
# "${!CONES[@]}"`
|
||||||
|
# ${!prefix*} ${!prefix@} variable-NAME listing — ordinary
|
||||||
|
# Round-9 review (Claude, N1). The round-8 refusal tested for `${!` and could
|
||||||
|
# not tell them apart, so a legitimate construct would have hard-failed a lift
|
||||||
|
# with a diagnostic naming a feature it does not use. A refusal is the most
|
||||||
|
# expensive verdict this tool has; it must be reserved for the case it is
|
||||||
|
# actually about.
|
||||||
|
INDIRECT = re.compile(r'\$\{!\s*[A-Za-z_][A-Za-z0-9_]*\s*\}')
|
||||||
|
if INDIRECT.search(payload):
|
||||||
|
print('INDIRECT-EXPANSION')
|
||||||
|
else:
|
||||||
|
print(' '.join(sorted(n for n in reads - assigns - ENV if not n.isdigit())))
|
||||||
|
PYGUARD
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
@ -38,18 +38,29 @@ cp "$HERE/HARNESS.sha256" "$STASH/HARNESS.sha256"
|
||||||
|
|
||||||
# Lift Phase 0c. The two repo families end the phase differently, so accept
|
# Lift Phase 0c. The two repo families end the phase differently, so accept
|
||||||
# either terminator rather than hardcoding one and silently lifting nothing.
|
# either terminator rather than hardcoding one and silently lifting nothing.
|
||||||
|
# The payload is written to its OWN file before the driver is assembled, for two
|
||||||
|
# reasons that both bit this repository. The size sanity check below has to
|
||||||
|
# measure what was LIFTED; measured on the concatenated driver it counts the
|
||||||
|
# prepended bindings too, so a lift that recovered almost nothing still clears
|
||||||
|
# the threshold (round-7 lift-guard-payload-assert, closed in the four forks
|
||||||
|
# with "all six lift sites" — six counted the forks, and these two accumulator
|
||||||
|
# lifts were never in that count). And lift-guard needs payload and driver
|
||||||
|
# separately to tell what the phase READS from what the driver DEFINES.
|
||||||
DRIVER="$STASH/phase0c.sh"
|
DRIVER="$STASH/phase0c.sh"
|
||||||
|
PAYLOAD="$STASH/payload.sh"
|
||||||
|
awk '/^# ── Phase 0c/{f=1} f{print} /^# ── Phase 1|^echo "=== Phase 1/{if(f && !/Phase 0c/) exit}' "$HERE/check.sh" \
|
||||||
|
| sed '/^# ── Phase 1/d; /^echo "=== Phase 1/d' > "$PAYLOAD"
|
||||||
{
|
{
|
||||||
echo 'set -euo pipefail' # -e matches the button; see lift-drivers-drop-errexit
|
echo 'set -euo pipefail' # -e matches the button; see lift-drivers-drop-errexit
|
||||||
echo "HERE=\"$HERE\""
|
echo "HERE=\"$HERE\""
|
||||||
awk '/^# ── Phase 0c/{f=1} f{print} /^# ── Phase 1|^echo "=== Phase 1/{if(f && !/Phase 0c/) exit}' "$HERE/check.sh" \
|
cat "$PAYLOAD"
|
||||||
| sed '/^# ── Phase 1/d; /^echo "=== Phase 1/d'
|
|
||||||
} > "$DRIVER"
|
} > "$DRIVER"
|
||||||
if [ "$(grep -c . "$DRIVER")" -lt 20 ]; then
|
if [ "$(grep -c . "$PAYLOAD")" -lt 20 ]; then
|
||||||
echo "FATAL: could not lift Phase 0c out of check.sh — the phase markers moved."
|
echo "FATAL: could not lift Phase 0c out of check.sh — the phase markers moved."
|
||||||
echo "This self-test must attack the shipping gate; refusing to run against nothing."
|
echo "This self-test must attack the shipping gate; refusing to run against nothing."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
"$HERE/lift-guard.sh" "$PAYLOAD" "$DRIVER" "check.sh Phase 0c" || exit 1
|
||||||
|
|
||||||
expect() { # expect <label> <want-rc> <want-substring>
|
expect() { # expect <label> <want-rc> <want-substring>
|
||||||
local label="$1" want_rc="$2" want_txt="$3" out rc
|
local label="$1" want_rc="$2" want_txt="$3" out rc
|
||||||
|
|
|
||||||
|
|
@ -46,18 +46,26 @@ cp "$T/AUDIT-MANIFEST.txt" "$T/MANIFEST.pristine"
|
||||||
|
|
||||||
# Phase 3d, lifted verbatim from the shipping button. HERE and INVLOG are the
|
# Phase 3d, lifted verbatim from the shipping button. HERE and INVLOG are the
|
||||||
# two variables it reads from its surroundings.
|
# two variables it reads from its surroundings.
|
||||||
|
# Payload to its own file before the driver is assembled: the size check must
|
||||||
|
# measure what was LIFTED, not the lift plus its bindings, and lift-guard needs
|
||||||
|
# the two apart to separate what the phase READS from what the driver DEFINES.
|
||||||
DRIVER="$T/phase3d.sh"
|
DRIVER="$T/phase3d.sh"
|
||||||
|
PAYLOAD="$T/payload3d.sh"
|
||||||
|
sed -n '/^# -- Phase 3d/,/^# -- Phase 4/p' "$SRC/check.sh" | sed '$d' > "$PAYLOAD"
|
||||||
{
|
{
|
||||||
echo 'set -euo pipefail' # -e matches the button; see lift-drivers-drop-errexit
|
echo 'set -euo pipefail' # -e matches the button; see lift-drivers-drop-errexit
|
||||||
echo "HERE=\"$T\""
|
echo "HERE=\"$T\""
|
||||||
echo 'INVLOG="$1"'
|
echo 'INVLOG="$1"'
|
||||||
sed -n '/^# -- Phase 3d/,/^# -- Phase 4/p' "$SRC/check.sh" | sed '$d'
|
cat "$PAYLOAD"
|
||||||
} > "$DRIVER"
|
} > "$DRIVER"
|
||||||
if [ "$(wc -l < "$DRIVER")" -lt 40 ]; then
|
if [ "$(wc -l < "$PAYLOAD")" -lt 40 ]; then
|
||||||
echo "FATAL: could not lift Phase 3d out of check.sh — the phase markers moved."
|
echo "FATAL: could not lift Phase 3d out of check.sh — the phase markers moved."
|
||||||
echo "This self-test must attack the shipping gate; refusing to run against nothing."
|
echo "This self-test must attack the shipping gate; refusing to run against nothing."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
# Guarded by the SHIPPING guard in $SRC, not the copy inside the scratch tree:
|
||||||
|
# a test that vets itself with its own copy of the instrument proves less.
|
||||||
|
"$SRC/lift-guard.sh" "$PAYLOAD" "$DRIVER" "check.sh Phase 3d" || exit 1
|
||||||
|
|
||||||
# Recompile the edited leaf module + the inventory into $T/inv.out.
|
# Recompile the edited leaf module + the inventory into $T/inv.out.
|
||||||
build_inventory() {
|
build_inventory() {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue