Skip to content

feat(bin): enforce the outbound artifact transport invariant - #101

Merged
sbracewell64 merged 84 commits into
mainfrom
fm/control-plane-outbound-transport-invariant
Aug 18, 2026
Merged

sbracewell64 merged 84 commits into
mainfrom
fm/control-plane-outbound-transport-invariant

Conversation

@sbracewell64

@sbracewell64 sbracewell64 commented Aug 16, 2026 •

Copy link
Copy Markdown
Owner

Intent

The developer handed off an in-progress autonomous "away mode" session to a new model/session and asked the agent to rehydrate state from authoritative sources (repos, GitHub PRs/issues, durable task state, immutable evidence, and the FirstMate/Browser Sol control plane) rather than treating the prompt itself as evidence, explicitly requiring that existing branches, PRs, exact-head protections, maker/checker separation, three-valued (observed-good/observed-bad/could-not-observe) evidence semantics, provenance, and existing HOLD/CNO states be preserved and not redone. Concrete goals were: process outstanding Browser Sol control traffic on issue #3 and verify PR #7's head, base, and checks before acting on any ruling, without merging PR #7 on the strength of the prompt; and resume work toward a persistent FirstMate-side poller (control issue #2) that wakes and polls without Captain involvement, with the prior blocker — no qualified maker / independently certifiable mutation path — re-evaluated under the new session, while forbidding any weakening of qualification standards or maker/checker separation to make the current model qualify. Constraints were a strict cost boundary (existing subscriptions and credentials only, no new spend) and an operating posture that routes material engineering judgments to Browser Sol, treats quota/CI/capacity issues as external dependencies, and reserves Captain escalation for genuine Captain-only decisions. Through subsequent supervisor escalations the developer kept the agent driving the same work: proving fixes with watched-red controls, keeping corrections inside their reviewed scope, reconciling diverged branch lineages without destroying unique evidence, and executing force-with-lease publication only after a from-scratch reconstruction confirmed no unique work would be lost.

What Changed

  • Adds bin/fm-outbound-artifact.sh and bin/fm-outbound-artifact-lib.sh: a sweep that joins every item whose durable state implies an outstanding outbound artifact against whether an applicable one exists, with three-valued verdicts (0 holds / 3 defect / 4 could-not-observe), exact-head and request-identity correlation, durable correlation records, and check|status|defects|reconcile|emit|ruling|poll|resume|close|show subcommands. The sol-control channel can emit and reconcile missing requests against the venue in the new optional config/sol-control.json; pull-request rows stay detect-only.
  • Wires the invariant into session start: bin/fm-bootstrap.sh gains outbound_artifact_report, which reconciles when the session holds the fleet lock and runs the detect-only sweep under FM_BOOTSTRAP_DETECT_ONLY, bounded by the new FM_OUTBOUND_BOOTSTRAP_DEADLINE (default 60) split away from the per-probe FM_OUTBOUND_TIMEOUT (default 15). Partial findings are relayed before the incompleteness marker, and OUTBOUND: handling is documented in the bootstrap-diagnostics skill.
  • Adds bin/fm-dead-predicate-check.sh, a fail-closed control that flags enrolled predicates with no accepted call site (unparseable syntax reports could-not-observe rather than passing), and runs it as a CI invariants step. Covered by new tests/fm-outbound-artifact.test.sh and tests/fm-dead-predicate-check.test.sh plus bootstrap/session-start test updates, with configuration, scripts, vocabulary-collision, and docs/verification/outbound-transport-invariant.md documentation.

Risk Assessment

⚠️ Medium: Every defect substantiated across four review rounds is now fixed and independently re-verified — the CI control this branch added is green again at this head — but the branch as a whole is a ~5.5k-line new mechanism that runs a mutating, forge-writing sweep at every session start and adds a hard CI gate, so it warrants the usual post-merge attention rather than being treated as routine.

Testing

Ran the three focused suites that own this change (outbound-artifact, dead-predicate, bootstrap) — all green — then drove the commands by hand against real fixture homes to produce operator-visible transcripts, because a green suite over a silence-shaped invariant proves little on its own. The transcripts show the split defect headings with an observed artifact: comment/900 filed under the correlation heading instead of the missing-artifact one, the relay line naming the artifact and the disagreeing request id, could-not-observe counted and sectioned at exit 4, reconcile printing a refused emit's reason and still reporting every other item at worst-of status 4 with no leftover locks, and a deadlined session start relaying its finding above an explicit INCOMPLETE marker. The dead-predicate control was also run against the real repository (exit 0, alive=77, could_not_observe=0). This surface is a bash CLI with no rendered UI, so the evidence is CLI transcripts rather than screenshots. No failures, no flakes; worktree left clean.

Evidence: Outbound sweep CLI transcript — split defect headings, observed-artifact naming, three-valued verdict

$ fm-outbound-artifact.sh status # nothing has been emitted yet outbound artifacts: 0 satisfied, 1 defect, 0 could-not-observe DEFECT - waiting with no applicable durable artifact (1) waiting-item gate: INDEPENDENT_BROWSER_REVIEW_REQUIRED · channel: sol-control · recognised: prose head: a25417df... · artifact: none · FM_OUTBOUND_NO_ARTIFACT [exit 3] $ fm-outbound-artifact.sh emit waiting-item requested: fm-ob-f8fd3f91130f on o/control#2 $ fm-outbound-artifact.sh emit waiting-item # idempotent already requested: fm-ob-f8fd3f91130f (comment 900) --- record under that request id rewritten to name another request --- $ fm-outbound-artifact.sh check outbound artifacts: 0 satisfied, 1 defect, 0 could-not-observe DEFECT - waiting with no applicable durable artifact (0) none DEFECT - the artifact exists, but the correlation record filed under its request id names a different request (1) waiting-item head: a25417df... · artifact: comment/900 · FM_OUTBOUND_IDENTITY_REFUSED [exit 3] $ fm-outbound-artifact.sh defects OUTBOUND: waiting-item has its artifact comment/900 on the forge, but the correlation record filed under fm-ob-f8fd3f91130f names a DIFFERENT request (FM_OUTBOUND_IDENTITY_REFUSED) [exit 3] --- venue unconfigured --- $ fm-outbound-artifact.sh check outbound artifacts: 0 satisfied, 0 defect, 1 could-not-observe COULD NOT OBSERVE ... FM_OUTBOUND_TRANSPORT_UNCONFIGURED [exit 4]


===== $ fm-outbound-artifact.sh status   # nothing has been emitted yet =====
outbound artifacts: 0 satisfied, 1 defect, 0 could-not-observe

DEFECT - waiting with no applicable durable artifact (1)
  waiting-item
         gate: INDEPENDENT_BROWSER_REVIEW_REQUIRED · channel: sol-control · recognised: prose
         head: a25417dfa564b42f6a5107da2cb9401088bf50e8 · artifact: none · FM_OUTBOUND_NO_ARTIFACT

DEFECT - the artifact exists, but the correlation record filed under its request id names a different request (0)
  none

COULD NOT OBSERVE - neither confirmed waiting legitimately nor confirmed defective (0)
  none

SATISFIED (0)
  none
[exit 3]

===== $ fm-outbound-artifact.sh defects  # the relay line bootstrap prints =====
OUTBOUND: waiting-item is waiting on INDEPENDENT_BROWSER_REVIEW_REQUIRED with no applicable durable artifact (FM_OUTBOUND_NO_ARTIFACT) - head a25417dfa564b42f6a5107da2cb9401088bf50e8, channel sol-control
[exit 3]

===== $ fm-outbound-artifact.sh emit waiting-item   # ask Browser Sol =====
requested: fm-ob-f8fd3f91130f on o/control#2
[exit 0]

===== $ fm-outbound-artifact.sh emit waiting-item   # idempotent, one cycle cannot duplicate =====
already requested: fm-ob-f8fd3f91130f (comment 900)
[exit 0]

===== $ fm-outbound-artifact.sh check   # the invariant now holds =====
outbound artifacts: 1 satisfied, 0 defect, 0 could-not-observe

DEFECT - waiting with no applicable durable artifact (0)
  none

DEFECT - the artifact exists, but the correlation record filed under its request id names a different request (0)
  none

COULD NOT OBSERVE - neither confirmed waiting legitimately nor confirmed defective (0)
  none

SATISFIED (1)
  waiting-item
         gate: INDEPENDENT_BROWSER_REVIEW_REQUIRED · channel: sol-control · recognised: prose
         head: a25417dfa564b42f6a5107da2cb9401088bf50e8 · artifact: comment/900 · FM_OUTBOUND_SATISFIED
[exit 0]

===== now corrupt the correlation: the record filed under the request id names a DIFFERENT request =====

===== $ fm-outbound-artifact.sh check   # split headings: observed artifact is NOT filed as missing =====
outbound artifacts: 0 satisfied, 1 defect, 0 could-not-observe

DEFECT - waiting with no applicable durable artifact (0)
  none

DEFECT - the artifact exists, but the correlation record filed under its request id names a different request (1)
  waiting-item
         gate: INDEPENDENT_BROWSER_REVIEW_REQUIRED · channel: sol-control · recognised: prose
         head: a25417dfa564b42f6a5107da2cb9401088bf50e8 · artifact: comment/900 · FM_OUTBOUND_IDENTITY_REFUSED

COULD NOT OBSERVE - neither confirmed waiting legitimately nor confirmed defective (0)
  none

SATISFIED (0)
  none
[exit 3]

===== $ fm-outbound-artifact.sh defects  # the relay names the artifact it observed =====
OUTBOUND: waiting-item has its artifact comment/900 on the forge, but the correlation record filed under fm-ob-f8fd3f91130f names a DIFFERENT request (FM_OUTBOUND_IDENTITY_REFUSED) - the artifact is present and its identity is refused, so the wait is not satisfied
[exit 3]

===== $ fm-outbound-artifact.sh check   # unconfigured venue: unevaluable, never satisfied =====
outbound artifacts: 0 satisfied, 0 defect, 1 could-not-observe

DEFECT - waiting with no applicable durable artifact (0)
  none

DEFECT - the artifact exists, but the correlation record filed under its request id names a different request (0)
  none

COULD NOT OBSERVE - neither confirmed waiting legitimately nor confirmed defective (1)
  waiting-item
         gate: INDEPENDENT_BROWSER_REVIEW_REQUIRED · channel: sol-control · recognised: prose
         head: a25417dfa564b42f6a5107da2cb9401088bf50e8 · artifact: none · FM_OUTBOUND_TRANSPORT_UNCONFIGURED

SATISFIED (0)
  none
[exit 4]
Evidence: reconcile: one item's refused transport does not suppress the report

$ fm-outbound-artifact.sh reconcile # item one refuses transport transport failed after 3 attempts; the request is NOT lost - fm-ob-7a6807cab266 is checkpointed at .../outbound-artifacts/fm-ob-7a6807cab266.json waiting-item remains waiting with no artifact. requested: fm-ob-63a13ad444af on o/control#2 OUTBOUND: reconciliation refused an emit (status 4) - its named reason is printed above, and the sweep below still reports every other item OUTBOUND: waiting-item is waiting on INDEPENDENT_BROWSER_REVIEW_REQUIRED with no applicable durable artifact (FM_OUTBOUND_NO_ARTIFACT) - head 5039adb0..., channel sol-control [exit 4] posts the forge accepted: 1 (the second item was still attempted) emit locks left behind: 0


===== $ fm-outbound-artifact.sh reconcile   # item one refuses transport =====
transport failed after 3 attempts; the request is NOT lost - fm-ob-7a6807cab266 is checkpointed at /tmp/fm-outbound-artifact-tests.kayuMA/demo-reconcile/home/data/outbound-artifacts/fm-ob-7a6807cab266.json
waiting-item remains waiting with no artifact.
requested: fm-ob-63a13ad444af on o/control#2
OUTBOUND: reconciliation refused an emit (status 4) - its named reason is printed above, and the sweep below still reports every other item
OUTBOUND: waiting-item is waiting on INDEPENDENT_BROWSER_REVIEW_REQUIRED with no applicable durable artifact (FM_OUTBOUND_NO_ARTIFACT) - head 5039adb098e8f1a8e3ff12a112710786c85352f1, channel sol-control
[exit 4]

posts the forge accepted: 1 (the second item was still attempted)
emit locks left behind: 0
Evidence: Session start: a deadlined sweep relays its finding and still marks itself incomplete

$ fm-bootstrap.sh (session start, FM_OUTBOUND_BOOTSTRAP_DEADLINE=1) OUTBOUND: early-item is waiting on a gate with no applicable durable artifact OUTBOUND: sweep unevaluable - bootstrap deadline expired after 1s; any OUTBOUND line above this one is what the sweep established before it was stopped, and the sweep is INCOMPLETE

===== $ fm-bootstrap.sh   (session start, FM_OUTBOUND_BOOTSTRAP_DEADLINE=1) =====
OUTBOUND: early-item is waiting on a gate with no applicable durable artifact
OUTBOUND: sweep unevaluable - bootstrap deadline expired after 1s; any OUTBOUND line above this one is what the sweep established before it was stopped, and the sweep is INCOMPLETE
Evidence: Dead-predicate control run against this repository (the CI invariants invocation)

$ bin/fm-dead-predicate-check.sh 233 consumer file(s) UNCHECKED - outside the accepted syntax, so no call site in them was read: ... [230 lines elided] ... This list measures how much of the repository this control can see. Shorten it by making files parse. fm-dead-predicate-check: ok enrolled=3 scanned=119 unchecked=233 alive=77 could_not_observe=0 marked=0 [exit 0]

$ bin/fm-dead-predicate-check.sh   # the control the CI invariants job runs

233 consumer file(s) UNCHECKED - outside the accepted syntax, so no call site in them was read:
  /home/shane/.no-mistakes/worktrees/5f306883d81c/01M0AJ1DY9VXKMTEZ90GM60T0W/bin/backends/cmux.sh:466   done <<EOF
  /home/shane/.no-mistakes/worktrees/5f306883d81c/01M0AJ1DY9VXKMTEZ90GM60T0W/bin/backends/herdr.sh:888   IFS=$'\t' read -r marker ws index socket focused order <<FMEOF
  /home/shane/.no-mistakes/worktrees/5f306883d81c/01M0AJ1DY9VXKMTEZ90GM60T0W/bin/backends/orca.sh:70 function scalar(v) {

... [230 unchecked-file lines elided] ...

This list measures how much of the repository this control can see. Shorten it by making files parse.
fm-dead-predicate-check: ok enrolled=3 scanned=119 unchecked=233 alive=77 could_not_observe=0 marked=0
[exit 0]
Evidence: Focused suite logs (outbound-artifact / dead-predicate / bootstrap)
ok - control 1 RED: a review-required item with no request is a defect
ok - inventory: a branch nobody annotated is found by enumeration, not by prose
ok - inventory identity: a shared branch name cannot suppress another project
ok - inventory: duplicate local and remote refs consume one probe
ok - inventory: a branch already contained in the landing target is not unsubmitted work
Squash commit -- not updating HEAD
ok - inventory squash landing: content containment excludes a squash-merged branch
ok - inventory landing target: an unresolvable target is could-not-observe
ok - inventory registry: an absent registry is not an empty inventory
ok - inventory refs: a failing for-each-ref is could-not-observe
ok - inventory head: failing ref and object-width reads are could-not-observe
ok - inventory posture: an unreadable project mode is could-not-observe
ok - inventory default branch: a failed name read is could-not-observe
ok - inventory candidate refs: failed target enumeration is could-not-observe
ok - inventory read class: every owned read boundary fails closed
ok - control 1 GREEN: the same item with a request on the forge is satisfied
ok - presence identity: exact marker and complete embedded identity are required
ok - control 2 RED: a moved head makes the previous request inapplicable
ok - control 2 GREEN: the moved head generates a fresh request with a new identity
ok - control 3: six cycles at one identity posted exactly one request
ok - control 3 NEGATIVE: with no observable prior request the same path does post again
ok - control 4 GREEN: two transient failures retried through to one request
ok - control 4 RED: an exhausted transport keeps the checkpoint and leaves the item red
ok - control 4 RECOVERY: a crashed emit adopts its own posted request instead of duplicating
ok - ambiguous post: retry re-observes an accepted request before posting again
ok - dedupe observation: emission fails closed unless absence is conclusive
ok - cadence: reconcile emits sol-control and leaves pull requests detect-only
ok - cadence: reconcile reports the emit's own verdict, never the next sweep's
ok - cadence locks: every emit releases its own lock rather than waiting for process exit
ok - cadence partial: a refused emit still leaves a complete report for every other item
ok - control 5: a ruling on the request wakes exactly the item that asked
ok - verdict: two verdict lines refuse and name the count, rather than resolving by position
ok - verdict: exactly one verdict is read, and zero refuses while naming the count
ok - control 6 RED: an unrelated ruling refuses and cannot wake the waiting item
ok - poll: inbound path records exact rulings without claiming work resumed
ok - poll: exactly one complete ruling identity is required and ambiguity names its count
ok - backlog identity joins refuse duplicate ids and name the count
ok - stale ruling: moved head is refused before fresh reconciliation
ok - identity transitions: ruling and resume require every bound axis
ok - poll severity: could-not-observe outranks later defects
ok - control 7: request, ruling, resumed item and disposition form one closed chain
ok - terminal: a closed request cannot satisfy a current wait
ok - correlation: missing, invalid, and mismatched records refuse without overwrite
ok - resume chain: disposition cannot bypass resumed work
ok - control 8: an incomplete binding refuses to emit and leaves the item red
ok - control 9: an unobservable artifact is could-not-observe, never a pass
ok - control 10: the pull-request channel detects but never creates the artifact
ok - combined prose: independent review takes precedence over contribution handoff
ok - combined architecture prose: architecture ruling takes precedence over contribution handoff
ok - typed declaration: declaration is authoritative over conflicting prose
ok - typed declaration: missing and invalid gates are incomplete bindings
ok - PR venue: detection uses the declared contribution target
ok - PR detection requires the pull request head to equal the waiting head
ok - PR detection refuses multiple exact-head matches and names the count
ok - control 11: a finished branch with no pull request is a transport defect
ok - control 12: a landed row carrying the same hold prose is not waiting
ok - control 13: an unreadable backlog row is could-not-observe, never clear
ok - forge error: an error payload is no observation, not a head
ok - truncation: a hold reason cut off at its first comma is still recognised
ok - untyped: an unclassifiable gate stays empty and does not absorb the tier
ok - identity: gate, project, repo, item, pull request and head each bind, and are stable
ok - binding: head width comes from the target repository, and resolvability beats shape
ok - forge head: authoritative PR heads keep strict width without local resolution
ok - forge record: provenance survives dedupe, sweep, ruling, and resume
ok - unfinished: an in-progress branch is could-not-observe, not a defect
ok - lifecycle conflict: completed and open records are could-not-observe
ok - unparsable lifecycle: an unreadable row is unobserved, never a conflict
ok - non-ship: a completed investigation branch is neither a defect nor a gap
ok - archive: completion evidence rotated out of the backlog is still read
ok - archive unreadable: an incomplete corpus is could-not-observe, not a verdict
ok - sections: could-not-observe is counted and sectioned apart from defects
ok - sender: exactly one whole-value closed-enum sender, prefix and duplicate refused
ok - sender e2e: a wrong sender wakes nothing, a right one still does

all fm-outbound-artifact tests passed

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ⚠️ bin/fm-outbound-artifact.sh:1412 - RECONCILE_RC=${PIPESTATUS[1]} reads the wrong pipeline stage. The pipeline is printf | jq | while, so index 1 is jq (always 0 when the sweep JSON parses) and the while loop that runs cmd_emit is index 2. Verified: printf|cat|while ... exit 7 yields PIPESTATUS=(0 0 7). Every emit refusal inside reconcile — FM_OUTBOUND_EMIT_IN_FLIGHT (3), FM_OUTBOUND_ARTIFACT_UNOBSERVED (4), TRANSPORT_UNCONFIGURED (4), transport exhaustion (4) — is therefore discarded, and reconcile falls through to the final sweep_exit, which reports the surviving row as exit 3 (a defect) instead of the emit's own 4 (could-not-observe). That is exactly the 3-vs-4 collapse this module's header forbids, and no test covers a failing emit inside reconcile. Use ${PIPESTATUS[2]}.
  • ⚠️ bin/fm-bootstrap.sh:1000 - outbound_artifact_report reuses FM_OUTBOUND_TIMEOUT as the whole-sweep bootstrap deadline, but bin/fm-outbound-artifact.sh:76,90 documents and uses the same variable as the timeout for ONE forge/git observation. With the shared default of 15, a single slow probe consumes the entire sweep deadline, and a reconcile that is allowed up to FM_OUTBOUND_MAX_PROBES=40 probes at 15s each cannot finish inside 15s on any non-trivial fleet. On timeout the collected output is rm -f'd unprinted, so real defects the sweep already found are replaced by OUTBOUND: sweep unevaluable - bootstrap deadline expired, and the mutating reconcile is SIGTERM'd mid-run at every session start. The invariant then reads as permanently blind — the reads-as-working-while-doing-nothing shape the module exists to refuse. Raising the value cannot separate the two concerns because it widens both. Needs its own knob (e.g. FM_OUTBOUND_BOOTSTRAP_DEADLINE) and a deadline larger than one probe timeout.
  • ⚠️ bin/fm-outbound-artifact.sh:1428 - Option parsing uses RATIONALE=${2:-}; shift 2 with no [ &#34;$#&#34; -ge 2 ] guard (same at lines 1441-1443, 1454, 1463-1464). In bash, shift 2 with only one positional left changes nothing and returns non-zero, so fm-outbound-artifact.sh emit x --rationale-file, ruling --request, resume --request, or close --disposition with a missing value spins forever instead of erroring. The repo's own idiom guards this — bin/fm-certify.sh:141-144 writes --repo) [ &#34;$#&#34; -ge 2 ] || die &#34;--repo needs a value&#34;; REPO=$2; shift 2. Adopt that guard.
  • ⚠️ bin/fm-outbound-artifact.sh:798 - The stale count calls records_all inside the per-row sweep loop. records_all re-reads every file in $RECORD_DIR and runs record_valid_for_id on each, which is ~8 jq invocations plus a git rev-parse per record. Cost is O(waiting_rows x records) processes, re-paid on each of reconcile's two sweeps, inside a session-start deadline that is 15s by default. The value does not depend on the row beyond the item/head filter, so hoisting one records_all snapshot above the loop and filtering it per row is behaviour-preserving.
  • ⚠️ bin/fm-outbound-artifact.sh:787 - In the sol-control branch of sweep, case $record_rc in 1) rc=4 ;; *) rc=5 ;; esac maps both record_read 2 (could-not-observe) and 5 (identity mismatch) to rc=5, which the verdict table renders as FM_OUTBOUND_RECORD_UNREADABLE. A correlation record that is perfectly readable but belongs to a different request is then reported to the operator as an unreadable file, sending them to look for corruption or permissions instead of the correlation defect. This is the exact distinction record_identity_verdict's own header (lines ~372-392) says must not be collapsed. Give the mismatch case its own rc and surface FM_OUTBOUND_IDENTITY_REFUSED.
  • ⚠️ bin/fm-outbound-artifact.sh:941 - supersede_other_heads walks EVERY record in $RECORD_DIR — including closed and superseded ones for unrelated items — and returns 2/5 on the first record whose identity will not revalidate, which makes cmd_emit die at line 1044-1048. Revalidation runs fm_outbound_binding_missing -> fm_outbound_head_valid, which for head_source of declared/local/empty requires the head object to still resolve in $PROJECTS/&lt;project&gt;. Once a branch is squash-merged and gc'd, a project clone is removed, or a project is retired from projects/, that historical record becomes permanently unvalidatable and every future emit for every item in this home refuses with exit 4. The same root cause makes show, resume, and close on those old records unreadable. Fail-closed is clearly intended, but the blast radius across unrelated items looks wider than intended — consider scoping the walk to records for the item being emitted, or exempting terminal (closed/superseded) records.
  • ℹ️ bin/fm-outbound-artifact.sh:1042 - cmd_emit sets the global EMIT_LOCK and relies on the EXIT trap to release it, but under reconcile it runs inside the while-loop subshell of a pipeline, and bash resets caught traps in subshells (verified). The per-rid lock directory is never released: within one reconcile each successive item overwrites EMIT_LOCK without releasing the previous, and all of them survive the run. Recovery works only because fm_lock_try_acquire steals a lock whose numeric pid is dead, so the impact is stale .&lt;rid&gt;.lock directories accumulating in $RECORD_DIR rather than a hang. Releasing the lock explicitly at the end of cmd_emit closes it.
  • ℹ️ bin/fm-outbound-artifact.sh:1403 - reconcile runs cmd_poll plus two full sweep passes in one process, and the probe budget is a single process-wide counter file capped at FM_OUTBOUND_MAX_PROBES (default 40). The pre-emit sweep can exhaust the budget on its own (the verification doc records a measured 42-branch inventory), so the post-emit sweep runs capped and sweep_exit returns 4 with probe cap 40 reached even when reconciliation succeeded. Either budget per sweep or document that reconcile needs roughly double the cap of a bare check.
  • ℹ️ bin/fm-outbound-artifact.sh:567 - In finished_work_evidence, the candidate grep -F -e &#34;- [x] $item &#34; matches the token anywhere in a line, while the state extraction sed -n &#39;s/^- \[x\] .*/completed/p&#39; is anchored to line start. A matching line that is indented or quotes the row mid-line yields zero parsed states, count=0, and the [ &#34;$count&#34; -eq 1 ] || return 3 branch reports FM_OUTBOUND_WORK_LIFECYCLE_CONFLICT ("lifecycle records disagree") for what is actually "no state could be parsed". Return 2 (WORK_STATE_UNOBSERVED) when count is 0 and reserve 3 for count &gt; 1.
  • ℹ️ bin/fm-dead-predicate-check.sh:2 - The new class control is not invoked by bin/fm-lint.sh, CI, or bootstrap — the only repo-wide guarantee is test_outbound_library_stays_enrolled, which pins the enrollment marker but never runs the checker over the repository. As shipped it therefore catches a new dead predicate only when someone runs it by hand. Separately, function_has_call_site and the call-form validation loop each re-run the char-by-char strip_quoted awk per (consumer file x function), so a repo-wide run is O(files x functions) awk processes over the ~118 parseable files; caching the stripped text per file would make wiring it into lint practical.

🔧 Fix: fix outbound reconcile status, lock, verdict and deadline splits
5 issues (2 warnings, 3 infos) still open:

  • ⚠️ bin/fm-outbound-artifact.sh:1483 - Now that cmd_emit runs in the main shell, its refusals exit the process directly, so the final sweep and render_defects at lines 1485-1486 never run. One item's emit refusal therefore suppresses the OUTBOUND report for every other item. The exit-3 case is the worst: bin/fm-bootstrap.sh:1043 deliberately skips its reconciliation exited N line for status 3, so a FM_OUTBOUND_EMIT_IN_FLIGHT (concurrent session) or FM_OUTBOUND_INCOMPLETE_BINDING refusal leaves session start printing only cmd_emit's un-prefixed stderr - no OUTBOUND: line at all, which is the exact token .agents/skills/bootstrap-diagnostics/SKILL.md triggers on, so the handling skill is not loaded and the sweep's other defects are invisible that session. Render the defect report before propagating the emit status (e.g. capture the emit rc, run sweep; render_defects, then exit with it).
  • ⚠️ bin/fm-outbound-artifact.sh:874 - The new rc=6 row is emitted with an empty artifact field, so it renders as artifact: none and render_defects (line 901) describes it with the fixed prose "is waiting on <gate> with no applicable durable artifact". But this branch is reached only after sol_artifact_present returned 0 and set $present to a real comment id - the artifact exists on the forge; what is wrong is that the local record filed under that request id names a different request. The operator is told the artifact is missing when it is not, which is the same misdescription the split was made to avoid. Pass comment/$present as the artifact argument, and give FM_OUTBOUND_IDENTITY_REFUSED its own defect sentence rather than the no-artifact one.
  • ℹ️ .agents/skills/bootstrap-diagnostics/SKILL.md:108 - FM_OUTBOUND_IDENTITY_REFUSED is a new token that now reaches session start on a DEFECT line, but the skill's OUTBOUND entry says "Read the token to choose the repair" and then enumerates only FM_OUTBOUND_NO_ARTIFACT, STALE_HEAD, INCOMPLETE_BINDING, and HEAD_UNOBSERVED. An operator hitting the new token gets a defect with no repair. The repair is distinct and worth stating: the correlation record filed under that request id belongs to another request, so it is re-keyed or removed, not re-emitted. (FM_OUTBOUND_CORRELATION_RECORD_MISSING has the same gap and predates this round.)
  • ℹ️ bin/fm-outbound-artifact.sh:1483 - cmd_emit &#34;$ITEM&#34; &#34;&#34; 0 drops the previous || exit $?. It is correct today only because every cmd_emit failure path calls exit/die rather than returning; the moment one returns non-zero, reconcile continues silently and reports the next sweep's verdict instead - the same "status credited to a stage that did not produce it" class the comment directly above this line warns about. Keeping || exit $? costs nothing and makes the property local rather than dependent on a callee's internal convention.
  • ℹ️ .github/workflows/ci.yml:429 - Measured on this machine (read-only run of the shipped command, not the test suite): exit 0 in 67s, enrolled=3 scanned=119 unchecked=233 alive=76 could_not_observe=0. The per-file quote-walk cache and the index($0, fn) prefilter make the repo-wide run viable, and the prefilter is sound - every rule that concludes anything embeds the function name, so a line without it could never have matched. Noting the margin rather than asking for a change: 233 of 352 consumer files are UNCHECKED, and exit 4 is red by design, so the green result holds only while every one of the 76 enrolled predicates keeps at least one call site in a parseable file. Moving a call site into a heredoc-bearing file (most test helpers) flips that predicate to could-not-observe and turns CI red with no legitimate suppression available.

🔧 Fix: keep reconcile reporting and name observed outbound artifacts
3 issues (1 error, 1 warning, 1 info) still open:

  • 🚨 bin/fm-outbound-artifact.sh:1532 - ( trap release_emit_lock EXIT; cmd_emit &#34;$ITEM&#34; &#34;&#34; 0 ) is not an accepted call-site form for bin/fm-dead-predicate-check.sh: its trap rule is anchored ^[[:space:]]*trap[[:space:]]+&lt;fn&gt;, and here a ( precedes trap. The validation loop therefore declares the whole of bin/fm-outbound-artifact.sh an UNCHECKED consumer, which removes the file that holds nearly every enrolled predicate's call site. Measured at this head by running the shipped command: exit 4, could_not_observe=52, with its own output naming bin/fm-outbound-artifact.sh:1532 unsupported call-site form for release_emit_lock. At the previous head the same command was exit 0 with could_not_observe=0. This turns red both the CI step added in round 2 (.github/workflows/ci.yml run: bin/fm-dead-predicate-check.sh) and test_repository_has_no_dead_predicates_under_the_control, which round 2 tightened to require exit 0. Fix inside the control's own syntax without changing behaviour: put the trap on its own line - ( newline trap release_emit_lock EXIT newline cmd_emit &#34;$ITEM&#34; &#34;&#34; 0 newline ) - which matches the anchored trap rule. (An # indirect-call: release_emit_lock comment also works but is the weaker choice, since it suppresses validation of that name file-wide.)
  • ⚠️ bin/fm-bootstrap.sh:1034 - The new header (bin/fm-outbound-artifact.sh:30, 55-62) promises the report renders "on the way out REGARDLESS", but at the relay that is the primary consumer it does not: when FM_OUTBOUND_BOOTSTRAP_DEADLINE expires, bootstrap rm -f &#34;$tmp&#34; and prints only the deadline line, discarding every OUTBOUND row the child had already written. Continuing after a refusal instead of exiting makes that path more reachable, not less: with the forge unobservable, each item now burns up to FM_OUTBOUND_ATTEMPTS transport attempts plus backoff before the next is tried, and the probe cap only short-circuits after 40 probes - so N refusing items serialise well past the 60s deadline where the old early exit stopped at the first. The net effect is the same silence the round-2/3 fixes were about, reached through the enclosing deadline instead of through control flow. Either relay the partial child output alongside the unevaluable line, or stop the emit phase (while still rendering) once a refusal says the forge cannot be observed at all.
  • ℹ️ bin/fm-outbound-artifact.sh:947 - render_defects now gives the identity refusal its own sentence, but the check/status human view still files the same row under render_section defect &#39;DEFECT - waiting with no applicable durable artifact&#39;. On a fleet whose only defect is an identity refusal, that heading asserts an absence directly above a row body reading artifact: comment/&lt;id&gt;, so the two renderers now disagree about the same row. Either soften the heading to cover both (e.g. "DEFECT - the artifact this wait depends on is missing or not identifiable") or let the section name what its rows actually say.

🔧 Fix: relay deadlined sweeps and split the outbound defect heading
1 info still open:

  • ℹ️ tests/fm-outbound-artifact.test.sh:457 - Splitting the defect section changed what grep &#39;DEFECT - waiting with no applicable durable artifact (0)&#39; proves. Three pre-existing assertions (lines 457, 476, 573) use that heading's zero-count as a proxy for "the fleet has no defect"; it now means only "no NON-identity defect", since an identity refusal is counted under the second heading. Not currently wrong - all three are branch-inventory fixtures on the pull-request channel, where the rc=6 identity path is unreachable, and each also asserts its own exit status independently - but the idiom is now a trap for the next fixture that does reach the sol-control channel. The unambiguous total is one line above: outbound artifacts: N satisfied, M defect, K could-not-observe (bin/fm-outbound-artifact.sh:947) still counts every defect row together, so assertions that mean "zero defects" are better anchored there.
✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-outbound-artifact.test.sh — all cases pass
  • bash tests/fm-dead-predicate-check.test.sh — all cases pass
  • bash tests/fm-bootstrap.test.sh — all cases pass, including bootstrap relays what a deadlined sweep established, and still marks it incomplete and bootstrap preserves definitive outbound defect classification
  • Manual CLI drive of fm-outbound-artifact.sh status|defects|emit|check against a real fixture home with the suite's forge shim (/tmp/no-mistakes-evidence/01M0AJ1DY9VXKMTEZ90GM60T0W/drive-cli.sh)
  • Manual CLI drive of fm-outbound-artifact.sh reconcile with the first item's transport exhausted, checking post count and leftover emit locks (drive-reconcile.sh)
  • Manual fm-bootstrap.sh run with FM_OUTBOUND_BOOTSTRAP_DEADLINE=1 against a sweep that establishes a finding then stalls (drive-bootstrap-deadline.sh)
  • bin/fm-dead-predicate-check.sh against this repository — the same invocation the CI invariants job runs (.github/workflows/ci.yml:433)
🔧 **Document** - 1 issue found → auto-fixed (2) ✅
  • ℹ️ bin/fm-outbound-artifact-lib.sh:177 - bin/fm-outbound-artifact-lib.sh declares two tokens the code never emits: FM_OUTBOUND_TOKEN_AMBIGUOUS (FM_OUTBOUND_AMBIGUOUS_CANDIDATES) and FM_OUTBOUND_TOKEN_ARCHIVE_UNREADABLE (FM_OUTBOUND_DONE_ARCHIVE_UNREADABLE); no bin/ or tests/ file references either, and an unreadable done-archive is actually reported as FM_OUTBOUND_WORK_STATE_UNOBSERVED. The header presents the token block as the closed gate vocabulary, so it documents two classifications that cannot occur - the same 'exists and is never consulted' shape the dead-predicate control in this change was built for, one level down at constants rather than functions. Left alone because the resolution is a code change (emit them or delete them), which this documentation pass may not make.

🔧 Fix: emit ambiguity and unreadable-archive outbound classification tokens
2 infos still open:

  • ℹ️ bin/fm-outbound-artifact-lib.sh:158 - The invariant "every token declared in this block must have an emit site" is now stated in the lib header and was verified by hand for all 26 tokens, but nothing enforces it. That is the same drift the dead-predicate control exists to stop, and the control provably cannot cover this case: it scans function definitions for call sites, so a constant declared and never emitted is outside its universe by construction. The next token added to the block can go unemitted exactly as these two did, and the only thing that would catch it is another review finding. A mechanical check is cheap (assert each FM_OUTBOUND_TOKEN_* name appears at a non-declaration site) but adding a new control is a design decision, not a documentation fix, so it is proposed rather than taken: whether the check lives in bin/fm-dead-predicate-check.sh as a second mode, in bin/fm-lint.sh, or as a case in tests/fm-outbound-artifact.test.sh determines what its universe and could-not-observe answer are, which is the part worth deciding deliberately.
  • ℹ️ bin/fm-outbound-artifact.sh:444 - Scope note, not an unresolved gap. This step's standing rule is documentation-only, but the accepted fix instruction required emitting two tokens, which is a behaviour change; the mislabelled classification could not be fixed in prose. One existing test assertion (tests/fm-outbound-artifact.test.sh, the unreadable-archive case) pinned the old wrong label FM_OUTBOUND_WORK_STATE_UNOBSERVED and would have gone red, so it was corrected to the new token plus a negative assertion that the neighbour is absent; the two ambiguity sites got the same paired assertions. No refusal behaviour, exit status, printed count, or existing rationale comment was changed at any of the three sites. Flagged so the deviation from the documentation-only rule is on the record rather than discovered in the diff.

🔧 Fix: enforce outbound token emit-site invariant with module-local control
✅ Re-checked - no issues remain.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@sbracewell64 sbracewell64 reopened this Aug 16, 2026
@sbracewell64
sbracewell64 force-pushed the fm/control-plane-outbound-transport-invariant branch from 0abb3ae to 3857f74 Compare August 16, 2026 19:24
@sbracewell64 sbracewell64 changed the title feat(bin): enforce outbound artifact transport invariant feat(bin): enforce the outbound transport invariant Aug 16, 2026
@sbracewell64
sbracewell64 force-pushed the fm/control-plane-outbound-transport-invariant branch from 8ba1a30 to 71bfd82 Compare August 16, 2026 21:10
@sbracewell64 sbracewell64 changed the title feat(bin): enforce the outbound transport invariant feat(bin): enforce the outbound artifact transport invariant Aug 16, 2026
@sbracewell64
sbracewell64 force-pushed the fm/control-plane-outbound-transport-invariant branch from e68e445 to b19f5fc Compare August 17, 2026 01:23
@sbracewell64 sbracewell64 changed the title feat(bin): enforce the outbound artifact transport invariant feat(bin): enforce outbound artifact transport invariants Aug 17, 2026
@sbracewell64 sbracewell64 changed the title feat(bin): enforce outbound artifact transport invariants feat(bin): enforce outbound transport invariant Aug 17, 2026
@sbracewell64 sbracewell64 changed the title feat(bin): enforce outbound transport invariant feat(bin): enforce outbound transport invariants Aug 17, 2026
@sbracewell64
sbracewell64 force-pushed the fm/control-plane-outbound-transport-invariant branch 2 times, most recently from 6ef2adf to 3405b04 Compare August 17, 2026 23:46
@sbracewell64 sbracewell64 changed the title feat(bin): enforce outbound transport invariants feat(bin): enforce outbound artifact transport invariant Aug 17, 2026
@sbracewell64
sbracewell64 force-pushed the fm/control-plane-outbound-transport-invariant branch 2 times, most recently from 88d4060 to b773a6a Compare August 18, 2026 02:45
@sbracewell64 sbracewell64 changed the title feat(bin): enforce outbound artifact transport invariant feat(bin): enforce durable outbound transport invariant Aug 18, 2026
@sbracewell64 sbracewell64 changed the title feat(bin): enforce durable outbound transport invariant feat(bin): enforce outbound artifact transport invariant Aug 18, 2026
@sbracewell64
sbracewell64 force-pushed the fm/control-plane-outbound-transport-invariant branch from c0cc29f to 1d606b3 Compare August 18, 2026 05:19
@sbracewell64 sbracewell64 changed the title feat(bin): enforce outbound artifact transport invariant chore: no-mistakes(review): Preserve could-not-observe classification for invalid ruling senders Aug 18, 2026
@sbracewell64
sbracewell64 force-pushed the fm/control-plane-outbound-transport-invariant branch from 1d606b3 to 06e11d6 Compare August 18, 2026 13:42
@sbracewell64 sbracewell64 changed the title chore: no-mistakes(review): Preserve could-not-observe classification for invalid ruling senders feat(bin): enforce the outbound artifact transport invariant Aug 18, 2026
The control plane had one direction. An inbound detector woke firstmate when
Browser Sol replied to a control issue that already existed, and nothing owned
the outbound direction - that work reaching a gate actually emits the artifact
the gate waits on. The gap is measured on two surfaces: four SSSF pull requests
sat "pending independent acceptance" for days having never been submitted, and
three items held finished work on a real branch with no pull request opened
anywhere. Nobody rejected that work; nobody was ever shown it.

The invariant: an item may not remain in a state that implies an outstanding
outbound artifact while no applicable durable artifact exists. That condition is
a control-transport defect, not an external wait, and it never reclassifies to
not-waiting to pass.

It is stated over artifacts rather than over Sol requests specifically. Building
the narrow form and adding the pull-request form later would have produced two
mechanisms with two identity rules and two dedupe stories. Channels differ only
in whether this code may create the missing artifact: sol-control emits, because
asking a question is reversible and carries no delivery authority;
pull-request is detect-only, because opening one is a delivery action owned by
the task's selected delivery path and outward-facing on an upstream
contribution.

Exact-head applicability falls out of the identity rather than sitting beside
it: the request id digests gate, project, repo, item, pull request and head, so
a moved head computes a different id and therefore a different artifact.
Idempotency, retry and crash safety are one mechanism - a per-id lock, a
checkpoint written before transport, and recovery that re-reads the forge for
the id rather than guessing from local state. Existence is always observed on
the forge; the local record is correlation and checkpoint, never proof.

Recognition reads durable state that already exists, in two tiers, so the
invariant is not vacuous on the day it lands: a typed hold-kind marker, and a
closed prose token set covering the population that predates it. An untyped
match is reported as an incomplete binding rather than passed.

bin/fm-bootstrap.sh relays defects at every session start, because the condition
produces no failure, no error and no wake, and nothing surfaced these items
until a person went looking.

Every control is watched-red in docs/verification/outbound-transport-invariant.md
under a targeted mutation. One was vacuous on first observation: require_record's
refusal ran inside a command substitution, where exit kills only the subshell, so
an unreadable record would have been reported as an identity mismatch - a
could-not-observe collapsing into a verdict, inside the mechanism built to
prevent exactly that. Two further defects appeared only against live data: tab is
IFS whitespace, so an untyped gate absorbed the tier; and a hold reason is parsed
only to its first comma, which hid all three never-submitted items.
sbracewell64 and others added 26 commits August 18, 2026 14:09
After merging repaired main, eight predicates in the outbound library reported
COULD_NOT_OBSERVE. The cause was not the merge: bin/fm-outbound-artifact.sh:520
calls a predicate as `|| ! fm_outbound_is_sha ...`, a continuation line opening
with `||` and a negation, which the accepted call-site syntax did not list. The
whole file was therefore refused as unparseable and every predicate it consumes
lost its caller universe.

The verdict was honest - could-not-observe rather than a false DEAD, which is the
control behaving as rebuilt - but the coverage loss was real: the module's own
predicates became unverifiable by its own control.

Two fixes were available and only one of them is legitimate. Rewriting the call
site would have made the file pass; widening the accepted syntax to silence a
refusal is shaping a control around its own answer, which is the failure this
whitelist exists to prevent. So the choice was decided by measurement rather than
convenience: `|| !` as a leading continuation occurs 65 times across bin/, and
once in this module. That makes it a normal idiom of this codebase and its
absence an under-specification of the whitelist, not an oddity in the caller. The
measurement is recorded beside the rule so a reader can judge whether the
widening was earned rather than take it on faith.

Verified in both directions, because adding an accepted call form is exactly the
change that can re-open a falsification: the eight predicates resolve to alive,
and a QUOTED `|| ! dead_one` is still reported DEAD, so prose cannot confirm a
call through the new rule.

CORRECTING AN EARLIER COMMIT MESSAGE. The message of "feat(bin): enumerate
branches, so the negative claim has a universe" claims the inventory composes
"the existing landed-containment test". That is false. It reuses the library's
ref-resolution helpers and then implements containment with
`merge-base --is-ancestor`, which is commit reachability - the approach
fm-landed-lib.sh's own header warns against by name, because squash merge, rebase
and local replay each break it while the content has landed. The code was
corrected in a later commit; the claim was not, and a false statement in a commit
message is a false statement in the permanent record. History is not rewritten to
fix it because this branch has already been rebased by several actors and a
rewrite would risk work that is not mine, so the correction is recorded here
instead, where it travels with the same branch.
…its reason

Three things, all about saying what is true rather than changing behaviour.

THE COMPLETENESS CLAIM IS SCOPED, in the control's own header where a reader
meets it. The per-predicate universe check closes the class for this control's
enumeration path: every read it performs is three-valued, and a failed read
yields could-not-observe rather than a negative answer. It does NOT close the
class for the shared landing library its callers use, because
fm_landed_candidate_refs returns success whenever any candidate ref resolved, so
a push-target read that fails inside the library leaves that ref absent from a
non-empty list and an incomplete candidate set is indistinguishable from a
complete one to any caller. That gap is filed as
landed-lib-unreadable-push-target-collapses and is deliberately out of scope: the
library is shared with the worktree guard, teardown, the decision surface and the
task-base library, and changing landing semantics from a task about outbound
transport would be an unreviewed change to the guards that protect unlanded work.
A control described as class-level that silently depended on someone else's
unfixed read would be the coverage inflation the rest of this record refuses.

THE LAST SINGLE-VALUE SELECTION NOW CARRIES ITS REASON. Every other one on this
surface refuses and names its count; this one takes the first line of
merge-tree --write-tree output, which is the resulting tree OID by definition
with conflict detail after it. That is reading a fixed field, not choosing among
candidates. It was safe before and it was also indistinguishable, on reading,
from the three by-position defects fixed earlier - so the rule was refuse-and-name
OR carry a written reason, and it now carries one.

The other two by-position selections were fixed in earlier rounds and are
recorded here as discharged rather than left in the owed list: a head matched by
more than one open pull request refuses and names the count and venue, and a
backlog id matching more than one record refuses rather than choosing by
position, because duplicate ids conceal a corruption rather than merely being
ambiguous.

The verification record carries the same two statements, plus the measurement
that earned the accepted-syntax widening.
The inventory flagged every unlanded fm/* branch without a pull request as a
transport defect without ever checking that the work was finished. Ordinary
in-progress branches therefore became standing defects at every startup, and a
control that fires on every run gets discounted - which is the same silence as
reporting nothing, reached more slowly.

Candidate scoping is now three-valued rather than two. Only recorded COMPLETED
SHIP work can be a defect. Recorded non-ship work is skipped, because an
investigation produces a report and never a pull request, so its missing
submission is correct - that exclusion is what pays for reading the record at
all. Work whose state cannot be established is COULD NOT OBSERVE by name.

That third answer is the PRIMARY signal here, not a footnote, and the code says
so where the logic lives. The three items this pass was commissioned from were
released tasks whose live records were already gone, so the population it exists
to catch is the population most likely to land in could-not-observe. It
therefore gets its own count and its own headed section, empty sections still
print, and a sweep carrying any of it cannot exit clean. Rendered inline it
would read as a defect; folded into clean it would disappear.

Both durable sources are read, established by measurement rather than assumed.
Retention does NOT bound this: the backlog keeps a fixed number of completed
entries and rotates the rest into an append-only, unpruned archive holding 589
of them, so the evidence survives release indefinitely. Reading only the backlog
would make everything older than that handful unobservable. What does bound it
is record completeness - entries naming no deliverable cannot answer - and, more
largely, HOME LOCALITY: records are per-home, so a branch produced by a
secondmate has its record in that home and is invisible here. Measured live
across three projects: 42 branches, 34 join to a durable record, 8 do not.

An archive that exists but cannot be read is could-not-observe even when the
backlog alone would have answered, because a confident verdict from a corpus we
know we could not finish reading is the failure this control exists to prevent.

Inbound sender parsing is now strict at the parser boundary, and refuses BEFORE
any verdict parsing: a body that cannot establish who sent it must not be read
for what it decided. Exactly one from field, whole trimmed value compared
against a closed enum, and for an inbound ruling that value must be browser-sol.
Prefix, substring, duplicate, unknown, and a ruling claiming to come from
firstmate are all invalid, and an invalid sender wakes nothing. Whole-value
equality is the point: the malformed sender from the live incident carries the
valid role as a PREFIX, so anything weaker accepts it.

The anchor control's fixture changed and that cost is deliberate rather than
hidden. Its branch had no completion record, so under this change it correctly
became could-not-observe rather than a defect. The fixture was given the record
that makes it a genuine defect, and a separate control now asserts the
in-progress case is could-not-observe. The control was not weakened to pass.

Seven new controls, each observed failing for its intended reason in isolation:
forcing everything to read as ship work makes both in-progress branches and
completed investigations false defects; dropping the archive read loses rotated
evidence; removing the unreadable-archive guard answers from a partial corpus;
reverting to one interleaved list and folding gaps into the defect count both
break the section separation; prefix-matching the sender accepts the live
incident string; removing the sender check lets a wrongly-sent ruling wake work.
…valid ruling senders

Carried from 1d606b3 on the published head, which this candidate lacked. An
invalid inbound sender exited 3 rather than 4, so a could-not-observe collapsed
into a different verdict at the exact boundary built to keep them apart.

The comment directly above that line already said an invalid sender is
could-not-observe - not a defect and not a rejection of the ruling's content,
because we did not learn the ruling is wrong, we learned we cannot tell who sent
it. The code then exited 3 anyway. The principle was stated correctly and
implemented as its opposite three lines later.

Also carries the control this candidate was missing entirely: the same invalid
sender arriving through the POLL path rather than the direct ruling path, which
asserts exit 4 and that the request does not advance. One of the two ways a
ruling reaches this system had no coverage at all.

Verified after carrying: full outbound suite green, dead-predicate class control
clean at alive=74 could_not_observe=0, shellcheck exit 0.
@sbracewell64
sbracewell64 force-pushed the fm/control-plane-outbound-transport-invariant branch from 46d5649 to 77a93ed Compare August 18, 2026 18:17
@sbracewell64
sbracewell64 merged commit e6bc4a1 into main Aug 18, 2026
18 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant