# WAVE 91 DISPATCH — AD-479 v1 Federation Hardening (capability-aware routing, federated recall, designed-agent sharing, cluster health, multicast discovery, TLS surface)

## Wave summary

**Umbrella AD:** AD-479 (Federation Hardening — six roadmap.md:7027 components above the existing ZeroMQ + NATS transports)

**Concrete v1 sub-AD letters built (nine):**
AD-479a (Capability-aware peer selection — `FederationRouter.select_peers(intent_name, available_peers)` replaces the `roadmap.md:7027` line-3201 "all peers" placeholder at `src/probos/federation/router.py:32` with a `peer_has_capability(peer, intent_name)` filter; fall-through to all peers when no peer model has reported capabilities yet preserves the W87 + W89 zero-data behavior),
AD-479b (Trust-weighted peer ranking — `FederationRouter` accepts an optional `trust_network` parameter; qualified peers from 479a are ranked by `TrustNetwork.get_score(f"federation_peer:{node_id}")` descending; `min_trust_score: float = 0.0` gate on `FederationRouterConfig` drops peers below threshold; record_outcome wired from `FederationBridge.forward_intent` per remote `IntentResult.success` value),
AD-479c (Federation Hebbian routing — `FederationHebbianMap` keyed `(intent_name, peer_node_id) -> weight` mirroring the AD-271 + AD-274 `HebbianRouter` ConnectionFactory pattern at `src/probos/mesh/routing.py:39`; success increments via existing reward constant, failure decays only; weights persist via `connection_factory` on the same SQLite file; `select_peers` consults Hebbian as final tie-break after capability + trust filters),
AD-479d (`recall_federated` IntentDescriptor — read-only no-consensus intent registered on the new `FederationRecallAgent` core agent; handler broadcasts via existing `bridge.forward_intent` against capability-qualified peers from 479a, aggregates returned `Episode` snapshots, deduplicates by `episode_id`, returns merged top-k limited by config; consumers route through `IntentBus.broadcast(IntentMessage(intent="recall_federated", ...))` so the existing governance pass-through used by AD-480 inbound holds),
AD-479e (`share_designed_agent` — extends the AD-443e mobility chain wire-types at `src/probos/federation/bridge.py:232`–`:325` with a `designed_agent_payload` field on `transfer_request` carrying the agent's `instructions` string + designed template metadata; receiver runs the existing `CodeValidator` static-analysis gate per `.github/copilot-instructions.md` warm-boot rule before reconstructing the designed agent template via `AgentDesigner.register_designed_template_from_payload(...)` — on validator rejection the certificate is rejected and the chain block is rolled back),
AD-479f (Federation TLS surface — `FederationTLSConfig` Pydantic model on `FederationConfig` with `enabled: bool = False`, `cert_file: str | None = None`, `key_file: str | None = None`, `ca_file: str | None = None`, `verify_peer: bool = True`; NATS path passes through to `nats-py` TLS context via `NATSBus.start()` — config flows through `nats_bus.config.tls` per the AD-637a connection at `src/probos/messaging/nats_bus.py`; ZeroMQ CURVE encryption is parked as AD-479l with explicit forcing function — AD-637e moved default transport to NATS so ZMQ TLS is downstream of "ZMQ becomes production transport again"),
AD-479g (Federation cluster health monitor — `FederationClusterMonitor` polls `bridge._router._peer_models` every `gossip_interval_seconds * 3` and flips peers whose `last_gossip_at` is older than `peer_unreachable_seconds: float = 60.0` to `unreachable=True`; `select_peers` filters out unreachable peers ahead of the capability filter; new EventTypes `FEDERATION_PEER_UNREACHABLE` + `FEDERATION_PEER_RECOVERED` emit on transition; auto-recovery is just gossip arriving again, no `auto-restart` shell-out — the roadmap.md:7027 line-3207 "auto-restart" semantic is satisfied by NATS transport reconnection at the AD-637e layer plus this gossip-driven liveness flag),
AD-479h (Multicast peer discovery — opt-in `FederationDiscoveryConfig` with `multicast_enabled: bool = False`, `multicast_group: str = "239.255.42.99"`, `multicast_port: int = 5556`, `announce_interval_seconds: float = 5.0`; new `MulticastDiscovery` class binds a UDP socket, periodically announces `{node_id, bind_address}`, listens for peer announcements, and calls a new public `FederationBridge.add_peer(peer_config)` method to register discovered peers at runtime; default-False per the AD-695 + W82 + W88 default-False precedent; raw UDP multicast works on the local broadcast domain and is OS-portable, cross-LAN mDNS via `zeroconf` is deferred to AD-479j with explicit forcing function),
AD-479i (`/federation routing` slash subcommand — `cmd_federation` at `src/probos/experience/commands/commands_status.py:100` extended to accept `routing` argument, listing each ZeroMQ peer with capability filter pass/fail, trust score, top-3 Hebbian intent weights, and unreachable status; existing `/federation` (no arg) and `/federation peers` (AD-480i) subcommands preserved).

**Future sub-AD letters with explicit forcing functions (four):**
AD-479j (mDNS cross-LAN peer discovery — depends on optional `zeroconf` package install + cross-LAN multi-instance demo wave; v1 ships raw UDP multicast which only crosses the local broadcast domain; forcing function: cross-LAN multi-instance demo wave),
AD-479k (Cross-protocol Hebbian routing for MCP / A2A peers — depends on AD-480l revival, which itself parked at the W89 archive depending on AD-479c shipping; W91 ships the substrate at AD-479c so AD-480l can revive in a coordinated AD-480/AD-479 follow-up; forcing function: AD-480l unblock now that AD-479c has shipped),
AD-479l (Federation TLS for the ZeroMQ DEALER / ROUTER fallback transport via CURVE encryption + cert hot-rotation — depends on ZeroMQ becoming the production transport again after AD-637e moved default traffic to NATS; v1 ships the `FederationTLSConfig` surface and the NATS pass-through but parks ZMQ CURVE configuration; forcing function: production multi-instance ZMQ deployment becomes a requirement again),
AD-479m (Distributed ChromaDB sharding for federated recall at scale — depends on >100-ship deployments where the v1 round-trip-full-Episode-summaries-per-peer approach hits NATS payload limits; forcing function: scale-out wave once federation has >100 ships).

**Commercial-repo carve-outs (NOT v1 deferrals — out-of-repo by roadmap design):** hosted multi-tenant cross-fleet trust-scoring service (per `roadmap.md:4111` cross-fleet line), fleet management dashboard (per `roadmap.md:4095` fleet dashboard line), fleet-wide compliance + the recurring-revenue acronym surface + the patterns-to-absorb phrase + the GTM-pattern phrase + outcome-style pricing phrase tied to centralized agent marketplace (per `roadmap.md:3478` + `:3595` + `:4111`). Tracked in the private commercial-repo path token. The audit text in this dispatch + the per-AD prompt does NOT contain literal forms of any banned pattern — descriptor-only language is used per the W87/W89/W90 placeholder convention.

## AD numbering

Highest stem at HEAD remains **AD-696** (Wave 72 — verified by `Select-String` over `PROGRESS.md` + `DECISIONS.md` + `roadmap.md` returning 696 / 695 / 694). AD-479 is pre-allocated by `docs/development/roadmap.md:3202`–`:3207` + `:7027`. Sub-AD letters a–m are organizational catalog markers only, mirroring the AD-480 a–m (Wave 89), AD-481 a–m (Wave 88), AD-443 a–h (Wave 87), AD-474 a–h (Wave 86) precedents — no new AD numbers minted.

## Verify-first against HEAD `4f18c7d`

The substrate AD-479 will extend is fully shipped and live — every component AD-479 v1 needs already exists at HEAD, so Wave 91 is "ship the routing-and-recall surface above the existing transports", not "ship the transports themselves":

- **`FederationBridge.forward_intent(intent)` already exists at `src/probos/federation/bridge.py:83`** — wired into `IntentBus._federation_fn` at `src/probos/mesh/intent.py:89` + `:739` via `set_federation_handler()`. AD-479d `recall_federated` reuses this verbatim — the new IntentDescriptor flows through the same fan-out path.
- **`FederationRouter.peer_has_capability(peer_node_id, intent_name) -> bool` already exists at `src/probos/federation/router.py:36`** — the capability filter for AD-479a is "use the existing predicate". The Phase-9 placeholder `select_peers` at `:32` returning all available_peers is the line that flips.
- **`TrustNetwork.create_with_prior(agent_id, alpha, beta)` + `TrustNetwork.get_score(agent_id) -> float` + `TrustNetwork.record_outcome(agent_id, success, weight, intent_type, source)` already exist at `src/probos/consensus/trust.py:195` + `:397` + `:208`** — AD-479b reuses the existing trust schema verbatim with a `federation_peer:{node_id}` record-id namespace; v1 does NOT introduce a new trust schema.
- **`FederationPeerRegistry` with `record_outcome(peer_id, success, intent_type)` already exists at `src/probos/federation/peer.py:39`** — wired into `runtime.federation_peer_registry` at `runtime.py:537` with probationary trust prior `Beta(probationary_alpha, probationary_beta)` per AD-480g. AD-479b extends the `record_outcome` call site at `bridge.py:118`–`:140` (inside `forward_intent`) to also flip the registry trust per-peer per-result; the registry was wired by W89 but the ZeroMQ path was the AD-480f-deferred "register on first gossip" line that 479b now wires via the new `_record_zmq_peer_outcome(peer_node_id, success, intent_type)` helper.
- **`HebbianRouter` connection_factory pattern already exists at `src/probos/mesh/routing.py:55`–`:75`** — `FederationHebbianMap` for AD-479c reuses `connection_factory: ConnectionFactory | None = None` + `default_factory` from `probos.storage.sqlite_factory` so federation Hebbian weights persist on the same SQLite file as agent Hebbian.
- **`EpisodicMemory.recall(query, k)` already exists at `src/probos/cognitive/episodic.py:1508`** — the AD-479d federated recall handler aggregates each peer's `recall(query, k)` output and dedupes by `episode_id`. No new ChromaDB query method is required at the cognitive layer — the federation layer aggregates locally-shaped `Episode` records from peer responses.
- **AD-443e mobility chain transfer wire types already exist at `src/probos/federation/bridge.py:232`–`:325`** — `chain_request` / `chain_response` / `transfer_request` / `transfer_response`. AD-479e extends the existing `transfer_request` payload with an optional `designed_agent_payload` field that carries the agent's `instructions` string + designed-template metadata; the existing `import_chain` + `import_transfer_certificate` pipeline runs unchanged, with the new payload validated by `CodeValidator` before `AgentDesigner.register_designed_template_from_payload(...)` reconstructs the agent template.
- **NATS federation transport AD-637e is COMPLETE at HEAD** — `NATSFederationTransport` at `src/probos/federation/nats_transport.py:30` is the production transport (ZMQ is fallback per `src/probos/startup/fleet_organization.py:148`–`:208`). AD-479f TLS surface flows through NATS via the existing `nats_bus.config.tls` injection; the ZeroMQ CURVE pathway is the AD-479l forcing-function deferral.
- **`runtime._build_self_model()` at `src/probos/runtime.py:3093` already populates `NodeSelfModel.capabilities` from registered intent_descriptors** — the AD-479a capability filter consumes this directly, no new gossip payload field is required for v1.
- **`/federation peers` subcommand wiring at `src/probos/experience/commands/commands_status.py:100`–`:124` (AD-480i)** — AD-479i extends the same `cmd_federation` dispatcher with a `routing` arg. Existing `/federation` (no-arg federation panel) + `/federation peers` (AD-480i) preserved verbatim.
- **No `FEDERATION_*` EventType exists at HEAD** — `Select-String -Path src/probos/events.py -Pattern '^\s+([A-Z_]+)\s*='` returns no `FEDERATION_*` matches; AD-479g + AD-479h EventTypes (`FEDERATION_PEER_UNREACHABLE`, `FEDERATION_PEER_RECOVERED`, `FEDERATION_PEER_DISCOVERED`, `FEDERATION_DESIGNED_AGENT_RECEIVED`) are collision-free.

## Reframe decision — no reframe

**Nine concrete sub-AD letters built + four future-AD letters with explicit forcing functions + three commercial-repo carve-outs (NOT deferrals — wrong-repo by roadmap design at lines 3478 + 3595 + 4095 + 4111) + zero hard-deferrals.** This is the strictest application of "don't defer unless no choice" available for AD-479 — every `roadmap.md:7027` component that does not depend on un-shipped substrate ships in v1, with consumer integrations parked behind explicit forcing functions:

1. **Dynamic Peer Discovery (#1)** ships in v1 as AD-479h opt-in raw UDP multicast (default-False — preserves static-peer-only default). Cross-LAN mDNS via `zeroconf` is the genuinely-upstream-blocked piece — `zeroconf` is an optional package and cross-LAN demo requires multi-LAN test infra. Parked as AD-479j.
2. **Cross-Node Episodic Memory (#2)** ships in v1 as AD-479d `recall_federated` IntentDescriptor + handler. Distributed ChromaDB sharding for >100-ship deployments is the genuinely-upstream-blocked piece — depends on scale-out demand that does not exist at HEAD. Parked as AD-479m.
3. **Cross-Node Agent Sharing (#3)** ships in v1 as AD-479e — extends AD-443e mobility chain transfer with the `designed_agent_payload` field + `CodeValidator` gate on receive + designed-template reconstruction. AD-443 already covers the sovereign-identity / chain / cert path for any agent including designed agents; AD-479e is the thin-wrapper that hands the foreign agent's `instructions` string + designed metadata across the chain so the destination ship can rehydrate the designed template (rather than just record the chain block).
4. **Smart Capability Routing (#4)** ships in v1 as AD-479a + AD-479b + AD-479c — capability filter, trust-weighted ranking, Hebbian tie-break. Cross-protocol (MCP / A2A) Hebbian routing via AD-480l is genuinely-upstream-blocked because AD-480l itself parked at the W89 archive line 19 depending on AD-479c shipping; W91 ships the substrate so AD-480l can revive coordinated.
5. **Federation TLS / Authentication (#5)** ships in v1 as AD-479f — `FederationTLSConfig` model + NATS pass-through. ZeroMQ CURVE encryption is genuinely-upstream-blocked because AD-637e moved default federation traffic to NATS, which means ZMQ TLS is downstream of "ZMQ becomes production transport again"; v1 surfaces the config and wires the NATS path that handles 100% of NATS-based deployments. Parked as AD-479l.
6. **Cluster Management (#6)** ships in v1 as AD-479g — gossip-driven liveness flag + 2 EventTypes + filter peers from routing on unreachable. Process-level auto-restart and graceful handoff of responsibilities (the `roadmap.md:7027`-line-3207 "auto-restart" + "graceful handoff" semantics) are satisfied at the AD-637e + AD-637c layer (NATS reconnection + JetStream durable consumers replay un-ack'd messages on disconnect); v1 does NOT add a process supervisor — that surface belongs in deployment tooling, not federation runtime.

The only honest hard-deferrals (AD-479j / AD-479k / AD-479l / AD-479m) all have crisp upstream blockers documented above. Captain rule "don't defer unless no choice" satisfied — the unblocked-substrate carve-out is empty.

## Files

- `prompts/WAVE-91-DISPATCH.md` (this file)
- `prompts/ad-479-federation-hardening-v1.md` (the per-AD prompt — nine implementation sections + tests + tracker updates)
- `prompts/wave-plan.yaml` (W91 entry appended)

## Wave-91 baseline + targets

- **HEAD:** `4f18c7d` (BF: Ship's Computer uses live callsign map). Captain reference HEAD `86a9fe5` (origin/main) is upstream of `4f18c7d` by one BF commit; the HXI BF does not touch federation files so the two HEADs are equivalent for W91 verify-first purposes.
- **Baseline pytest:** 11897.
- **Target pytest:** ≥ 11960 (+63 floor; ~71 tests planned across nine classes — TestCapabilityAwareSelectPeers ~6, TestTrustWeightedRanking ~8, TestFederationHebbianRouting ~8, TestRecallFederated ~10, TestShareDesignedAgent ~8, TestFederationTLSConfig ~5, TestClusterHealthMonitor ~8, TestMulticastDiscovery ~8, TestSlashFederationRoutingCommand ~6 — plus ~4 startup/runtime wiring tests).
- **Issue closed:** `#73 — AD-479: Federation Hardening` (single issue; no children).

## Banned-pattern audit on this dispatch + the per-AD prompt + this audit prose itself

11 patterns checked, descriptor-only language used throughout: "the e-word + tier phrase", "the private commercial-repo path token", "the e-word overlay phrase", "the e-word-prefixed repo token", "monthly-price regex", "per-month abbreviation regex", "rev-proj phrase", "the recurring-revenue acronym", "outcome-style pricing phrase", "the GTM-pattern phrase", "the patterns-to-absorb phrase". The audit text itself does NOT contain literal forms of any banned pattern. Pre-commit-hook simulation `Select-String -Path prompts/WAVE-91-DISPATCH.md, prompts/ad-479-federation-hardening-v1.md -Pattern <pattern> -SimpleMatch` returns zero hits per pattern (run as the per-pass review action below).

## Captain rule alignment

- **Don't defer unless no choice:** nine concrete sub-AD letters ship in v1; four future sub-AD letters parked with crisp upstream blockers; three commercial carve-outs are out-of-repo by roadmap design (NOT v1 deferrals). Reframe decision: no reframe — every roadmap.md:7027 component above un-shipped substrate ships.
- **Verify-first:** every concrete claim above has an explicit grep-evidence line in the per-AD prompt's `## Verified Against Codebase` footer.
- **`.github/copilot-instructions.md` compliance:** AD-479e CodeValidator gate on incoming designed-agent code preserves the warm-boot rule. AD-479c federation Hebbian respects the layer-discipline rule (federation imports from mesh.routing; mesh.routing does not import from federation). AD-479d `recall_federated` is a new IntentDescriptor with `requires_consensus=False` (read-only) per the destructive-intent-consensus rule. New EventTypes follow the AD-527 typed-events pattern. New Pydantic config models follow the AD-432 default-factory rule.
- **Close #73 cleanly:** issue closed at end of W91 with the canonical paragraph in Section 9 of the per-AD prompt; no children minted.
- **No commercial leak:** descriptor-only audit, banned-pattern scan returns zero hits.

## Build groups

W91 is a single-prompt wave — `ad-479-federation-hardening-v1.md` is the only build prompt. Builder cycle ships nine sections in order (479a → 479b → 479c → 479d → 479e → 479f → 479g → 479h → 479i) with a focused per-section pytest gate after each section, then a full parallel gate at the end before commit.

## Hard-stop conditions

Standard W87 / W89 set, plus W91-specific:
- **W91-1:** `CodeValidator` rejects every reasonable test designed-agent payload in AD-479e — Builder must NOT loosen the validator gate; instead surface as architectural decision back to Architect.
- **W91-2:** UDP multicast bind fails on the test runner's network stack (common on CI Linux containers without IPv4 multicast support) — fall back to skipping the AD-479h networking tests with `pytest.mark.skipif(not _multicast_available())` rather than disabling the feature.
- **W91-3:** `FederationHebbianMap` SQLite migration collides with the AD-271 / AD-274 `HebbianRouter` schema — Builder must use a separate table name (`federation_hebbian_weights`) and NOT alter the existing `hebbian_weights` table.
