Skip to content

DAEMON_ORIGIN Phase 2 gate: adoption needs a behavioural probe, and the obvious probe is destructive #64

Description

@iceteaSA

Filing the Tier-2 contract from #6 as a durable artifact rather than leaving it in a closed thread. Phase 1 is merged (#61); this is the gate that must pass before the daemon sets bit 6, and it is not started — so this is a settled contract awaiting build, not an implementation ask.

Why a probe is needed at all

Phase 2 has one hard precondition: every transport-direct consumer must already tolerate flag bit 6. Five modules decode subc frames without the SDK — astrocyte, broca, cerebellum, claustrum, insula — and none of them owns a decoder. subc-protocol is a path dependency for all five, so Phase-1 adoption per module is rebuild against a sibling containing Phase 1, redeploy, restart. There are no consumer-repo PRs to track.

That is what makes deploy chronology useless as a gate. A rebuild at the boundary can produce a binary that looks deployed and still hard-fails, because neither the repo nor the binary carries readable evidence of which sibling revision it compiled against. "Phase 1 merged" is not a go-signal; a go-signal must carry the sibling SHA containing Phase 1, since at least one consumer pins by SHA and resolves at build time.

The trap: the obvious probe severs the connection

The natural probe — send one frame with bit 6 set, see if it comes back ReservedFlagBits — is not a passive read. In the Rust SDK, read_frame propagates DecodeHeader errors and the client loop bubbles them with ? (see the frame-read arm in subc-client-rs's client loop, adjacent to the FrameType::Goodbye handling). A rejected header does not produce an error response; it kills the module's connection.

So a naive sweep of N unverified modules disconnects every module that fails — which is precisely the population the sweep exists to find. The probe would damage exactly the modules it identifies.

Two tiers

Tier 1 — free, non-destructive, no new mechanism. Once #59 lands, supervisor.provenance serves each module's declared build_git_sha. Check whether that SHA's ancestry contains the Phase-1 commit. This answers the question for every module that declares provenance, at zero risk.

Caveat, correctly placed: the declaration is module-authored and untrusted — the daemon serves the claim without vouching for it, and the operator's tooling judges it. That is adequate for a go/no-go, because a module lying about its build SHA fails at Tier 2, not in production.

Tier 2 — behavioural, only for modules Tier 1 cannot answer. A module that declares nothing is unverifiable, and no amount of reading closes that. Only a frame the deployed binary actually decodes proves the deployed binary decodes it.

The four properties Tier 2 must satisfy

  1. Non-destructive by construction. The probe must not rely on a rejected decode to learn the answer, since rejection severs the connection. Prefer a ModuleControlPush carrying bit 6 — unknown push ops are delivered and must-ignored, so a conformant module absorbs it silently while a non-conformant one reveals itself by how it fails, not by being killed.
  2. Three-state result, each rendered distinctly: decoded-and-ignored / dropped / no-answer. No answer is not an answer — a silent module must never render as a pass. (This is the third absence-read-as-answer case in a week, alongside drain notifications and epoch-drop records.)
  3. Bounded and operator-initiated. No implicit sweep on a timer that could walk the fleet.
  4. Probe outcomes MUST NOT consume a module's crash/restart budget. (Maintainer's addition, non-negotiable — and the one I missed.) Supervised modules exit on terminal connection state so the daemon owns resurrection, which means an induced disconnect is a counted respawn. Without an exemption, a Tier-2 sweep of N unverifiable modules walks each one toward its disable threshold — the same class of failure that took aft down under health-probe misses, with a different trigger. The probe needs the same exemption operator-initiated restarts already have.

Sequencing

Phase 2 emission waits on a Tier-1 census plus scheduled Tier-2 for stragglers, and the emission change ships as a separate reviewed PR with the census attached. Two scheduling facts from the consumer seats: one module runs a 4-hour rotation whose capture series a mid-latch restart would confound, so the boundary should avoid it; another is ready to rebuild on demand and its flag-decode path is byte-identical to shared head today, making Phase 1 a genuine delta there.

Not proposing an implementation here

Whether the daemon should own a probe verb at all is open. It is the only component that holds both the module connection and the supervisor state, which argues for it — but it also means the daemon would be probing its own children, and property 4 exists because that relationship has teeth. Happy to take this once #59 merges and the Tier-1 census is real.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions