Files
felhom.eu/documentation/tests/wire-contract-gate-2026-08-08/BEFORE.md
T
admin 3bf62b95bb
gates / gates (push) Successful in 33s
fix(gate): the wire-contract search shelled out to grep and read its failure as a finding
CI convicted ALL 174 checked tags while the pre-push hook was green. Cause, read from the run log
rather than guessed at the second attempt: the search used `grep -rnE --include=…`, and the CI
runner's image carries python3 and git and deliberately little else — its grep does not support
`--include`, so stdout was empty and the gate read empty as "the tag is absent".

That is a gate silently treating a tool failure as a finding, which is worse than no gate, and it is
exactly the error-swallowing this repo forbids. A green from it would have been just as untrustworthy
as the red.

Fixed by removing the dependency, not by working around it: the search is now pure Python — one
token index per receiving repo, built in a single pass, no subprocess. Faster too (one walk instead
of ~350 greps), and unreadable-file / empty-repo cases now exit 2 INCONCLUSIVE rather than reporting
absence.

THE BEFORE CAPTURE WAS RE-VERIFIED, NOT RE-GENERATED — the stronger claim. All 40 fields recorded in
BEFORE.md were re-tested against the new implementation: agree=40, disagree=0, i.e. exactly the four
this session fixed are now present and the other 36 still absent. The number 40 stands under both
implementations; only the mechanism changed. The whole-token property survives by construction — a
token index treats `healed_at` and `privsep_healed_at` as distinct tokens.

This is the THIRD instrument defect this gate's own controls caught before it was trusted, after the
substring false negative and the dr_recipe over-opacity. The first two were caught by re-finding the
known instances; this one by the CI-versus-hook disagreement the workflow's alarm mail explicitly
says outranks whatever the push was for.
2026-08-08 09:10:59 +02:00

6.0 KiB
Raw Blame History

The wire-contract gate, run against the tree at b7fb211 on 2026-08-08

Deliverable 1 of the G-1/R-260 session: the gate FAILING, before any field was fixed. Captured verbatim. If this had been green, the gate would not work and that would be the finding.

wire-contract gate — 210 tag(s) checked across 3 declared wire(s); 51 skipped (generic / opaque / allowlisted)

  agent -> hub   (POST /host-report)
  the RECEIVER (hub) contains no occurrence of:
      applied_at                       (emitted at pbs_dr.applied_at)
      checked_at                       (emitted at guest_net.checked_at)
      cpu_temp_c                       (emitted at host.cpu_temp_c)
      damped                           (emitted at guest_net.guests.damped)
      dhclient_alive                   (emitted at guest_net.guests.dhclient_alive)
      disk_bytes                       (emitted at guests.spec.disk_bytes)
      guest_net                        (emitted at guest_net)
      has_route                        (emitted at guest_net.guests.has_route)
      heal_succeeded                   (emitted at guest_net.guests.heal_succeeded)
      healed_at                        (emitted at oob.healed_at)
      healed_recently                  (emitted at mgmt_plane.healed_recently)
      heals_last_hour                  (emitted at guest_net.guests.heals_last_hour)
      last_handshake_age_s             (emitted at wireguard.last_handshake_age_s)
      last_heal_at                     (emitted at guest_net.guests.last_heal_at)
      loadavg                          (emitted at host.loadavg)
      memory_bytes                     (emitted at guests.spec.memory_bytes)
      memory_total_bytes               (emitted at host.memory_total_bytes)
      memory_used_bytes                (emitted at host.memory_used_bytes)
      model_name                       (emitted at storage_targets.smart.model_name)
      mount_inventory                  (emitted at restore_tests.mount_inventory)
      mount_parity                     (emitted at restore_tests.mount_parity)
      operator_key_configured          (emitted at oob.operator_key_configured)
      selfupdate_pending               (emitted at selfupdate_pending)
      selfupdate_pending_version       (emitted at selfupdate_pending_version)
      uptime_seconds                   (emitted at host.uptime_seconds)
      wg_handshake_age_s               (emitted at oob.wg_handshake_age_s)

  controller -> hub   (POST /report)
  the RECEIVER (hub) contains no occurrence of:
      config_hash                      (emitted at config_hash)
      last_db_dump                     (emitted at backup.last_db_dump)
      last_integrity_check             (emitted at backup.last_integrity_check)
      load_avg_1                       (emitted at system.load_avg_1)
      load_avg_15                      (emitted at system.load_avg_15)
      load_avg_5                       (emitted at system.load_avg_5)
      memory_total_mb                  (emitted at system.memory_total_mb)
      memory_used_mb                   (emitted at system.memory_used_mb)
      migrated_to                      (emitted at storage.migrated_to)
      reporting_disabled               (emitted at reporting_disabled)
      stacks                           (emitted at stacks)
      temperature_celsius              (emitted at system.temperature_celsius)
      uptime_seconds                   (emitted at system.uptime_seconds)

  hub -> controller  (report ACK, `escrow` object)
  the RECEIVER (controller) contains no occurrence of:
      escrow_stale                     (emitted at escrow_stale)

WIRE-CONTRACT GATE FAILED: 40 emitted field(s) cannot be received.
A tag whose literal string occurs nowhere in the receiving repo cannot be decoded
by any struct, named or anonymous — encoding/json discards it on arrival.
Fix: model the field on the receiving side and say what consults it — or, if it is
deliberately not consumed, add it to ALLOWLIST WITH A REASON. Never a quiet skip.

Blind spots (a green is not full coverage — see the module docstring):
  generic tag names are not checked; reachability of a NAME is not use of a VALUE;
  only the declared ROOTS are covered (hub desired-state and the agent local API
  are NOT).
exit=1

Self-test (a planted unreachable tag on a real root)

  planted an unreachable tag on agent HostReport -> gate exit 1, planted tag convicted: True
  unplanted tree -> gate exit 1
SELFTEST OK — the gate convicts a planted unreachable tag and the plant is the only difference.
exit=0

⚠ The search implementation changed AFTER this capture, and the capture was re-verified

The run above used grep -rnE --include=… to test whether a tag occurs in the receiving repo. That works on a workstation and returns nothing on the CI runner, whose image carries python3 and git and deliberately little else — its grep does not support --include. Empty stdout was then read as "the tag is absent", so the gate convicted all 174 checked tags and CI went red while the pre-push hook was green (runs 260262).

That is the gate silently reading a tool failure as a finding, which is worse than no gate, and it is the error-swallowing this repo's rules forbid. The search is now pure Python: one token index per receiving repo, no subprocess, no external dependency.

This capture was NOT re-generated — it is re-verified, which is the stronger claim. Every one of the 40 fields recorded above was re-tested against the new implementation:

fields recorded in BEFORE.md: 40
agree=40 disagree=0

— i.e. the new implementation finds exactly the four this session fixed (operator_key_configured, wg_handshake_age_s, healed_at, escrow_stale) present, and the other 36 still absent. The number 40 stands under both implementations; only the mechanism and its portability changed.

The whole-token property also survives by construction: a token index treats healed_at and privsep_healed_at as distinct tokens, so the substring false negative that the control caught cannot come back.