--- fp-driver-d2-r3.sh 2026-09-13 02:44:48.681902900 -0700 +++ fp-driver-d2-r4.sh 2026-09-13 03:20:31.770335200 -0700 @@ -204,9 +204,13 @@ # LEDGER_RESERVE_S the report cannot be starved by the work before it. Every cleanup step # is bounded by WORK_DEADLINE = REPORT_DEADLINE - this; the ledger owns # the remainder ALONE and emits without waiting for any child. -# KILL_GRACE_S `timeout -k N` TERMs at the bound and KILLs N seconds later, so a bound -# of `deadline - now` overruns its own deadline by N. Every bound -# subtracts it, so TERM+KILL completes BY the deadline. +# KILL_GRACE_S the seconds every bound holds back from its own deadline. It was named +# for `timeout -k N`'s TERM-then-KILL escalation, and NOTHING ESCALATES +# ANY MORE: no `timeout` stands on the launch boundary, and termination is +# held -- no kill, no tree kill. The reserve now covers the LAUNCHER'S OWN +# RETURN (writing the outcome into the identity record and reporting), so +# a bound still completes BY the deadline rather than past it, and a step +# that cannot cover it is NOT STARTED. # HANDOFF_CAP_S a POLICY ALLOCATION of the activity window -- how much of the 300s this # run will spend waiting on another agent. 94s and 134s are two samples, # not a distribution, and nothing here is derived from them. @@ -830,14 +834,19 @@ # a budget checked BETWEEN steps bounds a sequence, not a process. # # FOUR THINGS, NOT ONE, and each is a correction doyle made to my first proposal: -# 1. the bound subtracts KILL_GRACE_S, so TERM-then-KILL completes BY the deadline rather -# than five seconds past it, and a step that cannot cover the grace is NOT STARTED; -# 2. the child is REGISTERED BEFORE it can finish, and the register distinguishes the msys -# WRAPPER from the NATIVE child -- they are different processes with different pids, and -# the one bash hands back is the wrapper while the one that may survive is the child; -# 3. on expiry the driver MEASURES what stopped instead of assuming it. `timeout` sends TERM -# to a native Windows process that owes it no POSIX answer, and that process's descendants -# are in no job object the kill reaches. A read-only command still leaves a process; +# 1. the bound subtracts KILL_GRACE_S, so the LAUNCHER'S OWN RETURN completes BY the +# deadline rather than past it, and a step that cannot cover it is NOT STARTED; +# 2. the subject is REGISTERED FROM THE ACT OF LAUNCHING IT. The register no longer asks +# Win32 for a wrapper's children: that query could only ever reach the wrapper itself +# (A7, A6, probe-a3-parent), because the winpid bash hands back is a pre-exec stub's. +# The identity kept is the DIRECT child's, and it carries NO claim over anything that +# child may itself have started; +# 3. on expiry the driver MEASURES the subject's disposition by pid AND creation instant, +# and TERMINATES NOTHING. Tree kill is withdrawn until containment behaviour is +# reviewable, so an expired step's subject MAY STILL BE RUNNING and is reported +# SURVIVING rather than stopped. `timeout` sent TERM to a native Windows process that +# owed it no POSIX answer, and whose descendants sat in no job object the kill reached; +# dropping it removes a wrapper, not a guarantee; # 4. whatever the killed step wrote is PRESERVED under a name that says it is unverified. # An expired bound says the COMPLETION is unverified; it says nothing about the disk. # @@ -846,79 +855,83 @@ BOUNDED_STATE=NOT_REACHED # the outcome of the most recent bounded() call LAUNCH_SEQ=0 launch_file() { echo "$R/launched-identities.txt"; } -# The WINPID behind an msys pid. `ps` is an msys builtin-class probe over msys's own table: -# it costs no CIM query and it is the only place the mapping exists. An unmappable pid is -# recorded as UNREADABLE -- never as 'the wrapper had no child'. -winpid_of() { # MSYS_PID -> WINPID | UNREADABLE - local p="$1" w - w=$(ps -p "$p" 2>/dev/null | awk -v p="$p" '$1==p {print $4}' | head -1) - case "$w" in (''|*[!0-9]*) echo UNREADABLE ;; (*) echo "$w" ;; esac -} -# ONE bounded CIM query for the children of a live wrapper. It runs WHILE the wrapper is alive -# because that is the only moment the parent link exists: after `timeout` returns, a survivor -# has no edge back to anything this run can name. -register_launched() { # LABEL MSYS_PID DEADLINE - local label="$1" mpid="$2" dl="$3" lf; lf=$(launch_file) +# winpid_of() IS GONE, and not merely unused. Its only consumer was the descendant query, and +# the mapping it supplied -- an msys pid's winpid -- is the very reading that misattributed the +# subject: on cygwin the winpid bash hands back belongs to the PRE-EXEC STUB, so a +# ParentProcessId query fed from it could only ever reach the exec'd wrapper. The launcher now +# reports the identity it CREATED, so nothing needs the map, and leaving the function behind +# would leave the next author a tool whose answer looks authoritative and is not. +# THE REGISTER READS THE LAUNCHER'S OWN IDENTITY RECORD. It queries no parent link, and it +# enumerates no descendant. +# +# WHY THE QUERY IS GONE, measured. The predecessor read the backgrounded job's winpid out of +# msys `ps` and asked Win32 for THAT pid's children. On cygwin the pid bash hands back is the +# PRE-EXEC STUB's, so the only process that ever answered was the exec'd `timeout.exe`: A7's +# single kind=native row named the WRAPPER, A6 got no row at all, and +# disposition-20260913T092245Z measured five wrapper identities GONE while the subjects they +# bounded had no identity at all and stay UNREADABLE for good. A deeper walk down the same +# links inherits the same broken premise (doyle JDCJYANF). +# +# WHAT REPLACES IT. launch_bounded.ps1 CREATES the subject and writes the identity it created +# -- metadata only -- before it waits. The register copies that in. The identity comes from the +# act of launching, not from a query afterwards, and the launcher keeps the handle so the pid +# cannot be recycled under the record (doyle NRTFH236 #1). +# +# COVERAGE IS NEVER COMPLETE (doyle NRTFH236 #2). Nothing enforces containment, so no launch +# can establish that it has seen everything its subject started. A scan that found nothing +# would not raise this either -- that is the absence of a reading. COMPLETE is not emissible +# anywhere in this driver, and CONFIRMED_GONE therefore is not emissible either. +SUBJECT_STATE=NOT_MEASURED +register_launched() { # LABEL RECORD_FILE + local label="$1" rec="$2" lf; lf=$(launch_file) LAUNCH_SEQ=$((LAUNCH_SEQ + 1)) - local wp; wp=$(winpid_of "$mpid") local now; now=$(date -u +%Y-%m-%dT%H:%M:%SZ) - echo "LAUNCHED seq=$LAUNCH_SEQ label=$label kind=wrapper msys_pid=$mpid winpid=$wp launched=$now" >> "$lf" - if [ "$wp" = UNREADABLE ]; then - echo "LAUNCHED seq=$LAUNCH_SEQ label=$label kind=native state=ATTRIBUTION_INCOMPLETE reason=wrapper-winpid-unreadable" >> "$lf" - record " LAUNCH($label) the wrapper's WINPID could not be read: the native child is ATTRIBUTION_INCOMPLETE, which is NOT a statement that it is gone" - return 0 - fi - local q_left=$(( dl - $(date +%s) )) - [ "$q_left" -gt 3 ] || { - echo "LAUNCHED seq=$LAUNCH_SEQ label=$label kind=native state=ATTRIBUTION_INCOMPLETE reason=no-budget-for-descendant-query" >> "$lf" - return 0 - } - [ "$q_left" -gt 10 ] && q_left=10 - SPT_D2_WP="$wp" timeout -k 2 "$q_left" pwsh -NoProfile -Command ' - $p = [int]$env:SPT_D2_WP - try { $kids = @(Get-CimInstance Win32_Process -Filter "ParentProcessId=$p" -ErrorAction Stop) } - catch { "QUERY=UNREADABLE reason=enumeration-failed"; exit 0 } - if ($kids.Count -eq 0) { "QUERY=NO_CHILD"; exit 0 } - foreach ($k in $kids) { - $c = try { $k.CreationDate.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ") } catch { "UNREADABLE" } - "CHILD pid=$($k.ProcessId) created=$c exe=[$($k.ExecutablePath)]" - } - ' > "$R/.launch-q.$LAUNCH_SEQ" 2>&1 - local qrc=$? - if [ "$qrc" -ne 0 ] || grep -q '^QUERY=UNREADABLE' "$R/.launch-q.$LAUNCH_SEQ" 2>/dev/null; then - echo "LAUNCHED seq=$LAUNCH_SEQ label=$label kind=native state=ATTRIBUTION_INCOMPLETE reason=descendant-query-unreadable(rc=$qrc)" >> "$lf" + echo "LAUNCHED seq=$LAUNCH_SEQ label=$label kind=launcher exe=[$BIN/launch_bounded.ps1] registered=$now" >> "$lf" + if [ ! -s "$rec" ]; then + echo "LAUNCHED seq=$LAUNCH_SEQ label=$label coverage=NONE reason=no-identity-record" >> "$lf" + record " LAUNCH($label) the launcher wrote no identity record: coverage=NONE. That is the ABSENCE OF A READING, and it is never the statement that nothing was launched." return 0 fi - if grep -q '^QUERY=NO_CHILD' "$R/.launch-q.$LAUNCH_SEQ" 2>/dev/null; then - # A CHILD THAT EXITED BEFORE REGISTRATION IS NOT CONFIRMED GONE (doyle B3CMWRQI). An - # absence at the moment of registration is the absence of a READING, not a reading of an - # absence; only a registered pid re-queried and measured absent is the second thing. - echo "LAUNCHED seq=$LAUNCH_SEQ label=$label kind=native state=ATTRIBUTION_INCOMPLETE reason=no-child-at-registration wrapper_winpid=$wp" >> "$lf" + cat "$rec" >> "$R/launch-records.txt" 2>/dev/null + local subj; subj=$(grep -m1 '^subject pid=' "$rec" 2>/dev/null) + if [ -z "$subj" ]; then + echo "LAUNCHED seq=$LAUNCH_SEQ label=$label coverage=NONE reason=record-has-no-subject-row" >> "$lf" + record " LAUNCH($label) the identity record names no subject: coverage=NONE, and nothing about this launch can be re-queried." return 0 fi - while IFS= read -r line; do - case "$line" in - CHILD\ *) echo "LAUNCHED seq=$LAUNCH_SEQ label=$label kind=native ${line#CHILD } parent_winpid=$wp" >> "$lf" ;; - esac - done < "$R/.launch-q.$LAUNCH_SEQ" + echo "LAUNCHED seq=$LAUNCH_SEQ label=$label kind=subject ${subj#subject }" >> "$lf" + local st; st=$(grep -m1 '^subject_state=' "$rec" 2>/dev/null) + [ -n "$st" ] && echo "LAUNCHED seq=$LAUNCH_SEQ label=$label $st" >> "$lf" + echo "LAUNCHED seq=$LAUNCH_SEQ label=$label coverage=PARTIAL reason=direct-subject-only-no-enforced-containment" >> "$lf" + record " LAUNCH($label) subject registered from the launcher's own identity record; coverage=PARTIAL -- the DIRECT child only, because nothing here enforces containment" return 0 } -# What actually stopped. Re-queries THIS call's registered native rows by pid AND creation -# instant -- pid reuse on this box is measured, so a pid answering with a different creation -# time is a DIFFERENT process and ours is gone. -measure_termination() { # LABEL SEQ DEADLINE -> sets TERMINATION +# WHAT STOPPED, REPORTED AS TWO THINGS THAT ARE NEVER SUMMED (doyle NRTFH236 #2). The DIRECT +# subject's disposition is measured by pid AND creation instant. The WHOLE OPERATION stays +# ATTRIBUTION_INCOMPLETE while coverage is short of COMPLETE -- which, under this design, it +# always is. CONFIRMED_GONE is not emissible. +# +# THE ANCHOR IS NOT DECORATION: control L7 (controls-out/20260913T100902Z-L) caught pid 53072 +# recycled 24 seconds after that subject exited. A pid-only re-query would have called the +# subject SURVIVING. +# +# THE RE-QUERY IS STILL BOUNDED BY `timeout`, DELIBERATELY. That is a bound on a MEASUREMENT +# whose identity is never registered; it is not the launch boundary, and nothing here can be +# mistaken for the subject. +measure_termination() { # LABEL SEQ DEADLINE -> sets TERMINATION and SUBJECT_STATE local label="$1" seq="$2" dl="$3" lf; lf=$(launch_file) - TERMINATION=UNREADABLE - local rows; rows=$(grep "^LAUNCHED seq=$seq .* kind=native pid=" "$lf" 2>/dev/null) + TERMINATION=ATTRIBUTION_INCOMPLETE + SUBJECT_STATE=UNREADABLE + local rows; rows=$(grep "^LAUNCHED seq=$seq .* kind=subject pid=" "$lf" 2>/dev/null) if [ -z "$rows" ]; then - TERMINATION=ATTRIBUTION_INCOMPLETE - record " TERMINATION($label)=ATTRIBUTION_INCOMPLETE — no native identity was registered for this launch, so nothing can be re-queried. This is NOT 'confirmed gone'." + record " SUBJECT_STATE($label)=UNREADABLE -- no subject identity was registered for this launch, so nothing can be re-queried. This is NOT 'confirmed gone'." + record " TERMINATION($label)=ATTRIBUTION_INCOMPLETE" return fi local q_left=$(( dl - $(date +%s) )) if [ "$q_left" -le 3 ]; then - TERMINATION=UNREADABLE - record " TERMINATION($label)=UNREADABLE — no budget remained to re-query the registered identities. UNREADABLE is never CLEAR." + record " SUBJECT_STATE($label)=UNREADABLE -- no budget remained to re-query the registered identity. UNREADABLE is never CLEAR." + record " TERMINATION($label)=ATTRIBUTION_INCOMPLETE" return fi [ "$q_left" -gt 10 ] && q_left=10 @@ -927,27 +940,45 @@ p=$(echo "$r" | sed -n 's/.*[ ]pid=\([0-9]*\).*/\1/p') c=$(echo "$r" | sed -n 's/.*[ ]created=\([^ ]*\).*/\1/p') [ -n "$p" ] || continue + if [ -z "$c" ] || [ "$c" = UNREADABLE ]; then + unread=$((unread + 1)) + record " SUBJECT_STATE($label) pid=$p carries NO creation anchor, so it cannot be re-queried. An unanchored pid is never reported gone." + continue + fi now_c=$(SPT_D2_P="$p" timeout -k 2 "$q_left" pwsh -NoProfile -Command ' $p = [int]$env:SPT_D2_P try { $x = @(Get-CimInstance Win32_Process -Filter "ProcessId=$p" -ErrorAction Stop) } catch { "UNREADABLE"; exit 0 } if ($x.Count -eq 0) { "GONE"; exit 0 } - try { $x[0].CreationDate.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ") } catch { "UNREADABLE" } + try { $x[0].CreationDate.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffffffZ") } catch { "UNREADABLE" } ' 2>/dev/null | tr -d ' \r\n') case "$now_c" in - GONE) gone=$((gone + 1)) ;; + GONE) gone=$((gone + 1)) ;; UNREADABLE|'') unread=$((unread + 1)) ;; - "$c") live=$((live + 1)); record " TERMINATION($label) SURVIVING pid=$p created=$c — the bound expired and this process did not" ;; - *) gone=$((gone + 1)) ;; # same pid, different creation instant: reuse, ours is gone + *) + # COMPARED AT SECOND GRANULARITY. The launcher records the creation instant to 100ns + # and this re-query reads the same field, but the two readers round the last digits + # differently (L1a: ...8749085 recorded, ...8749080 re-queried). A pid recycled within + # the same second is not a case this box produces -- L7's measured reuse took 24s -- + # and both raw values are written down, so the comparison can be re-derived. + if [ "${now_c%.*}" = "${c%.*}" ]; then + live=$((live + 1)) + record " SUBJECT_STATE($label) SURVIVING pid=$p created=$c -- the bound elapsed and this process did not stop. NOTHING WAS TERMINATED: termination is held." + else + gone=$((gone + 1)) + record " SUBJECT_STATE($label) pid=$p was REUSED (recorded created=$c, now created=$now_c): a different process answers on that pid, so ours is gone" + fi ;; esac done < WINDOWS PATH | '' + local prog="$1" p + p=$(command -v "$prog" 2>/dev/null) || return 1 + [ -n "$p" ] || return 1 + cygpath -w "$p" 2>/dev/null +} bounded() { # LABEL DEADLINE_EPOCH OUT ERR -- COMMAND... local label="$1" dl="$2" out="$3" err="$4"; shift 4 if [ "$1" = '--' ]; then shift; else @@ -991,22 +1033,51 @@ if [ "$dl" -le 0 ] || [ "$left" -lt 1 ]; then BOUNDED_STATE=NOT_STARTED exits "${label}_BOUND=NOT_STARTED" - record "BOUND_NOT_STARTED $label — ${left}s would remain after the ${KILL_GRACE_S}s escalation reserve. The step is NOT RUN." + record "BOUND_NOT_STARTED $label — ${left}s would remain after the ${KILL_GRACE_S}s launcher reserve. The step is NOT RUN." record " TRUNCATED phase=$label got 0s. What it would have observed is NOT CAPTURED, which is not the statement 'not present'." return 125 fi stamp "${label}_START bound=${left}s deadline_epoch=$dl" - timeout -k "$KILL_GRACE_S" "$left" "$@" > "$out" 2> "$err" & - local mpid=$! - local myseq - register_launched "$label" "$mpid" "$dl" - myseq="$LAUNCH_SEQ" - wait "$mpid"; local rc=$? + # THE LAUNCHER IS THE BOUNDARY (doyle NRTFH236 #3). No `timeout` and no `env` stand between + # this driver and the process it means to run: launch_bounded.ps1 creates the subject + # DIRECTLY, holds its handle for the child's whole life, writes the identity it created, and + # enforces the bound itself. The environment reaches the subject by INHERITANCE -- a call site + # that needs a variable exports it around this call. + # + # ARGUMENTS CROSS ON DISK, ONE PER LINE. An array handed through `pwsh -File` collapses into a + # single string, which is how quoting defects are built. (Ceiling: an argument containing a + # newline would not survive this protocol. No call site has one, and one would have to be + # written deliberately.) + local myseq=$((LAUNCH_SEQ + 1)) + local rec="$R/.launch-rec.$myseq" argf="$R/.launch-args.$myseq" + rm -f "$rec" "$argf" + local lexe="$1"; shift + local lwin; lwin=$(win_exe "$lexe") + if [ -z "$lwin" ]; then + BOUNDED_STATE=SETUP_FAILED + exits "${label}_LAUNCH=UNRESOLVABLE" + record "LAUNCH_UNRESOLVABLE $label — the program [$lexe] does not resolve on PATH. The step is NOT RUN and NOTHING WAS LAUNCHED, which is a different statement from a step that ran and failed." + return 91 + fi + if [ $# -gt 0 ]; then printf '%s\n' "$@" > "$argf"; else : > "$argf"; fi + pwsh -NoProfile -File "$(cygpath -w "$BIN/launch_bounded.ps1")" \ + -Label "$label" -Seconds "$left" \ + -RecordFile "$(cygpath -w "$rec")" -ArgsFile "$(cygpath -w "$argf")" \ + -Exe "$lwin" \ + -OutFile "$(cygpath -w "$out")" -ErrFile "$(cygpath -w "$err")" > "$R/.launch-log.$myseq" 2>&1 + local rc=$? + # THE LAUNCHER'S OWN STREAMS GO TO ITS OWN FILE. The SUBJECT's output is written by the + # launcher straight into OUT and ERR, so anything the launcher itself says would otherwise + # contaminate either the subject's capture or this driver's log (`record` tees to stdout -- + # r10's defect, and it cost IR-123 an unpassable gate). + [ -s "$R/.launch-log.$myseq" ] && record " LAUNCHER($label) wrote to .launch-log.$myseq: $(head -3 "$R/.launch-log.$myseq" | tr ' +' ' ')" + register_launched "$label" "$rec" stamp "${label}_END rc=$rc" exits "${label}_EXIT=$rc" if [ "$rc" -eq 124 ] || [ "$rc" -eq 137 ]; then BOUNDED_STATE=EXPIRED - record "BOUND_EXPIRED $label after ${left}s (killer rc=$rc). The bound stopped THIS DRIVER'S WAIT and attempted to stop the operation; what actually stopped is measured below." + record "BOUND_EXPIRED $label after ${left}s (launcher rc=$rc). The bound stopped THIS DRIVER'S WAIT AND NOTHING ELSE: no termination is authorized here, so the subject MAY STILL BE RUNNING. What it is actually doing is measured below." measure_termination "$label" "$myseq" "$dl" return 124 fi @@ -1133,7 +1204,7 @@ local rc=$? if [ "$rc" -eq 125 ]; then echo "NO_BUDGET" > "$exf" - record "D2($point) NOT RUN — the remaining budget could not cover the ${KILL_GRACE_S}s escalation reserve. NOT CAPTURED is not 'not present'." + record "D2($point) NOT RUN — the remaining budget could not cover the ${KILL_GRACE_S}s launcher reserve. NOT CAPTURED is not 'not present'." return 1 fi if [ "$rc" -eq 124 ] || [ "$rc" -eq 137 ]; then @@ -1236,8 +1307,14 @@ # that as UNREADABLE rather than as an empty list. capture_run_identities() { # WHEN local when="$1" + # THE INTERPOSED `env` IS GONE (doyle NRTFH236 #3). It was a whole extra process standing + # between this driver and the probe, and with it in the way the launch boundary's direct + # child was the INTERMEDIARY rather than the probe itself. The variable now reaches the probe + # by INHERITANCE, which is what an environment is for, and the intended executable IS the + # direct child. It is exported around this call only, and unset immediately after. + export SPT_D2_HOME="$H" bounded "identity_capture_$when" "$(cur_deadline)" "$R/run-identities.txt" "$R/run-identities.err" -- \ - env SPT_D2_HOME="$H" pwsh -NoProfile -Command ' + pwsh -NoProfile -Command ' $h = $env:SPT_D2_HOME $pidFile = Join-Path $h "daemon.pid" if (-not (Test-Path $pidFile)) { "IDENT_PROBE=UNREADABLE reason=no-pid-file"; exit 0 } @@ -1262,6 +1339,7 @@ ' local rc=$? IDENT_CAPTURE_RC=$rc + unset SPT_D2_HOME # A BOUND THAT EXPIRED HERE IS THE WORST ONE TO MISREAD: cleanup can only re-query an # identity somebody wrote down, so an expired identity capture makes the run's residue # UNREADABLE -- which is never CLEAR -- rather than making the host empty. @@ -1867,9 +1945,12 @@ # WAITED ON (doyle B3CMWRQI). The ledger owns the reserved interval and must emit on time; # an un-reaped child is reported as OUTSTANDING, which is a line to print. if [ -f "$(launch_file)" ]; then - local out_n; out_n=$(grep -c 'state=ATTRIBUTION_INCOMPLETE' "$(launch_file)" 2>/dev/null) - case "$out_n" in (''|*[!0-9]*) out_n=0 ;; esac - record "LAUNCHED_IDENTITIES $(grep -c '^LAUNCHED ' "$(launch_file)" 2>/dev/null) rows, of which $out_n are ATTRIBUTION_INCOMPLETE — see launched-identities.txt. An incomplete attribution is NOT a process measured absent." + local subj_n cov_n + subj_n=$(grep -c ' kind=subject pid=' "$(launch_file)" 2>/dev/null) + cov_n=$(grep -c ' coverage=' "$(launch_file)" 2>/dev/null) + case "$subj_n" in (''|*[!0-9]*) subj_n=0 ;; esac + case "$cov_n" in (''|*[!0-9]*) cov_n=0 ;; esac + record "LAUNCHED_IDENTITIES $(grep -c '^LAUNCHED ' "$(launch_file)" 2>/dev/null) rows: $subj_n DIRECT subjects across $cov_n launches, and every launch is coverage=PARTIAL or NONE — see launched-identities.txt. No launch in this driver can claim COMPLETE coverage of what it started, so no whole operation is reported CONFIRMED_GONE; a direct subject's own disposition is reported separately and is never summed into one." else record "LAUNCHED_IDENTITIES none recorded — no bounded child was ever launched, or the register could not be written" fi @@ -2181,16 +2262,16 @@ # bracketed; d2_capture.ps1 and d2_validate.py are new. runner-census.ps1 STAYS: shared-host # admission is not workload, and its ancestry-authenticated census is what makes box_gate a # measurement instead of a name filter. -for inst in census.ps1 runner-census.ps1 portfields.ps1 d1_render.py d2_capture.ps1 d2_validate.py; do - [ -f "$BIN/$inst" ] || die "instrument $BIN/$inst is missing — the six dependencies must all exist before any runnable claim" +for inst in census.ps1 runner-census.ps1 portfields.ps1 d1_render.py d2_capture.ps1 d2_validate.py launch_bounded.ps1; do + [ -f "$BIN/$inst" ] || die "instrument $BIN/$inst is missing — the seven dependencies must all exist before any runnable claim" done sha256sum "$BIN"/census.ps1 "$BIN"/runner-census.ps1 "$BIN"/portfields.ps1 "$BIN"/d1_render.py \ - "$BIN"/d2_capture.ps1 "$BIN"/d2_validate.py \ + "$BIN"/d2_capture.ps1 "$BIN"/d2_validate.py "$BIN"/launch_bounded.ps1 \ > "$R/INSTRUMENTS.sha256" 2>"$R/instruments.err" irc=$? exits "instruments_sha_EXIT=$irc" [ "$irc" -eq 0 ] || die "could not hash the instruments — directory placement is not provenance (C); the hashes must ride the evidence" -record "INSTRUMENTS hashed into INSTRUMENTS.sha256 (6 files)" +record "INSTRUMENTS hashed into INSTRUMENTS.sha256 (7 files)" # SCOPE OF PRIOR INSTRUMENT EXERCISE, recorded accurately (doyle 2026-09-12, extended # 2026-09-13 for the two D2 instruments): # d1_render.py was exercised by LIVE LOCAL EXECUTION against blob c28874ef plus three