diff --git a/.ciagent/CHECKPOINT.json b/.ciagent/CHECKPOINT.json index 3b46ad6..ecfbe4c 100644 --- a/.ciagent/CHECKPOINT.json +++ b/.ciagent/CHECKPOINT.json @@ -1,15 +1,11 @@ { - "phase": 4, - "stage": "complete", - "milestone": "v0.4", - "milestone_type": "nfr", - "tag_base": "v0.3.x", - "phase_role": "final", + "phase": 0, + "stage": "mvp_ux_check", + "milestone": "v0.5", + "milestone_type": "feature", + "tag_base": "v0.4.x", + "phase_role": "pre_execution", "project": "oy", "attempts": 0, - "updated_at": "2026-08-17T23:50:00Z", - "milestone_complete": true, - "milestone_release_tag": "v0.3.4", - "release_id": 752, - "requirements_covered": ["REQ-029", "REQ-030", "REQ-031", "REQ-032"] + "updated_at": "2026-08-18T00:40:00Z" } \ No newline at end of file diff --git a/.ciagent/config.json b/.ciagent/config.json index 50e4ba0..4c406e6 100644 --- a/.ciagent/config.json +++ b/.ciagent/config.json @@ -6,9 +6,9 @@ } ], "active_project": "oy", - "milestone": "v0.4", - "milestone_type": "nfr", - "tag_base": "v0.3.x", + "milestone": "v0.5", + "milestone_type": "feature", + "tag_base": "v0.4.x", "autonomy": { "level": "full", "escalation_hooks": ["deploy", "delete_data", "merge_to_main"], diff --git a/.ciagent/oy/ARCHITECTURE.md b/.ciagent/oy/ARCHITECTURE.md index c7a99e1..09636c1 100644 --- a/.ciagent/oy/ARCHITECTURE.md +++ b/.ciagent/oy/ARCHITECTURE.md @@ -259,4 +259,256 @@ This section documents the lifecycle type shape-divergences flagged by AUDIT.md - **Spec source**: P4-02-01 said "DefaultParams/GenesisState unchanged" (bearers is an EXTENSION in v0.2, not a new module; the A-212 `ValidateGenesis` upgrade was scoped to NEW modules only). - **Implemented**: `ValidateGenesis` remains a no-op (`x/bearers/types/types.go:108` returns `nil` unconditionally). -- **Decision (v0.4)**: CORRECT per spec — no action (AUDIT explicitly notes "no action"). The A-212 upgrade applies to NEW modules (v0.2's `x/window`, `x/stand`, etc.), not to EXTENDED modules like `x/bearers`. Listed here for completeness; no code change, no test change. \ No newline at end of file +- **Decision (v0.4)**: CORRECT per spec — no action (AUDIT explicitly notes "no action"). The A-212 upgrade applies to NEW modules (v0.2's `x/window`, `x/stand`, etc.), not to EXTENDED modules like `x/bearers`. Listed here for completeness; no code change, no test change. + +--- + +## v0.5 Runtime Architecture (Bearers Runtime) + +This section appends the v0.5 runtime architecture to the v0.1/v0.2/v0.3/v0.4 +content above. It does NOT rewrite or supersede earlier sections. v0.5 is the +first **feature** milestone to ship executable behavior: the v0.3 Bearers +skeletons are promoted from types + in-memory keeper stubs + invariant tests +to **live keeper MsgServer message handlers + simtest-grade end-to-end flows** +(D-054). This is NOT mainnet — D-020 continues to govern network deployment; +runtime = simtest-grade handlers, not live chain. Tags run on the `v0.4.x` +patch line (config.json `tag_base`). + +### v0.5 Skeleton→Runtime Promotion Pattern + +The promotion is uniform across all 8 target modules. The v0.3 skeleton +baseline (verified against the current tree): each module has only a `types/` +subdir with `types.go` (pure-Go structs + locked consts + enums), +`genesis.go` (`ValidateGenesis`), and `*_test.go` (invariant + lexicon +tests). The in-memory `Keeper` stub lives INSIDE `types/types.go` (e.g., +`x/partner/types/types.go:101 type Keeper struct{...}`, `NewKeeper()` returns +`&Keeper{partners: make(map[string]Partner)}`). There is NO `keeper/` subdir, +NO `msg_server.go`, NO `types.Msg*`, NO `sdk.Context`, and NO cosmos-sdk +import anywhere in `x/` (grep for `cosmos-sdk` / `sdk.Context` / +`cosmos/cosmos` returns zero matches — verified at v0.5 P0). + +v0.5 promotes each module per the Cosmos-SDK `MsgServer` convention: + +| Layer | v0.3 skeleton | v0.5 runtime addition | +|---|---|---| +| Keeper | in-memory `map[string]T` in `types/types.go` | `keeper/keeper.go` (store-backed, wraps `sdk.KVStore`); the v0.3 stub is retired or wrapped as a test helper | +| Messages | none | `types/msg_*.go` with `Msg*` structs implementing `sdk.Msg` (`ValidateBasic`, `GetSigners`) | +| Handlers | none | `keeper/msg_server.go` with `MsgServer` + one `*Response, error` method per `Msg*` | +| Module wiring | none | `module.go` (`AppModule` with `RegisterServices` registering the `MsgServer`); simtest may use a lighter `ModuleManager` shim | +| End-to-end test | invariant tests only | `simtest/` (or `keeper/msg_server_simtest_test.go`) exercising each handler against an in-memory `sdk.Context` | +| Cross-module deps | by-ID-string only (G-003) | by-ID-string preserved at the type level; keeper-to-keeper calls via `expected_keepers.go` interface shims (ibc-go convention) | + +The existing `types/` locked consts, enums, and structs are NOT amended — +the runtime layer adds behavior on top, not changes to the contract. The +locked-const firewall (8%/0% bond cap, 6 bearers, 4 Partner tiers, Mission +Lock non-amendable, etc.) stays green. + +### v0.5 Per-Module Runtime Surface + +| Module | REQ | Phase | Runtime surface (MsgServer handlers) | Key types added/extended | +|---|---|---|---|---| +| `x/exit` | REQ-033 | P1 | `MsgSubmitExitRoute`, `MsgExecuteDEXSwap`, `MsgRefundExit` driving the v0.3 `ExitStatus` lifecycle (Proposed→InProgress→Settled/Failed/Refunded) | `Msg*` types; cross-chain exit invokes `x/bridge` via `BridgeKeeper` expected-keeper shim | +| `x/bridge` | REQ-033 | P1 | `MsgAttestBridgeRoute` (Pending→Attested via Watcher quorum), `MsgActivateBridge`, `MsgCloseBridge`, `OnRecvPacket`, `OnAcknowledgementPacket`, `OnTimeoutPacket` (ibc-go `IBCModule` contract) | `Msg*` types; ICS-20 v1 payload parser; Solana via wormhole-adapter verification branch (D-059) | +| `x/bearers` | REQ-034 | P2 | `MsgSendOYSATFrame`, `MsgReceiveOYSATFrame`, `MsgIssueOYQR`, `MsgConsumeOYQR` (one-shot) + session lifecycle (Open/Active/Closed/Revoked) | `Session` struct; store-backed `BearerTransport` impl (keeper as transport in simtest); `consumed` flag is the OY-QR replay firewall | +| `x/partner` | REQ-035 | P3 | `MsgIssueAnchorCredential`, `MsgOnboardAnchor` (Pending→Onboarded), `MsgSuspendAnchorCredential`, `MsgRevokeAnchorCredential` (Watcher-quorum authz) | `Msg*` types; `expected_keepers.go` shims for `x/watcher` (revocation authz) and `x/hub` (custody-provider-id validity, P4-wired) | +| `x/hub` | REQ-036 | P4 | `MsgRegisterCustodyService` (operator must be Onboarded Anchor), `MsgCustodyReceiveAsset`, `MsgCustodyReleaseAsset` (compliance-before-debit), `MsgRecordLendingPrimitive` (coupon clamp [0,800]), `MsgRecordComplianceAttestation` | `CustodyKeyring` interface + `memKeyring` in-memory test impl (D-058); `Msg*` names avoid banned "deposit" (lexicon) | +| `x/services` | REQ-037 | P5 | `MsgRegisterService` (window-grant check), `MsgActivateService`, `MsgSuspendService`, `MsgRevokeService`; per-kind: `MsgIssueCareGrant`, `MsgActivateSIM`, `MsgProvisionVault`, `MsgBindMailbox` | Per-kind `Msg*` (typed dispatch, not generic); `window-id` grant checked on EVERY op (revoked Window invalidates) | +| `x/bond` | REQ-038 | P6 | `MsgIssueBond`, `MsgIssueGrowthBond` (`Clamp` + `ClampGrowth`), `MsgTickGrowthBond`, `MsgPlaceSecondaryOrder`, `MsgCancelSecondaryOrder`, `MsgMatchSecondaryOrder` (CLOB, price-time priority, per-match clamp) | CLOB matching engine; per-match coupon clamp to [0, 800] bps via v0.3 `Clamp` (D-057/D-028); match above 800 REJECTED (fails closed, A-562) | +| `x/council` | REQ-039 | P7 | `MsgSubmitProposal` (MissionLockAmendment kind rejected at `ValidateBasic`), `MsgVote` (Veto is Watcher-only, quorum-based), `MsgTallyProposal` | `Proposal` struct + `ProposalKind` enum (4, incl. rejected MissionLockAmendment) + `ProposalStatus` enum (5) + `VoteOption` enum (4) — AUDIT §193 P1-1 promotion; `SignalKind` stays 4 (P1-2 defensible); Mission Lock const firewall intact | + +### v0.5 Custody Keyring Interface Boundary (D-058) + +`x/hub/types/keyring.go` defines the `CustodyKeyring` Go interface — the +custody key-share abstraction (MPC-via-interface, not a concrete HSM/MPC +vendor): + +``` +type CustodyKeyring interface { + Sign(ctx context.Context, assetID string, payload []byte) (sig []byte, err error) + Derive(ctx context.Context, assetID string) (pub PubKey, err error) + Status(ctx context.Context, assetID string) (KeyringStatus, error) +} +``` + +- v0.5 ships an in-memory test-only `memKeyring` impl (`x/hub/keeper/ + keyring_mem.go` or `x/hub/types/keyring_mem_test.go`) that signs with a + throwaway ed25519 key. Real MPC/HSM backing is deferred (operational, + Year 3+). +- The interface supports key rotation: `Status` reports the active key + version; the handler consults the keyring per operation (no caching + across blocks — a cached pubkey breaks rotation). +- The boundary keeps v0.5 dep-neutral w.r.t. custody vendors while landing + the handler surface. GRILL reviews the interface boundary. + +### v0.5 CLOB Matching Engine Invariants (D-057) + +`x/bond` secondary-market matching is a **central-limit order book (CLOB)** +(not an AMM — D-057 rejects AMM as a Year-4 concern). The invariants: + +1. **Price-time priority** — at the same price, the earlier resting order + fills first (by sequence). This is REQ-007 FCFS at the same price. +2. **Per-tx matching** — the handler matches a new order against the resting + book in the same tx (dYdX-v4-shaped); no asynchronous / end-of-block + batch matching in v0.5 simtest. +3. **Per-match coupon clamp** — every match's resulting coupon is clamped to + `[CouponFloorBps=0, CouponCapBps=800]` (D-028, locked since v0.2) via the + v0.3 `Clamp` helper. A match whose implied coupon exceeds 800 bps is + **REJECTED** (fails closed — A-562, the mission-lock-true choice; D-057 + says "clamp", the runtime interpretation is reject-above-cap. Planner + confirms before P6). +4. **Mission-lock const firewall** — the handler references the consts + directly (not a local copy); the REQ-030 cross-const test (hub lending + consts == bond consts) stays green. +5. **No front-running safety claim** — per-tx matching in a single- + validator simtest has no MEV; the handler is documented as NOT + front-running-safe for mainnet (a Year-3+ concern). Simtest does not + assert front-running safety (out of scope for simtest-grade runtime, + D-054). + +### v0.5 IBC Packet Handler Scope (D-059) + +`x/bridge` IBC packet handlers cover the **5 locked L2 chains** already in +the v0.2 `x/satellite` skeleton (Polygon, Base, Arbitrum, Optimism, Solana +per REQ-009). No new L2 chains in v0.5. The handler shape: + +- **4 EVM chains (Polygon/Base/Arbitrum/Optimism):** standard IBC + recv/ack/timeout on the ICS-20 v1 payload (the v0.2 satellite packet + shape). Timestamp-only timeouts (IBC Eureka model, ibc-go v10) avoid the + EVM height-timeout ambiguity. +- **Solana:** via the wormhole-style bridge adapter (D-021 stub promoted + to runtime). Solana packets arrive as wormhole VAAs (Verified Action + Approvals); the `x/bridge` handler verifies the guardian signature set + (a 2-of-N quorum, N = the wormhole guardian set) before transitioning the + route. The guardian set is read from state (not hardcoded); simtest uses + a frozen stub guardian set. Live wormhole integration deferred (D-054). +- **Replay protection:** mirrors ibc-go — delete the in-flight record on + first ack; reject on second; `OnTimeoutPacket` refunds the source-chain + escrow exactly once. Simtest covers both replay and timeout-refund cases + (the CVE-class ibc-go pitfall). + +### v0.5 Council Governance Enum Additions (D-060) + +`x/council/types` gains the AUDIT §193 P1-1 enums deferred from v0.4 +(D-050/D-001 rejected them as `feat:` for the NFR milestone; v0.5 promotes +them as the feature milestone's P7): + +| New type | Values | Locked count | Notes | +|---|---|---|---| +| `Proposal` struct | (id, council-id, kind, proposer-reach, submit-time, voting-deadline, status, tally) | — | Mirrors OZ Governor / `x/gov` proposal shape | +| `ProposalKind` enum | `Stand`, `Guild`, `Mesh`, `MissionLockAmendment-Rejected` | `ProposalKindCount = 4` | The 4th value exists but the handler rejects it — documents the non-amendability in code | +| `ProposalStatus` enum | `Pending`, `Active`, `Succeeded`, `Failed`, `Executed` | `ProposalStatusCount = 5` | Mirrors OZ Governor / `x/gov` lifecycle | +| `VoteOption` enum | `Yes`, `No`, `Abstain`, `Veto` | `VoteOptionCount = 4` | `Veto` is Watcher-only; quorum-based (default `WatcherVetoQuorum = 6` per REQ-004 6-of-9); a single Veto does NOT block (anti-greed, vision §19) | + +**Mission Lock const firewall intact (G-003):** +- `MissionLockAmendable = false` (v0.2 locked const) is UNCHANGED. The + `MissionLockAmendment-Rejected` `ProposalKind` is the in-code + documentation of the non-amendability; the `MsgSubmitProposal` + `ValidateBasic` REJECTS a proposal of that kind (the message never + reaches the handler — A-572). The const is the firewall; the + `ValidateBasic` is the gate. The v0.2 `TestMissionLockAmendableFalse` + regression test stays green. +- `SignalKind` stays at 4 sources (P1-2 defensible per AUDIT; the v0.4 + `TestSignalKindShapeIntentional` regression-guard test stays green). + Expansion to 5 is a locked-const change deferred to v0.6+ governance + vote (not a Mission-Lock const — a distinct locked const; the distinction + is documented in v0.4 ARCHITECTURE.md). +- Proposal execution (auto-executing a passed proposal) is NOT in v0.5; + the handler records the tally result but does not auto-execute (a v0.6+ + concern). + +### v0.5 G-003 Production Firewall (still intact) + +The G-003 by-ID-string rule (no production cross-`x//types` struct +imports) survives the runtime promotion. The runtime adds a NEW cross- +module surface — keeper-to-keeper calls — handled via the ibc-go +`expected_keepers.go` convention: + +- Each module's `types/expected_keepers.go` defines Go INTERFACES for the + keepers it depends on (e.g., `x/exit/types/expected_keepers.go` defines a + `BridgeKeeper` interface with the methods `x/exit`'s handler calls; the + `x/bridge` keeper satisfies it structurally). +- The handler depends on the INTERFACE, not the concrete keeper struct. + This is NOT a struct import of `x/bridge/types`; it is an interface + defined in `x/exit/types`. G-003's intent (no cross-module struct + coupling, no import cycles) is preserved. +- Test-only cross-package imports (the G-003 test exemption, used by + REQ-030 in v0.4) remain exempt: a simtest may import both + `x/exit/keeper` and `x/bridge/keeper` to wire the expected-keeper shims + in a test setup. + +### v0.5 G-006 Controlled Exception (cosmos-sdk dep, D-055) + +`go.mod` gains `github.com/cosmos/cosmos-sdk` (+ transitive deps) as the +runtime substrate. This is a GRILL-approved controlled exception to G-006 +(zero-dep go.mod), scoped to the runtime promotion phases: + +- **Runtime phases (P1..P7):** `keeper/`, `msg_server.go`, `module.go`, + `simtest/` import cosmos-sdk. The dep is load-bearing. +- **P0 (pre-execution) + P8 (final):** stay dep-neutral where possible + (RESEARCH.md, PERSONAS.md, PLAN — no Go code). +- **`types/` packages:** the v0.3 `types/` packages were pure stdlib + (`encoding/json`); v0.5 ADDS `types.Msg*` structs implementing `sdk.Msg`, + so the `types/` package gains a cosmos-sdk import. The invariant tests + (locked-const, lexicon) stay stdlib-only and green. The `Msg*` types are + isolated in `types/msg_*.go` files for clarity. +- **Version pin (A-504, planner/GRILL confirms):** cosmos-sdk v0.50.x + (LTS, go 1.22-compatible) + ibc-go v8.x (for cosmos-sdk v0.50) for the + IBC packet handler interfaces. ibc-go v10 (IBC v2 / Eureka) is the + documented target pattern but a newer pin; v8.x is the stable choice. + The exception is GRILL-ratified per D-055. + +### v0.5 Cross-Component Dependencies (within v0.5, by-ID-string + expected-keeper shims) + +Per the v0.2-v0.4 G-003 invariant, v0.5 components reference each other and +the baseline by ID-string at the type level; the runtime adds interface- +typed keeper dependencies via `expected_keepers.go` shims. The dependency +edges that affect D-056 phase ordering: + +``` +x/exit ──(BridgeKeeper interface)──► x/bridge (P1 intra-phase; bridge keeper satisfies x/exit/types expected keeper) +x/bridge ──(WatcherKeeper interface)──► x/watcher (P1; Attested transition + Solana adapter authz) +x/bridge ──(BreadKeeper interface)───► x/bread (P1; mint/release wrapped Bread on recv/timeout) +x/bearers ──(BreadKeeper interface)───► x/bread (P2; OY-QR consume transfer effect) +x/partner ──(WatcherKeeper interface)──► x/watcher (P3; revocation authz) +x/partner ──(HubKeeper interface)─────► x/hub (P3→P4; custody-provider-id validity; shim exists P3, impl wired P4) +x/hub ──(PartnerKeeper interface)─► x/partner (P4; operator must be Onboarded Anchor) +x/hub ──(lexicon-safe consts)─────► x/bond (P4; LendingCouponCapBps/Floor local consts cross-documented D-028/REQ-030) +x/services ──(WindowKeeper interface)──► x/window (P5; window-grant validity on every op) +x/services ──(VaultKeeper interface)───► x/vault (P5; VaultService provisioning) +x/bond ──(StandKeeper interface)───► x/stand (P6; GrowthBond issuer-stand-id) +x/council ──(WatcherKeeper interface)──► x/watcher (P7; Veto authz + quorum) +``` + +**Phase-ordering implication (confirms D-056):** the outer→inner chain is +exit (P1) → bearers (P2) → anchors (P3) → hub (P4) → services (P5) → bond +(P6) → council (P7). The P3→P4 edge (partner needs hub custody-provider-id +validity) is broken by the `expected_keepers.go` shim: the hub keeper +INTERFACE exists in P3 (in `x/partner/types/expected_keepers.go`); the real +hub keeper impl is wired in P4. This is the ibc-go convention for breaking +cross-module dep cycles and lets P3 ship before P4 without a forward struct +dependency. + +### v0.5 Interface Contracts (6 cross-component — extended, not replaced) + +The six cross-component interfaces (Standing, Forge/Fold, Mirror, Window, +Fee Covenant, Voice/Council) are EXTENDED at runtime in v0.5 (they were +skeleton-only in v0.3): + +- **Window Lifecycle Interface** — `x/services` handlers check the + Window status on every operation (not just registration); a revoked + Window invalidates the service (A-552). +- **Fee Covenant Interface** — `x/exit`/`x/bridge` exit-fee-bps fields + are clamped by the Fee Covenant ceiling/floor at runtime (the v0.3 + field was typed but the Clamp was not invoked; v0.5 invokes it). +- **Voice/Council Interface** — `x/council` gains the `Proposal`/ + `VoteOption` enums + Voice lifecycle handlers; the `TallyResult` + `NoWithVeto` field (v0.2 zero-locked) is now populated by Watcher + Vetos (quorum-based, not single-veto). +- **Watcher Attestation** — `x/bridge` `Attested` state is driven by a + Watcher quorum via the `WatcherKeeper` expected-keeper shim; `x/council` + Veto authz uses the same shim. +- **Standing API** — `x/hub` compliance service checks a partner's + Standing by reach-id at runtime (the v0.3 by-ID-string field becomes a + query). +- **Forge/Fold** — unchanged in v0.5 (no forge/fold runtime promotion this + milestone). \ No newline at end of file diff --git a/.ciagent/oy/GRILL.md b/.ciagent/oy/GRILL.md index 6fb4938..2695a83 100644 --- a/.ciagent/oy/GRILL.md +++ b/.ciagent/oy/GRILL.md @@ -386,3 +386,710 @@ Overall: SHIP Phase 0 with binding changes (confidence 0.84) ### v0.4 Grill Verdict **SHIP Phase 0** with G-015 (absolute-value const assertion in P1-02-01) and G-016 (firewall-gates-docs-build in P3-01-01) applied. The v0.4 NFR milestone is a legitimate, well-scoped refinement cycle that closes three real v0.3 forward-references (G-014, A-304, AUDIT §193) and lands the deferred docs CI (D-046). The D-001 refinement-only filter is enforced throughout; the NFR purity gate in P4 is enforceable. No escalations. + +--- + +# GRILL: OpenYield (oy) — v0.5 (Bearers Runtime) + +> **Reviewer**: CIAgent adversarial grill (red-team, full autonomy) +> **Date**: 2026-08-17 +> **Target**: v0.5 Phase 0 artifacts (PROJECT.md D-054..D-061, REQUIREMENTS.md REQ-033..REQ-039, ARCHITECTURE.md v0.5 Runtime Architecture section, PERSONAS.md v0.5 roster, RESEARCH.md A-501..A-574, PLANS.md v0.5 plan — 8 phases, 36 tasks, MVP/UX check) + v0.1..v0.4 codebase baseline +> **Milestone**: v0.5 — Bearers Runtime (feature type) +> **Autonomy**: full (decision_confidence_threshold = 0.60) +> **Mode**: multi-project (slug `oy`) +> **Branch**: phase/00-pre-execution (off milestone/v0.5-bearers-runtime) +> **G-NNN sequence**: continues from G-016 (highest prior grill id). New fixes G-017.. + +## Methodology + +v0.5 is the first milestone to ship executable behavior beyond invariant tests: 8 +v0.3 skeleton modules are promoted to live keeper `MsgServer` handlers + simtest. +This is a load-bearing change of the project's character: `go.mod` gains +`cosmos-sdk` + `ibc-go` (D-055 — the first break of G-006), `types/` packages gain +`sdk.Msg` imports (the first break of the v0.1-v0.4 zero-dep `types/` property), +and keeper-to-keeper cross-module calls appear (a NEW G-003 surface). The grill +treats each of these as a forcing question, not a narrative. + +### Evidence baseline (verified against the actual repo, not the docs) + +- `go.mod`: `module github.com/oy/openyield`, `go 1.22`, **zero dependencies** + (confirmed — no require lines). D-055 will make cosmos-sdk the FIRST external + Go dep in the project's history. This is a one-way door for `go.mod` hygiene. +- `x/` module count: **29 entries** under `x/` (one is `README.md`; 28 actual + modules — verified). The 8 v0.5 targets (exit, bridge, bearers, partner, hub, + services, bond, council) all exist as `types/`-only packages from v0.3. +- **Zero cosmos-sdk imports** in `x/` today (grep `cosmos-sdk|cosmos/cosmos| + sdk.Context` → empty). v0.5 P1-01-01 introduces the first. +- **Empty `keeper/` subdirs exist for 5 v0.1 baseline modules** (`x/mirror/keeper`, + `x/forge/keeper`, `x/still/keeper`, `x/watcher/keeper`, `x/bread/keeper`) — + all EMPTY (no `.go` files). RESEARCH §1.1 says "there is NO `keeper/` subdir" + for the v0.3 targets, which is correct; but the claim "NO `keeper/` subdir + anywhere" is imprecise — v0.1 baseline has empty keeper dirs. Not blocking, but + the v0.5 "promote to runtime" pattern should NOT touch the 5 v0.1 baseline + keepers (out of scope; D-056 scopes P1..P7 to the 8 v0.3 modules). **Noted.** +- **G-003 import-invariant test** exists: `x/window/types/types_test.go` scans + non-test `x/**/*.go` via `go/parser`. **Confirmed: zero cross-module struct + imports in production x/ today** (`grep -rn "openyield/x/" x/ --include="*.go" + | grep -v "_test.go"` → empty). The v0.5 `expected_keepers.go` shims must keep + this green (interfaces, not struct imports). +- **Locked consts verified present and unchanged**: + - `x/bond/types/types.go:21,26`: `CouponCapBps = 800`, `CouponFloorBps = 0` + (D-028). + - `x/bond/types/types.go:239`: `ClampGrowth` has the **G-012 guard** + (`if currentBps >= CouponCapBps { return 0 }`) — the v0.3 grill fix landed. + - `x/council/types/types.go:25`: `MissionLockAmendable = false`. + - `x/council/types/types.go:30`: `SignalKindCount = 4`. + - `x/partner/types/types.go:18`: `PartnerTierCount = 4`. + - `x/hub/types/types.go:42,51,55`: `HubServiceCount = 3`, + `LendingCouponCapBps = uint32(800)`, `LendingCouponFloorBps = uint32(0)`. +- **Proposal / VoteOption / ProposalStatus / WatcherVetoQuorum ABSENT** from + `x/council/types` today (correct — P7 adds them). `TallyResult.NoWithVeto` + field EXISTS (`types.go:142`) with an explicit comment "always 0 — no veto + option (anti-greed)". +- **CRITICAL FINDING (feature purity gate)**: `x/council/types/types_test.go:233- + 239` contains `TestTallyResultNoWithVetoAlwaysZero` which asserts + `tr.NoWithVeto != 0` → error "expected 0 (no veto option — anti-greed)". v0.5 + P7 (D-060, ARCHITECTURE.md v0.5 Voice/Council Interface, RESEARCH §2.7) + **populates `NoWithVeto` with Watcher Vetos** ("the v0.2 `NoWithVeto` field, + zero-locked in v0.2, is now populated by Watcher Vetos"). This is a + **contradiction**: the v0.2 regression test says NoWithVeto is ALWAYS 0 + (anti-greed); the v0.5 plan says NoWithVeto is populated by Vetos. The plan + lists P7-03-01 as extending `types_test.go` but does NOT call out that + `TestTallyResultNoWithVetoAlwaysZero` must be REPLACED, not extended. This is a + breaking change to a locked-invariant test that the feature purity gate + ("no breaking schema changes") must reconcile. **Binding fix G-017** (below). +- **Cross-const test** (REQ-030/G-015) exists at `x/hub/types/cross_const_test.go` + with `TestConstsAreMissionLocked800And0` asserting absolute 800/0 (G-015 + landed). The v0.5 P4 lending clamp must keep this green. +- **Both lexicon firewalls** present (`lexicon_meta_test.go`, + `lexicon_meta_docs/lexicon_meta_docs_test.go`). +- **git history**: v0.4 milestone COMPLETE (`4369b3e checkpoint(milestone): v0.4 + complete`). HEAD on `phase/00-pre-execution` off `milestone/v0.5-bearers- + runtime`. P0 for v0.5 is in progress (PLAN committed at `a0145e4`). + +These baseline facts confirm the v0.5 plan's architecture claims against the +actual codebase, with one material contradiction (the NoWithVeto regression test +vs. the P7 Veto population). + +--- + +## 1. Decision Ratifications (D-055, D-062, D-063, D-064, D-065) + +### D-055 — cosmos-sdk + ibc-go dep as G-006 controlled exception + +**Verdict: RATIFY (confidence 0.82)** + +**Evidence**: `go.mod` is zero-dep today (verified). The v0.3 skeleton keepers +are in-memory `map[string]T` stubs (verified: `x/partner/types/types.go` Keeper +stub). Promoting to `MsgServer` (`sdk.Msg`, `sdk.Context`, `sdk.KVStore`, +`RegisterServices`) is impossible without cosmos-sdk — hand-rolling store + +message routing would duplicate the SDK and is the rejected alternative in +D-055's rationale. The exception is genuinely scoped: runtime phases P1..P7 +import cosmos-sdk; P0 (this PLAN) and P8 stay dep-neutral; `types/` packages +gain `sdk.Msg` imports isolated in `types/msg_*.go`. The G-006 *intent* +(durability of skeleton types) is preserved by keeping the v0.3 `types/` contract +structs/enums/consts unamended — runtime adds behavior on top (D-054). + +**One caveat**: the `types/` packages losing zero-dep status is a one-way door. +Once `x/bond/types` imports cosmos-sdk for `Msg*`, the v0.1-v0.4 property +"`types/` compiles with stdlib only" is gone. RESEARCH §3.1 acknowledges this and +isolates `Msg*` to `types/msg_*.go`. The invariant/lexicon tests in `types/` +MUST stay stdlib-only and green (they don't need `Msg*`). **Binding fix G-017 +family** (below) does not block D-055 but records the constraint: P1-01-01 must +add a CI assertion that invariant/lexicon tests pass WITHOUT the cosmos-sdk +build tag (or that they remain in stdlib-only `_test.go` files that don't import +`sdk.Msg`). Confidence holds. + +### D-062 — cosmos-sdk v0.50.x + ibc-go v8.x version pin + +**Verdict: RATIFY (confidence 0.80)** + +**Evidence**: cosmos-sdk v0.50.x is the LTS line compatible with go 1.22 +(RESEARCH §3.1, A-504). ibc-go v8.x is the stable pairing for v0.50 +(Osmosis/dYdX-v4 lineage). The alternative (ibc-go v10 / IBC v2 Eureka) is newer +and risks churn in a runtime-promotion milestone; the v0.5 impl uses the v8 +stable `OnRecvPacket`/`OnAcknowledgementPacket`/`OnTimeoutPacket` contract +(ARCHITECTURE.md v0.5 IBC Packet Handler Scope). The RESEARCH §2.1 Eureka +timestamp-only-timeout pattern is DOCUMENTED but the v8 interfaces suffice for +the 5 locked L2 chains (D-059). Pinning v8 now and upgrading to v10 in a later +milestone is lower-risk than pinning v10 in a milestone whose primary goal is +runtime promotion, not IBC-v2 migration. + +**One caveat**: cosmos-sdk v0.50.x and ibc-go v8.x have large transitive dep +trees (hundreds of modules). P1-01-01 (`go mod tidy`) will produce a `go.sum` +that is orders of magnitude larger than the zero-dep baseline. This is +expected and acceptable (D-055 ratifies), but the P1-99-01 verification must +confirm `go build ./...` succeeds under go 1.22 with the new tree (a +transitive dep requiring go 1.23+ would block). The plan's P1-01-01 verification +says "go version compatible (go 1.22+)" — **binding fix G-018** makes this a +HARD gate: if `go build ./...` fails under go 1.22, P1 does not ship (escalate +to a go version bump, which is out of scope for v0.5). Confidence holds. + +### D-063 — Bond CLOB match above 800 bps = REJECT (fails closed) + +**Verdict: RATIFY (confidence 0.78)** + +**Evidence**: D-057 says "hard clamp on each match"; the runtime interpretation +(D-063) is REJECT above cap. The rationale is sound: the 8% cap is a Mission- +Lock invariant (D-028), not a soft cap. A match above 800 bps is a usury +violation, not a clampable excess. REJECT fails closed (the resting order +stays; the incoming order rests or is cancelled) — no refund path, no +partial-clearing ambiguity. The Fee Covenant `Clamp` shape (clamp, not reject) +applies to ISSUANCE (a coupon set by the issuer), where clamping is the +mission-lock-true choice; MATCHING is market-determined, where reject is the +mission-lock-true choice. The distinction is defensible and documented in +D-063's rationale. + +The alternative (clamp-with-refund) adds a refund path — a new state +transition with its own failure modes (what if the refund fails? does the +match revert?). REJECT is simpler and safer for the highest-severity locked +const. Simtest (P6-03-01) covers the reject-above-cap case explicitly. + +**One caveat**: the "implied coupon" computation must be unambiguous. The +match's implied coupon is derived from the trade price (fraction of principal +in bps) — the plan does not specify the exact formula (price → implied coupon). +If two implementers compute it differently, the reject threshold is +inconsistent. **Binding fix G-019**: P6-02-01 (`clob.go`) MUST define a single +`ImpliedCoupon(priceBps, principal) uint32` helper (or equivalent) used by +BOTH the match and the clamp check, with a unit test covering the boundary +(price implying exactly 800, 801, 799 bps). Confidence holds after the fix. + +### D-064 — MissionLockAmendment-Rejected rejected at ValidateBasic + +**Verdict: RATIFY (confidence 0.85)** + +**Evidence**: `MissionLockAmendable = false` is a v0.2 locked const +(`x/council/types/types.go:25`, verified). `TestMissionLockAmendableConstFalse` +and `TestMissionLockAmendableCannotBeSetTrue` (types_test.go:67-88) guard the +const. D-064 rejects `MissionLockAmendment-Rejected` ProposalKind at +`MsgSubmitProposal.ValidateBasic` — the message never reaches the handler. This +is the cleanest firewall: the const is the firewall, `ValidateBasic` is the +gate, and no state record is created for an unproposable proposal. The +alternative (propose-then-fail) would create dead state (a Pending proposal that +auto-transitions to Failed) — unnecessary state growth and a misleading +on-chain record. The Mission-Lock-non-amendable design intent is +"unproposable", not "propose-then-fail" (RESEARCH §2.7). + +P7-03-01 simtest asserts `MsgSubmitProposal` with the rejected kind fails +`ValidateBasic` with a Mission-Lock error and the keeper's Proposal store is +empty. This is a concrete, testable firewall. Confidence holds. + +### D-065 — Watcher Veto quorum default = 6 + +**Verdict: RATIFY (confidence 0.78)** + +**Evidence**: REQ-004 fixes the Watcher quorum at 6-of-9. The Veto quorum +mirrors it (a Watcher-coordinated veto requires the same quorum as a Watcher +attestation). A single Veto blocking would violate the anti-greed principle +(vision §19 — no single-actor veto gate). Defaulting to 6 as a PARAM (not a +locked const) lets a future governance vote adjust without a Mission-Lock-class +amendment — defensible, since Veto quorum is NOT a Mission-Lock const (the +distinction is documented in v0.4 ARCHITECTURE.md). The `Params` struct is +currently empty (`types.go:148` `type Params struct{}`); P7-01-01 adds +`WatcherVetoQuorum` defaulting to 6. + +**Two caveats**: +1. **Param validation**: a `WatcherVetoQuorum` param of 0 (no quorum needed — + any single Veto blocks) or 10 (more than the 9 Watchers — veto impossible) + would violate the anti-greed / REQ-004 semantics. The plan's P7-01-01 does + NOT specify `Params.Validate()` bounds. **Binding fix G-020**: P7-01-01 MUST + add a `Params.Validate()` (or `ValidateBasic` on the param) asserting + `WatcherVetoQuorum` is in `[1, 9]` (or `[2, 9]` to forbid single-veto-block + even if the param is mis-set). The default 6 is correct; the validation + bounds are the missing piece. +2. **The NoWithVeto contradiction** (see G-017) is the bigger Veto concern — + the v0.2 regression test asserts NoWithVeto is ALWAYS 0. P7 populates it. + This must be reconciled regardless of the quorum value. + +Confidence holds after G-017 and G-020. + +--- + +## 2. Nine-Axis Adversarial Scorecard + +### Axis 1 — Feasibility (8 modules × MsgServer + simtest at full autonomy) — **PASS** (confidence 0.80) + +36 tasks across 8 phases for 8 module promotions is proportionate. Each module +gets a types+msg task, a keeper+msg_server task, a simtest task, and a +verification task — the same 4-task vertical-slice shape, repeated. v0.3 +shipped 50 tasks / 6 phases at full autonomy and closed clean; v0.5's 36 is +smaller. The MsgServer promotion is the standard pre-mainnet Cosmos-SDK step +(every ibc-go/Osmosis/Celestia/dYdX module follows it). The simtest grade (D-054 +— in-memory `sdk.Context` + dbm, no real IBC/MPC/hardware) is genuinely +achievable, not a half-finished mainnet. No axis-1 risk reaches escalation. + +The one feasibility risk is **cosmos-sdk v0.50.x build under go 1.22**: the +transitive tree is large and may pull a module requiring go 1.23+. G-018 +makes this a hard P1 gate. Confidence holds. + +### Axis 2 — Scope (8 modules, outer→inner chain) — **PASS** (confidence 0.78) + +D-056's phase ordering (P1 exit+bridge → P2 bearers → P3 anchors → P4 hub → P5 +services → P6 bond → P7 council → P8 final) follows the dependency graph +correctly: exit needs nothing internal; bearers route through exit; anchors +ride bearers; hub custody backs anchors; services sit on hub; bond uses hub +lending; council is cross-cutting. The P3→P4 hub dep is broken by the +`expected_keepers.go` shim (the hub keeper INTERFACE exists in P3, impl wired +in P4 — ibc-go convention, A-532). The out-of-scope list is explicit (no +mainnet, no real IBC, no real MPC, no real bearer hardware, no real +institutional onboarding — D-054). Scope is not over-scoped for a runtime- +promotion milestone. + +The one scope concern: **P7 adds 3 new enums + 1 struct to `x/council/types`** +(ProposalKind, ProposalStatus, VoteOption, Proposal). This is `feat:`-class +new-type work (correctly deferred from v0.4 by D-050/D-001). The feature +purity gate ("no breaking schema changes") must distinguish "adding new +types" (allowed) from "amending existing types/consts" (forbidden). The +`TallyResult.NoWithVeto` field is the gray area: the FIELD exists in v0.2 (zero- +locked), v0.5 POPULATES it. Is populating an existing field a "breaking schema +change"? No — the struct shape is unchanged; the SEMANTIC invariant +("NoWithVeto always 0") changes. **G-017** forces the plan to reconcile the +v0.2 regression test explicitly. Confidence holds after the fix. + +### Axis 3 — Cost / Effort (36 tasks, cosmos-sdk onboarding) — **CONDITIONAL** (confidence 0.72) + +36 tasks is proportionate to the deliverable (8 module promotions). The hidden +cost is the **cosmos-sdk onboarding** in P1-01-01: `go mod tidy` will pull a +large transitive tree, and the FIRST `go build ./...` with cosmos-sdk will +surface any go-version / module-replace / cometbft-separation issues. This is +a one-time cost concentrated in P1. If P1-01-01 takes more than 1 task's worth +of effort (likely — cosmos-sdk dep resolution is notoriously finicky), the +plan's single P1-01-01 task underestimates it. **Not blocking** (the task is +scoped correctly; the risk is effort, not feasibility), but P1 may absorb +spillover. No binding change — record the risk. + +The other cost driver is **simtest coverage ≥80% on 8 keeper packages**. v0.3 +hit ≥95.9% on type packages (trivial structs). Keeper packages with MsgServer +handlers + state machines + replay/timeout logic have higher cyclomatic +complexity; 80% is achievable but requires table-driven handler tests per +`Msg*` (the plan specifies this). Not a cost cliff. + +### Axis 4 — Architecture (expected_keepers, G-003 survival, IBC contract) — **CONDITIONAL** (confidence 0.75) + +The G-003 by-ID-string rule survives the runtime promotion via +`expected_keepers.go` interface shims (ibc-go convention) — verified: the +existing G-003 import-invariant test in `x/window/types/types_test.go` scans +non-test `x/**/*.go` and will auto-cover the new `keeper/` + `msg_*.go` + +`expected_keepers.go` files. The shims are INTERFACES defined in the consuming +module's `types/` (e.g., `x/exit/types/expected_keepers.go` defines +`BridgeKeeper`); the concrete keeper satisfies it structurally. This is NOT a +struct import — G-003 intent (no cross-module struct coupling, no import +cycles) is preserved. Correct. + +Two architecture concerns: +1. **The G-003 import-invariant test scans `x/**/*.go`** — but `expected_keepers + .go` defines an interface that REFERENCES another module's types by NAME in + comments (not imports). The test uses `go/parser` ImportsOnly, so interface + method signatures that mention `x/bridge` types by STRING (e.g., return + `(status, bridgeType, err)`) are fine; but if an implementer writes a + method returning `x/bridge.BridgeRoute` (a struct import) to satisfy the + shim, the test catches it. Good — the test is the firewall. No binding + change, but P8-01-01 (REVIEW) must explicitly probe this. +2. **IBC `OnRecvPacket` / `OnAcknowledgementPacket` / `OnTimeoutPacket` + contract**: the plan pins the ICS-20 v1 payload shape (v0.2 satellite) and + timestamp-only timeouts for EVM chains (ibc-go v8 supports this). The Solana + wormhole-adapter branch verifies a 2-of-N guardian sig set from state. The + replay protection (delete-on-ack, refund-on-timeout) mirrors ibc-go (A-513, + the CVE-class pitfall). This is the highest-risk architecture surface in + v0.5. The simtest (P1-06-01) MUST cover both replay and timeout-refund + explicitly — the plan says it does. **Binding fix G-021**: P1-06-01 simtest + MUST include a NEGATIVE assertion that a SECOND `OnAcknowledgementPacket` + with the same packet commitment is REJECTED (not silently no-op'd) — the + distinction between "no-op" and "reject" matters for relayer error handling. + Confidence holds after the fix. + +### Axis 5 — Risk (top 3 assumptions, pre-mortem) — **CONDITIONAL** (confidence 0.72) + +**Top 3 assumptions the plan rests on:** +1. **A-504** (cosmos-sdk v0.50.x builds under go 1.22) — confidence 0.78. The + dep tree is large; a single transitive module requiring go 1.23+ blocks P1. + G-018 makes this a hard gate. Evidence: RESEARCH §3.1 confirms go 1.22 + compatibility for v0.50.x, but does not enumerate the transitive tree. +2. **A-513** (IBC ack/timeout replay protection mirrors ibc-go) — confidence + 0.90. This is the highest-severity runtime invariant (CVE-class pitfall). + The simtest covers it; G-021 strengthens the assertion. Evidence: RESEARCH + §2.1, P1-06-01. +3. **A-521** (OY-QR one-shot: `consumed` flipped BEFORE transfer effect) — + confidence 0.88. The atomicity argument (SDK store is atomic per tx; a panic + rolls back the whole tx) is sound. Evidence: RESEARCH §2.2, P2-02-01. + +**Pre-mortem (12 months from now, v0.5 failed — why?):** +- Most likely: **P1 cosmos-sdk dep resolution spirals** — `go mod tidy` pulls a + module requiring go 1.23+, or a cometbft/tendermint replace directive conflict + blocks `go build`. P1 stalls, the milestone slips. Mitigation: G-018 hard + gate + escalation to a go version bump (out of scope, but visible). +- Second: **the NoWithVeto regression test (G-017) is discovered at P7, not P0** + — the P7 implementer finds `TestTallyResultNoWithVetoAlwaysZero` fails, does + not know whether to delete it (breaking a v0.2 locked-invariant test) or + keep it (blocking Veto population). The plan did not flag this. Mitigation: + G-017 forces the reconciliation NOW, with a binding fix that specifies + exactly how the test evolves (rename + re-scope, not delete). +- Third: **the CLOB per-match clamp's "implied coupon" is ambiguous** (G-019) — + two implementers compute it differently, the reject threshold is + inconsistent, and the simtest passes with one formula while mainnet would + use another. Mitigation: G-019 forces a single `ImpliedCoupon` helper. + +### Axis 6 — Dependency graph (P3→P4 shim, outer→inner chain) — **PASS** (confidence 0.80) + +The cross-phase dependency map (PLANS.md) is complete and correct: +- P1-01-01 (cosmos-sdk dep) blocks ALL runtime work (no `Msg*` compiles without + it). D-062 GRILL ratification is the gate. +- P1-03-01 (x/bridge keeper) blocks P1-05-01 (x/exit keeper — BridgeKeeper shim + wired to real bridge keeper in simtest). +- P3-01-01 (x/partner expected-keepers incl. HubKeeper shim) breaks the P3→P4 + hub dep. P4-04-01 wires the real hub keeper to the PartnerKeeper shim. +- P4-99-01 blocks P5-01-01 (services sit on hub) AND P6-01-01 (bond uses hub + lending). P5 and P6 could parallelize but config `parallelization.enabled: + false` — serial. Correct. +- P6-99-01 blocks P7-01-01 (council cross-cutting, lands last). + +No hidden edges. The v0.1 baseline keepers (mirror/forge/still/watcher/bread) +have EMPTY `keeper/` dirs — v0.5 does NOT promote them (out of scope). The +expected-keeper shims reference the baseline keepers by INTERFACE (e.g., +`BreadKeeper.MintWrappedBread`), but the baseline keepers are empty stubs — +the shims will be wired to STUB implementations in simtest (G-003 test +exemption), not real keepers. This is correct for simtest grade (D-054) but +**the plan does not explicitly state that the v0.1 baseline keepers remain +empty stubs**. **Binding fix G-022**: P1-06-01 (and each simtest wiring a +baseline keeper shim) MUST document that the baseline keeper is a STUB +returning sentinels, not a real implementation — so a future agent does not +mistakenly promote a v0.1 baseline keeper in v0.5. Confidence holds. + +### Axis 7 — Risk surface (CustodyKeyring, CLOB, IBC, Mission-Lock) — **PASS** (confidence 0.78) + +The four security-critical surfaces (PERSONAS.md security-engineer): +1. **CustodyKeyring** (D-058): interface + in-memory `memKeyring`. Key rotation + via `Status` reporting active key version; no cross-block caching. Simtest + covers rotation. The interface boundary keeps v0.5 dep-neutral w.r.t. + custody vendors. Sound. +2. **CLOB per-match clamp** (D-057/D-063): REJECT above 800 (G-019 implies- + coupon helper). The 8%/0% consts referenced directly (A-563, verified: the + v0.3 `Clamp` uses the consts). REQ-030 cross-const test stays green. +3. **IBC replay/timeout** (A-513): delete-on-ack, refund-on-timeout. G-021 + strengthens the replay assertion. Sound. +4. **Mission-Lock const firewall** (D-064): `ValidateBasic` rejects + `MissionLockAmendment-Rejected` kind; the const + the gate are the dual + firewall. v0.2 `TestMissionLockAmendableFalse` stays green (verified: the + const is unchanged; P7 does NOT touch `MissionLockAmendable`). Sound. + +The risk surface is well-identified and the mitigations are concrete. The one +gap is the NoWithVeto contradiction (G-017) — the v0.2 regression test +`TestTallyResultNoWithVetoAlwaysZero` is a "locked-invariant test" that v0.5 +must reconcile, not silently break. This is the single most material risk the +plan misses. + +### Axis 8 — Persona coverage (backend + lead + security + cosmos + mesh + data) — **PASS** (confidence 0.80) + +The roster is coherent: +- **backend-engineer** spans all P1..P7 handler + simtest work (the bulk). +- **lead-developer** owns P0 + P8 + coordination + GRILL-ratification follow- + through. +- **security-engineer** (REACTIVATED) owns CustodyKeyring, CLOB clamp, IBC + replay, Mission-Lock firewall — the four security-critical surfaces. Correct + reactivation; v0.5 has higher invariant density than v0.3. +- **cosmos-engineer** (REACTIVATED) owns MsgServer/expected-keepers/simtest + scaffolding — correct, since cosmos-sdk is now load-bearing (D-055). +- **mesh-engineer** (REACTIVATED, P2 phase-specific) owns bearer session + lifecycle. Correct scoping (P2 only; removed after). +- **data-engineer** (REACTIVATED, P4 phase-specific) owns hub custody state + (in-memory test store). Correct scoping (P4 only; removed after). +- **ci-security-auditor** activated in P8 for the feature purity gate. + +Territory globs are non-overlapping (backend-engineer's `x/{exit,bridge,...} +/**` vs cosmos-engineer's `keeper/**`, `types/msg_*.go`, `expected_keepers.go`, +`module.go`). The overlap risk is `keeper/msg_server.go` — both backend and +cosmos could claim it. The plan assigns cosmos-engineer to `keeper/**` and +backend-engineer to `x//**` (which includes keeper/). This is a glob +overlap. **Binding fix G-023**: clarify in PERSONAS.md that for v0.5, +cosmos-engineer owns the Cosmos-convention scaffolding (`keeper/keeper.go`, +`keeper/msg_server.go` skeleton, `module.go`, `types/msg_*.go`, +`types/expected_keepers.go`); backend-engineer owns the handler LOGIC (the +business rules inside `msg_server.go` methods, the simtest). This mirrors the +v0.2 G-007 split. Warn-mode (config), non-blocking, but ambiguous. Confidence +holds. + +### Axis 9 — Verification coverage (simtest, locked-const, feature purity gate) — **CONDITIONAL** (confidence 0.73) + +Each phase has a verification task (P1-99-01..P7-99-01) running `go build ./... ++ go test ./...` + coverage ≥80% + lexicon + G-003. The P8 audit (P8-02-01) +runs the feature purity gate: no breaking schema changes, locked-const +firewall intact, G-003 intact, G-006 GRILL-ratified. This is concrete. + +Two verification gaps: +1. **The feature purity gate does not define "breaking schema change" precisely + enough for the NoWithVeto case.** Populating an existing zero-locked field is + a SEMANTIC change to a v0.2 locked-invariant test, but NOT a struct-shape + change. The gate as written ("v0.3 `types/` contracts NOT amended") would + pass (the struct is not amended), but `TestTallyResultNoWithVetoAlwaysZero` + would FAIL. The gate must be extended to "no v0.1..v0.4 locked-invariant + TEST is broken without an explicit, documented reconciliation". **G-017** + forces this reconciliation. The P8 audit (P8-02-01) must explicitly verify + that the v0.2 `TestTallyResultNoWithVetoAlwaysZero` is either (a) renamed and + re-scoped to "default is 0, but Watcher Vetos populate it" with a new test, + or (b) explicitly deleted with a replacement test asserting the quorum-based + Veto semantics. **Binding fix G-017** specifies (a). +2. **The v0.1 baseline keeper stubs are not verified to remain empty.** The + feature purity gate should assert that v0.5 does NOT promote the 5 v0.1 + baseline keepers (mirror/forge/still/watcher/bread). **G-022** records this. +3. **The `types/` stdlib-only property is not verified.** v0.5 breaks it (by + design — `Msg*` needs `sdk.Msg`), but the invariant/lexicon TESTS in `types/` + must stay stdlib-only. **Binding fix G-024**: P1-99-01 MUST add a CI + assertion (or a test) that `go test ./x//types/...` passes WITHOUT + importing cosmos-sdk in the test files (i.e., the invariant/lexicon tests + remain stdlib-only; only `msg_*.go` imports `sdk.Msg`). This protects the + v0.1-v0.4 invariant-test durability. + +Confidence holds after G-017, G-022, G-024. + +--- + +## 3. Feature Purity Gate Verification + +v0.5 is a **feature** milestone (not NFR). The gate (P8-02-01) verifies: + +- **At least one `feat:` phase exists**: YES — all P1..P7 are `feat` (confirmed: + each phase's task table header says `Type: feat`). P8 is `final`. ✅ +- **No breaking schema changes (locked-consts unchanged)**: + - `CouponCapBps=800` / `CouponFloorBps=0` (D-028) — unchanged (verified; + P6 references directly, A-563). ✅ + - `SignalKindCount=4` — unchanged (P1-2 defensible; P7 does NOT change it; + `TestSignalKindShapeIntentional` stays green). ✅ + - `MissionLockAmendable=false` — unchanged (P7 does NOT touch the const; + `TestMissionLockAmendableFalse` stays green). ✅ + - `BearerTypeCount=6`, `PartnerTierCount=4`, `HubServiceCount=3`, + `BridgeStatusCount=4`, `ExitStatusCount=5`, `ServiceKindCount=4`, + `CouncilKindCount=3`, `OrderSideCount=2`, `OrderStatusCount=3` — all + unchanged (each phase's Must-Haves assert regression). ✅ + - **`LendingCouponCapBps=800` / `LendingCouponFloorBps=0`** — unchanged + (P4 references directly; REQ-030 cross-const test stays green). ✅ + - **New locked-consts added in P7** (per D-060): `ProposalKindCount=4`, + `ProposalStatusCount=5`, `VoteOptionCount=4`. These are ADDITIVE (new + types), not amendments to existing consts. Allowed under the feature + purity gate. ✅ + - **`TallyResult.NoWithVeto` field**: the FIELD is unchanged (v0.2 shape); the + SEMANTIC invariant ("always 0") changes. The v0.2 regression test + `TestTallyResultNoWithVetoAlwaysZero` MUST be reconciled (G-017). This is + the gray area the gate must explicitly address. **CONDITIONAL → fixed by + G-017**. +- **G-003 production firewall intact**: expected_keepers.go are INTERFACES (not + struct imports); the existing G-003 import-invariant test auto-covers new + files. P8-01-01 probes this. ✅ (with G-021 strengthening the IBC replay + assertion). +- **G-006 controlled exception genuinely scoped**: cosmos-sdk + ibc-go in + `go.mod` (D-055/D-062), scoped to runtime phases P1..P7; P0 + P8 dep-neutral; + `types/` gain `sdk.Msg` in `msg_*.go` only; invariant/lexicon tests stay + stdlib-only (G-024). ✅ (with G-018 build gate + G-024 stdlib-test assertion). + +**Feature purity gate verdict: PASS WITH FIXES** (G-017, G-018, G-021, G-022, +G-024). The gate is enforceable after the fixes land. + +--- + +## 4. Binding Grill Fixes (G-017..G-024) + +These are **binding** — the orchestrator MUST apply them before the affected +phase ships. Numbered G-017..G-024 (continuing from the v0.4 grill G-016). + +| ID | Binding Fix | Rationale | Confidence | Affects (phase / task) | +|----|-------------|-----------|------------|------------------------| +| **G-017** | **Reconcile the v0.2 `TestTallyResultNoWithVetoAlwaysZero` regression test with the v0.5 Veto population.** The v0.2 test (`x/council/types/types_test.go:233-239`) asserts `NoWithVeto == 0` "always" (anti-greed). v0.5 P7 (D-060) populates `NoWithVeto` with Watcher Vetos (quorum-based, D-065). The plan does NOT flag this contradiction. **Binding fix**: P7-03-01 MUST (a) RENAME `TestTallyResultNoWithVetoAlwaysZero` to `TestTallyResultNoWithVetoDefaultZero` (asserting the DEFAULT `TallyResult` has `NoWithVeto==0`, i.e., a proposal with zero Vetos has zero NoWithVeto — still true), and (b) ADD `TestTallyResultNoWithVetoPopulatedByQuorum` asserting that Watcher Vetos populate `NoWithVeto` and the proposal fails only at `NoWithVeto >= WatcherVetoQuorum` (default 6). The v0.2 anti-greed invariant ("no SINGLE-veto block") is preserved by the quorum rule; the "always 0" wording was v0.2's way of saying "no Veto option existed yet". The P8 feature purity gate (P8-02-01) MUST explicitly verify this reconciliation (the v0.2 test is not silently deleted; it is renamed + re-scoped). | The v0.2 test is a locked-invariant regression firewall. Silently breaking it at P7 violates the feature purity gate ("no breaking schema changes" extended to "no locked-invariant TEST broken without reconciliation"). The contradiction is real (verified: `types_test.go:233` asserts always 0; RESEARCH §2.7 / ARCHITECTURE.md v0.5 say NoWithVeto is populated). The fix preserves the anti-greed invariant (default 0; quorum-based population) while documenting the v0.2→v0.5 evolution. | 0.88 | **P7-03-01** (simtest + types_test.go extend); **P8-02-01** (feature purity gate must verify the reconciliation). Must land before P7 ships. | +| **G-018** | **P1-01-01 cosmos-sdk dep is a HARD build gate under go 1.22.** `go mod tidy` + `go build ./...` MUST succeed under `go 1.22` (the `go.mod` declared version). If the cosmos-sdk v0.50.x + ibc-go v8.x transitive tree requires go 1.23+, P1 does NOT ship — escalate (a go version bump is out of scope for v0.5). P1-01-01 verification MUST assert `go version` reports 1.22+ and `go build ./...` exits 0 with no module-replace hacks for go-version conflicts. If a `replace` directive is needed for cometbft/tendermint separation, it MUST be documented in P1-01-01's deliverable. | cosmos-sdk v0.50.x is documented as go 1.22-compatible, but the transitive tree is large (hundreds of modules). A single transitive module requiring go 1.23+ blocks P1 and cascades to all P2..P7. The plan's P1-01-01 verification says "go version compatible" — this makes it a HARD gate, not advisory. | 0.82 | **P1-01-01** (go.mod + go.sum). Must land before P1 ships. | +| **G-019** | **P6-02-01 (`x/bond/keeper/clob.go`) MUST define a single `ImpliedCoupon(priceBps, principal) uint32` helper (or equivalent) used by BOTH the CLOB match and the per-match clamp check.** The "implied coupon" derivation from trade price (fraction of principal in bps) is not specified in the plan. If two implementers compute it differently, the D-063 REJECT threshold (above 800) is inconsistent. The helper MUST have a unit test covering the boundary: price implying exactly 800, 801, 799 bps (reject at 801, clamp at 800, pass at 799). | D-063 ratifies REJECT above 800, but the "implied coupon" formula is the unstated precondition. A latent ambiguity in the highest-severity locked-const clamp is a real defect. A single helper + boundary test closes it. | 0.80 | **P6-02-01** (clob.go). Must land before P6 ships. | +| **G-020** | **P7-01-01 MUST add a `Params.Validate()` (or `ValidateBasic` on the param) asserting `WatcherVetoQuorum` is in `[1, 9]` (recommended `[2, 9]` to forbid single-veto-block even if the param is mis-set).** The default 6 (D-065) is correct; the validation bounds are the missing piece. A param of 0 (any single Veto blocks) or 10 (veto impossible — more than 9 Watchers) would violate REQ-004 / anti-greed semantics. The v0.2 `Params` struct is empty (`types.go:148`); P7-01-01 adds the field + MUST add the validation. | D-065 ratifies the default 6 but does not bound the param. An unbounded param is a governance footgun: a future vote could set 0 (single-veto-block, violating anti-greed) or 10 (veto impossible). Validation bounds are the standard Cosmos-SDK `Params.Validate` pattern. | 0.78 | **P7-01-01** (Params + Validate). Must land before P7 ships. | +| **G-021** | **P1-06-01 (`x/bridge` simtest) MUST include a NEGATIVE assertion that a SECOND `OnAcknowledgementPacket` with the same packet commitment is REJECTED (returns an error), not silently no-op'd.** The distinction between "no-op" and "reject" matters: ibc-go returns an error on a duplicate ack (the relayer sees the failure and does not retry); a silent no-op could mask a relayer bug. The simtest MUST assert the second ack returns a non-nil error. | A-513 mandates replay protection (delete-on-ack, reject-on-second). The plan says "reject on second" but the simtest verification says "deletes the in-flight record on first ack and rejects the second" — the "reject" must be an ERROR, not a silent no-op. A silent no-op is the CVE-class pitfall (the relayer cannot distinguish a bug from idempotency). | 0.80 | **P1-06-01** (bridge simtest). Must land before P1 ships. | +| **G-022** | **Each simtest wiring a v0.1 baseline keeper shim (BreadKeeper, WatcherKeeper, WindowKeeper, VaultKeeper, StandKeeper) MUST document that the baseline keeper is a STUB returning sentinels, NOT a real implementation.** v0.5 promotes 8 v0.3 modules; the 5 v0.1 baseline keepers (mirror/forge/still/watcher/bread) remain EMPTY stubs (verified: their `keeper/` dirs are empty). The expected-keeper shims reference baseline keepers by interface; in simtest they are wired to STUB impls (G-003 test exemption). The P8 feature purity gate MUST assert the 5 v0.1 baseline keepers are NOT promoted in v0.5 (no new `.go` files in their `keeper/` dirs). | The plan does not explicitly state the baseline keepers remain stubs. A future agent might "helpfully" promote a baseline keeper while wiring a shim, breaking the v0.5 scope boundary (D-056 scopes P1..P7 to the 8 v0.3 modules). Documenting the stub expectation + a P8 gate assertion prevents scope creep. | 0.75 | **P1-06-01, P2-03-01, P3-03-01, P5-03-01, P6-03-01, P7-03-01** (all simtests wiring baseline keeper shims); **P8-02-01** (feature purity gate asserts baseline keepers unchanged). Must land before each simtest phase ships; P8 gate before milestone ship. | +| **G-023** | **Clarify `keeper/msg_server.go` ownership between backend-engineer and cosmos-engineer in PERSONAS.md.** Both personas' territory globs overlap on `keeper/**` (cosmos-engineer's explicit list) and `x//**` (backend-engineer's glob, which includes `keeper/`). **Binding fix**: for v0.5, cosmos-engineer owns the Cosmos-convention SCAFFOLDING (`keeper/keeper.go` skeleton, `keeper/msg_server.go` method signatures, `module.go`, `types/msg_*.go`, `types/expected_keepers.go`); backend-engineer owns the handler LOGIC (the business rules inside `msg_server.go` method bodies, the simtest). This mirrors the v0.2 G-007 split (Cosmos-mirroring vs bespoke modules). Warn-mode (config), non-blocking, but ambiguous. | Territory enforcement is `warn` (non-blocking), but ambiguous ownership on the highest-effort files (`msg_server.go` method bodies) risks edit conflicts. The split mirrors v0.2 G-007 and aligns with the actual task assignments (cosmos-engineer scaffolds, backend-engineer implements logic). | 0.72 | **PERSONAS.md** (v0.5 roster). Apply before P1 begins. | +| **G-024** | **P1-99-01 MUST add a CI assertion (or a test) that the invariant/lexicon tests in `x//types/` pass WITHOUT importing cosmos-sdk.** v0.5 breaks the v0.1-v0.4 `types/`-is-stdlib-only property by adding `sdk.Msg` to `types/msg_*.go` (D-055). The invariant tests (locked-const counts, enum round-trips) and lexicon assertions in `types/*_test.go` MUST remain stdlib-only — they should not import `sdk.Msg` or `sdk.Context`. The assertion: `grep -L "cosmos-sdk\|sdk.Msg\|sdk.Context" x//types/*_test.go` (or a `go/parser` scan) confirms the invariant/lexicon test files do not import cosmos-sdk. | The v0.1-v0.4 invariant tests are durable because they compile with stdlib only. If v0.5 silently lets an invariant test import `sdk.Msg`, the test breaks if cosmos-sdk is removed (e.g., a future skeleton revert). Keeping invariant/lexicon tests stdlib-only preserves their durability across dep changes. The `msg_*.go` isolation (RESEARCH §3.1) is the intent; G-024 makes it a tested invariant. | 0.78 | **P1-99-01** (and each phase's 99-01 verification). Must land before P1 ships; carries through P2..P7. | + +--- + +## 5. Escalations + +**None.** All nine axes resolved at confidence ≥ 0.60 after the binding fixes +G-017..G-024 are applied. No axis required escalation to the human. At full +autonomy, the orchestrator applies the binding decisions and proceeds to P1. + +The single most material finding is **G-017** (the NoWithVeto regression-test +contradiction): the v0.2 `TestTallyResultNoWithVetoAlwaysZero` test and the v0.5 +P7 Veto-population design are in direct conflict. This is not an escalation +(the fix is mechanical: rename + re-scope the test, add a quorum-population +test), but it is the finding most likely to cause a P7 stall if not surfaced +now. The grill surfaces it; the orchestrator applies G-017 before P7. + +--- + +## 6. Overall Verdict + +### **SHIP Phase 0 WITH FIXES** (confidence 0.78) + +The v0.5 Phase 0 plan is fundamentally sound and well-grounded: the runtime- +promotion pattern (MsgServer + simtest, not mainnet — D-054) is the standard +Cosmos-SDK pre-mainnet step; the 8-module scope is proportionate (36 tasks / +8 phases); the outer→inner dependency chain (D-056) is correct; the +`expected_keepers.go` shim convention preserves G-003; the locked-const +firewall is intact (all v0.1..v0.4 consts unchanged; new P7 enums are +additive). The plan's architecture claims were verified against the actual +codebase (zero cosmos-sdk imports today; locked consts present; G-003 test +green; ClampGrowth has the G-012 guard; cross-const test has the G-015 absolute +assertion). + +The 5 decision ratifications (D-055, D-062, D-063, D-064, D-065) are all +**RATIFIED**: the cosmos-sdk dep is necessary and scoped (D-055); the v0.50.x ++ ibc-go v8.x pin is the stable choice (D-062); REJECT above 800 is the +mission-lock-true choice (D-063); `ValidateBasic` rejection is the cleanest +Mission-Lock firewall (D-064); Veto quorum 6 as a param (not a const) is the +governance-tunable choice (D-065). + +The 8 binding fixes (G-017..G-024) are **correctness and verification +hardening**, not scope rework: +- **G-017** (NoWithVeto reconciliation) — the single real contradiction; a + v0.2 regression test that v0.5 must reconcile, not silently break. Must + land before P7 ships. +- **G-018** (go 1.22 build gate) — the cosmos-sdk dep's transitive tree is + the highest feasibility risk; a hard gate prevents a P1 stall from + cascading. Must land before P1 ships. +- **G-019** (ImpliedCoupon helper) — the CLOB per-match clamp's unstated + formula; a single helper + boundary test closes the ambiguity. Must land + before P6 ships. +- **G-020** (WatcherVetoQuorum validation bounds) — the param is unbounded + in the plan; validation bounds prevent a governance footgun. Must land + before P7 ships. +- **G-021** (IBC replay reject-not-noop) — the CVE-class pitfall's simtest + assertion must require an ERROR, not a silent no-op. Must land before P1 + ships. +- **G-022** (baseline keeper stub documentation + P8 gate) — the 5 v0.1 + baseline keepers must remain stubs; document + gate. Must land before each + simtest phase; P8 gate before milestone ship. +- **G-023** (msg_server.go ownership split) — backend vs cosmos territory + overlap on the highest-effort files; clarify before P1. Apply before P1. +- **G-024** (types/ invariant-test stdlib-only assertion) — the v0.1-v0.4 + invariant-test durability must survive the cosmos-sdk onboarding; a CI + assertion protects it. Must land before P1 ships; carries through P2..P7. + +None of these rise to "RETHINK" or "REDUCE SCOPE" — the architecture, scope, +ordering, and persona assignments are correct. Apply the 8 binding fixes and +proceed to Phase P1. + +**Confidence in overall verdict: 0.78** + +--- + +## 7. Summary Block + +``` +Decision ratifications: + D-055 (cosmos-sdk + ibc-go dep, G-006 exception) — RATIFY (0.82) + D-062 (cosmos-sdk v0.50.x + ibc-go v8.x pin) — RATIFY (0.80) + D-063 (bond match above 800 = REJECT) — RATIFY (0.78) + D-064 (MissionLockAmendment reject at ValidateBasic) — RATIFY (0.85) + D-065 (Watcher Veto quorum default 6) — RATIFY (0.78) + +Nine-axis scorecard: + 1. Feasibility — PASS (0.80) + 2. Scope — PASS (0.78) + 3. Cost/Effort — CONDITIONAL (0.72) [cosmos-sdk onboarding risk] + 4. Architecture — CONDITIONAL (0.75) → strengthened by G-021 + 5. Risk — CONDITIONAL (0.72) → fixed by G-017, G-019 + 6. Dependency graph — PASS (0.80) → strengthened by G-022 + 7. Risk surface — PASS (0.78) + 8. Persona coverage — PASS (0.80) → strengthened by G-023 + 9. Verification — CONDITIONAL (0.73) → fixed by G-017, G-022, G-024 + +Feature purity gate: PASS WITH FIXES (G-017, G-018, G-021, G-022, G-024) + +Binding fixes: 8 (G-017..G-024) + G-017 — NoWithVeto regression-test reconciliation — before P7 + G-018 — cosmos-sdk go 1.22 hard build gate — before P1 + G-019 — CLOB ImpliedCoupon helper + boundary test — before P6 + G-020 — WatcherVetoQuorum Params.Validate bounds — before P7 + G-021 — IBC replay reject-not-noop simtest assertion — before P1 + G-022 — v0.1 baseline keeper stub documentation + P8 gate — before each simtest / P8 + G-023 — keeper/msg_server.go ownership split — before P1 + G-024 — types/ invariant-test stdlib-only CI assertion — before P1 + +Escalations: 0 +Overall: SHIP Phase 0 WITH FIXES (confidence 0.78) +``` + +--- + +## 8. CI Commit Block (for the orchestrator; DO NOT auto-commit per task constraints) + +``` +docs(grill): v0.5 adversarial review — 5 decisions ratified, 8 binding fixes (G-017..G-024) + +---ci--- +project: oy +phase: 0 +milestone: v0.5 +status: grill +decisions: + - id: D-055 + decision: RATIFY — cosmos-sdk + ibc-go dep as G-006 controlled exception (scoped to runtime P1..P7) + rationale: go.mod zero-dep today (verified); MsgServer promotion impossible without cosmos-sdk; exception scoped (types/ Msg* isolated, invariant/lexicon tests stdlib-only per G-024) + confidence: 0.82 + alternatives: [stay zero-dep hand-rolling store+messages (duplicates SDK, high risk); defer all runtime to v0.6+ (stalls)] + - id: D-062 + decision: RATIFY — cosmos-sdk v0.50.x + ibc-go v8.x version pin + rationale: v0.50.x LTS go 1.22-compatible; ibc-go v8.x stable pairing; v10 IBC-v2 deferred (newer, churn risk); G-018 hard go 1.22 build gate + confidence: 0.80 + alternatives: [cosmos-sdk v0.50.x + ibc-go v10 (newer); cosmos-sdk v0.47.x + ibc-go v7.x (older LTS)] + - id: D-063 + decision: RATIFY — bond CLOB match above 800 bps = REJECT (fails closed) + rationale: 8% cap is Mission-Lock invariant (D-028); match above cap is usury violation, not clampable excess; REJECT simpler (no refund path); G-019 ImpliedCoupon helper closes formula ambiguity + confidence: 0.78 + alternatives: [clamp-with-refund (adds refund path, softens mission-lock)] + - id: D-064 + decision: RATIFY — MissionLockAmendment-Rejected ProposalKind rejected at ValidateBasic + rationale: const MissionLockAmendable=false (v0.2, verified) is firewall; ValidateBasic is gate; message never reaches handler; no dead state; v0.2 TestMissionLockAmendableFalse stays green + confidence: 0.85 + alternatives: [propose-then-fail (records Pending→Failed, dead state growth)] + - id: D-065 + decision: RATIFY — Watcher Veto quorum default = 6 (param, not locked const) + rationale: matches REQ-004 6-of-9; single Veto does NOT block (anti-greed); param-tunable (not Mission-Lock const); G-020 adds Validate bounds [2,9] + confidence: 0.78 + alternatives: [lock as const 6 (over-rigid); default 9 (veto impossible)] +fixes: + - id: G-017 + fix: Reconcile v0.2 TestTallyResultNoWithVetoAlwaysZero with v0.5 Veto population — rename to TestTallyResultNoWithVetoDefaultZero + add TestTallyResultNoWithVetoPopulatedByQuorum + affects: P7-03-01, P8-02-01 + before_phase: P7 + confidence: 0.88 + - id: G-018 + fix: P1-01-01 cosmos-sdk dep is HARD go 1.22 build gate (escalate if transitive tree requires go 1.23+) + affects: P1-01-01 + before_phase: P1 + confidence: 0.82 + - id: G-019 + fix: P6-02-01 clob.go MUST define single ImpliedCoupon helper + boundary unit test (800/801/799 bps) + affects: P6-02-01 + before_phase: P6 + confidence: 0.80 + - id: G-020 + fix: P7-01-01 MUST add Params.Validate asserting WatcherVetoQuorum in [2,9] + affects: P7-01-01 + before_phase: P7 + confidence: 0.78 + - id: G-021 + fix: P1-06-01 bridge simtest MUST assert second OnAcknowledgementPacket returns ERROR (not silent no-op) + affects: P1-06-01 + before_phase: P1 + confidence: 0.80 + - id: G-022 + fix: Simtests wiring baseline keeper shims MUST document stub-not-real; P8 gate asserts 5 v0.1 baseline keepers unchanged + affects: P1-06-01, P2-03-01, P3-03-01, P5-03-01, P6-03-01, P7-03-01, P8-02-01 + before_phase: P1 (simtests); P8 (gate) + confidence: 0.75 + - id: G-023 + fix: PERSONAS.md clarify keeper/msg_server.go ownership — cosmos-engineer scaffolds, backend-engineer implements logic + affects: PERSONAS.md + before_phase: P1 + confidence: 0.72 + - id: G-024 + fix: P1-99-01 CI assertion — invariant/lexicon tests in x//types/ stay stdlib-only (no cosmos-sdk import) + affects: P1-99-01 (carries through P2..P7) + before_phase: P1 + confidence: 0.78 +escalations: [] +---/ci--- +``` diff --git a/.ciagent/oy/PERSONAS.md b/.ciagent/oy/PERSONAS.md index 34df323..7c9e052 100644 --- a/.ciagent/oy/PERSONAS.md +++ b/.ciagent/oy/PERSONAS.md @@ -3,80 +3,131 @@ active_personas: - id: backend-engineer active: true phase_specific: false - reason: Owns the v0.4 NFR code work: REQ-029 (lexicon shared helper in `lexicon/lexicon.go` + refactor of both meta-tests to consume it), REQ-030 (new `x/hub/types/cross_const_test.go` test-only import of `x/bond/types`), and REQ-031's regression-guard test (council SignalKind intent-assertion test). The v0.3 frontend/docs-writer personas are deactivated because v0.4 has no docs-content authoring; the only docs-adjacent work is the CI workflow file (REQ-032, owned by lead-developer as infra/config). v0.4 is pure Go test/refactor work, which is backend-engineer's core territory. - frameworks: [Go 1.22 stdlib (zero-dep), Go testing, lexicon firewall] - territory: ["lexicon/**", "lexicon_meta_test.go", "lexicon_meta_docs/**", "x/hub/types/**", "x/bond/types/**", "x/council/types/**"] - constraints: ["zero external deps (G-006 — go.mod stays zero-require)", "D-001 refinement-only filter: refactor/test/quality only, NO feat: (no new enum types, no new production types, no behavioral change)", "G-003 by-ID-string rule preserved in PRODUCTION imports; test-only cross-package imports are EXEMPT (G-003 test exemption — REQ-030 relies on this)", "lexicon firewall stays green on both x/ and docs/ after refactor", "locked-const invariants stay green (SignalKindCount==4 unchanged; LendingCouponCapBps==800, LendingCouponFloorBps==0 unchanged)", "≥80% coverage on any modified package (do not reduce existing coverage)"] + reason: Owns the v0.5 runtime promotion across P1..P7 — every keeper MsgServer message handler + simtest end-to-end flow for x/exit, x/bridge, x/bearers, x/partner, x/hub, x/services, x/bond, and x/council. This is the bulk of the milestone: the v0.3 skeletons were types + in-memory keeper stubs (verified — e.g. `x/partner/types/types.go:101 type Keeper struct{...}` with `NewKeeper()` returning `&Keeper{partners: make(map[string]Partner)}`, zero cosmos-sdk imports in `x/`). v0.5 adds `keeper/keeper.go` (store-backed), `keeper/msg_server.go` (one handler per `Msg*`), `types/msg_*.go` (`sdk.Msg` impls), `module.go` (RegisterServices), and a simtest exercising each handler against an in-memory `sdk.Context`. backend-engineer is the single persona that spans all seven runtime phases (P1..P7) plus the lexicon/locked-const regression guards that carry forward from v0.4. The reactivated cosmos-engineer/security-engineer/mesh-engineer personas advise on conventions and invariants but the implementation is backend-engineer's territory. + frameworks: [Go 1.22, cosmos-sdk v0.50.x (D-055 GRILL-approved), ibc-go v8.x, Go testing, simtest, lexicon firewall, locked-const invariant tests] + territory: ["x/exit/**", "x/bridge/**", "x/bearers/**", "x/partner/**", "x/hub/**", "x/services/**", "x/bond/**", "x/council/**", "lexicon/**", "lexicon_meta_test.go", "lexicon_meta_docs/**"] + constraints: ["G-003 production firewall intact — keeper-to-keeper cross-module calls use expected_keepers.go interface shims (ibc-go convention), NOT struct imports of x//types; by-ID-string rule preserved at the type level", "G-006 controlled exception (D-055) — go.mod gains cosmos-sdk v0.50.x + ibc-go v8.x (GRILL-ratified); types/ packages gain sdk.Msg imports for Msg* types but invariant/lexicon tests stay stdlib-only and green", "locked-const invariants unchanged — 8%/0% bond cap (D-028), 6 bearers, 4 Partner tiers, MissionLockAmendable=false, SignalKindCount=4 (P1-2 defensible), BearerTypeCount=6, BridgeStatusCount=4, ExitStatusCount=5, etc. — v0.5 ADDS ProposalKind/ProposalStatus/VoteOption enums (AUDIT §193 P1-1) but does NOT change existing locked consts", "lexicon firewall stays green on both x/ and docs/ after runtime promotion — Msg* struct names are the new lexicon surface (e.g. AVOID 'deposit' in x/hub custody message names; use MsgCustodyReceiveAsset/MsgCustodyReleaseAsset per A-542)", "simtest NOT mainnet (D-054) — handlers exercised against in-memory sdk.Context + dbm in-memory store; no real IBC light clients, no real MPC, no real bearer hardware, no real DEX venues, no real Watcher attestations (all stubbed)", "≥80% coverage on runtime packages (D-033 carries forward) — every keeper/msg_server.go + simtest must hit the bar; table-driven handler tests per Msg*", "Mission Lock const firewall intact (G-003) — MissionLockAmendment-Rejected ProposalKind is rejected at ValidateBasic (A-572); the const + the ValidateBasic gate are the dual firewall"] - id: lead-developer active: true phase_specific: false - reason: Coordinates v0.4 phase decomposition (P1 lexicon+const hardening → P2 lifecycle divergence docs+guard → P3 docs CI → P4 review/ship), territory enforcement (warn mode per config.json), the final-phase NFR purity gate audit (zero `feat:` commits), and the milestone ship. Owns REQ-031's ARCHITECTURE.md documentation deliverable (the divergence-decision writeup) and REQ-032's CI workflow file (`.gitea/workflows/docs-build.yml`) as infra/config territory. Also owns the v0.4 ROADMAP.md / REQUIREMENTS.md status updates at milestone completion. - frameworks: [cross-cutting, Gitea Actions, Markdown, YAML] + reason: Coordinates v0.5 phase decomposition (P1 exit+bridge → P2 bearers → P3 anchors → P4 hub → P5 services → P6 bond → P7 council → P8 final review/audit/ship per D-056), territory enforcement (warn mode per config.json), and the final-phase feature purity gate audit (no breaking schema changes; locked-const firewall intact; G-003 production firewall intact). Owns the v0.5 ROADMAP.md / REQUIREMENTS.md status updates at milestone completion and the milestone ship. Also owns the GRILL-ratification follow-through for the cosmos-sdk version pin (A-504) and the planner-escalation items (A-562 reject-vs-clamp, A-572 reject-at-ValidateBasic, A-574 Watcher Veto quorum value) — these are escalated through the normal decision flow, not auto-decided. + frameworks: [cross-cutting, Gitea Actions, Markdown, YAML, git] territory: [".ciagent/**", ".gitea/workflows/**", ".ciagent/oy/ARCHITECTURE.md", ".ciagent/oy/ROADMAP.md", ".ciagent/oy/REQUIREMENTS.md"] - constraints: ["D-052 phase ordering (P1 firewall-first; each phase independently shippable)", "milestone versioning (v0.4 NFR / tag_base v0.3.x)", "NFR purity gate: zero feat: commits in the milestone (final-phase audit)", "persona territory warn-mode enforcement", "zero Go deps invariant (G-006) preserved; CI workflow may use build-only Python deps (mkdocs) in a separate job", "D-001 filter: no feat: scope creep — the CI workflow is chore (build+artifact), NOT a publishing feature"] + constraints: ["D-056 phase ordering (P1 exit → P2 bearers → P3 anchors → P4 hub → P5 services → P6 bond → P7 council → P8 final); each phase independently shippable (vertical slices)", "milestone versioning (v0.5 feature / tag_base v0.4.x); final-phase patch IS the milestone release (D-008)", "feature purity gate: zero breaking schema changes; zero locked-const amendments (Mission Lock non-amendable; SignalKind 4-not-5 unchanged); G-003 production firewall intact; G-006 controlled exception GRILL-ratified", "persona territory warn-mode enforcement (config.json)", "planner-escalation items (A-504 cosmos-sdk version pin, A-562 bond match reject-vs-clamp, A-572 MissionLockAmendment ValidateBasic rejection, A-574 Watcher Veto quorum) surfaced through the normal decision flow, not auto-decided"] + + - id: security-engineer + active: true + phase_specific: false + reason: REACTIVATED for v0.5. Owns the security-critical invariant surfaces introduced by runtime promotion: (1) the CustodyKeyring interface boundary in x/hub (D-058) — the Sign/Derive/Status contract + the in-memory memKeyring test impl, with key-rotation semantics (Status reports active key version; no caching across blocks); (2) the CLOB mission-lock clamp in x/bond (D-057) — the per-match coupon clamp to [0, 800] bps via the v0.3 Clamp helper, with a match above 800 REJECTED (fails closed, A-562; planner confirms reject-vs-clamp before P6); (3) IBC packet replay protection in x/bridge — the delete-on-ack / refund-on-timeout contract mirroring ibc-go (the CVE-class pitfall); simtest must cover both replay and timeout-refund; (4) the governance Mission-Lock const firewall in x/council (G-003) — MissionLockAmendable=false unchanged, the MissionLockAmendment-Rejected ProposalKind rejected at ValidateBasic (A-572), and the Watcher Veto quorum semantics (single Veto does NOT block; quorum-based, default 6 per REQ-004 6-of-9; A-574). The v0.3/v0.4 locked-const regression tests (TestMissionLockAmendableFalse, TestSignalKindShapeIntentional, the REQ-030 cross-const test) stay green. + frameworks: [Go 1.22, cosmos-sdk v0.50.x, ibc-go v8.x, Go testing, simtest, locked-const invariant tests, lexicon firewall] + territory: ["x/hub/types/keyring.go", "x/hub/keeper/keyring_mem*.go", "x/bond/types/types.go", "x/bond/keeper/**", "x/bridge/keeper/**", "x/council/types/types.go", "x/council/keeper/**", "lexicon/**"] + constraints: ["CustodyKeyring interface supports key rotation (Status reports active key version; handler consults keyring per operation, no cross-block caching)", "CLOB per-match coupon clamp to [0, 800] bps (D-028/D-057); match above 800 REJECTED (fails closed, A-562) — planner confirms reject-vs-clamp before P6", "IBC ack/timeout replay protection mirrors ibc-go (delete-on-ack, refund-on-timeout); simtest MUST cover both replay and timeout-refund cases (CVE-class pitfall)", "Mission Lock const firewall intact (G-003): MissionLockAmendable=false unchanged; MissionLockAmendment-Rejected ProposalKind rejected at ValidateBasic (A-572); Watcher Veto quorum-based (default 6, REQ-004 6-of-9), single Veto does NOT block (anti-greed, vision §19)", "locked-const regression tests stay green: TestMissionLockAmendableFalse, TestSignalKindShapeIntentional, the REQ-030 cross-const test (hub.LendingCouponCapBps==bond.CouponCapBps)", "compliance-before-custody ordering enforced in x/hub (withdrawal checks compliance status before the custody debit, A-544)", "lexicon firewall stays green — Msg* names avoid banned terms (e.g. 'deposit' banned; use MsgCustodyReceiveAsset/MsgCustodyReleaseAsset)"] + + - id: cosmos-engineer + active: true + phase_specific: false + reason: REACTIVATED for v0.5. cosmos-sdk is now a load-bearing dependency (D-055 GRILL-approved controlled exception to G-006), so Cosmos-SDK convention alignment is owned rather than advisory. Owns: (1) the MsgServer promotion pattern across all 8 target modules — keeper/keeper.go (store-backed, wraps sdk.KVStore), types/msg_*.go (sdk.Msg: ValidateBasic + GetSigners), keeper/msg_server.go (one *Response,error method per Msg*), module.go (AppModule + RegisterServices), simtest exercising each handler against an in-memory sdk.Context; (2) the IBC v2 / IBC Eureka patterns in x/bridge (OnRecvPacket/OnAcknowledgementPacket/OnTimeoutPacket, timestamp-only timeouts for EVM chains, the ICS-20 v1 payload parser); (3) the expected_keepers.go shim convention (ibc-go standard for breaking cross-module keeper dep cycles — e.g. x/exit/types/expected_keepers.go defines a BridgeKeeper interface that the x/bridge keeper satisfies structurally; preserves G-003 by-ID-string rule at the type level); (4) the simtest scaffolding (in-memory store, sdk.Context construction, event emission assertions). The v0.3 in-memory Keeper stubs (in types/types.go) are retired or wrapped as test helpers — the types/ public API is not broken. + frameworks: [Go 1.22, cosmos-sdk v0.50.x (D-055), ibc-go v8.x, cometbft (simtest in-memory store only), Go testing, simtest] + territory: ["x/exit/keeper/**", "x/exit/types/msg_*.go", "x/exit/types/expected_keepers.go", "x/exit/module.go", "x/bridge/keeper/**", "x/bridge/types/msg_*.go", "x/bridge/types/expected_keepers.go", "x/bridge/module.go", "x/bearers/keeper/**", "x/bearers/types/msg_*.go", "x/bearers/module.go", "x/partner/keeper/**", "x/partner/types/msg_*.go", "x/partner/types/expected_keepers.go", "x/partner/module.go", "x/hub/keeper/**", "x/hub/types/msg_*.go", "x/hub/types/expected_keepers.go", "x/hub/module.go", "x/services/keeper/**", "x/services/types/msg_*.go", "x/services/types/expected_keepers.go", "x/services/module.go", "x/bond/keeper/**", "x/bond/types/msg_*.go", "x/bond/types/expected_keepers.go", "x/bond/module.go", "x/council/keeper/**", "x/council/types/msg_*.go", "x/council/types/expected_keepers.go", "x/council/module.go"] + constraints: ["MsgServer convention (cosmos-sdk v0.40+ Stargate): MsgServer struct wraps the module Keeper; one method per Msg* returning (*Response, error); routed by base app MsgServiceRouter", "sdk.Msg contract: ValidateBasic (stateless gate, runs before handler), GetSigners (authz), ProtoMessage/JSONCodec registration", "handler state-machine ordering: (1) ValidateBasic (in msg), (2) keeper authz check, (3) state mutation under store, (4) ctx.EventManager().EmitEvent — reordering causes double-spend/replay", "expected_keepers.go convention: cross-module keeper deps are INTERFACES defined in the consuming module's types/ (e.g. x/exit/types/expected_keepers.go BridgeKeeper); the concrete keeper satisfies it structurally; NOT a struct import of x/bridge/types — G-003 preserved", "IBC handlers implement the ibc-go IBCModule / PacketExecutor contract (OnRecvPacket/OnAcknowledgementPacket/OnTimeoutPacket); ICS-20 v1 payload pinned to the v0.2 satellite packet shape", "simtest uses SDK in-memory store (dbm in-memory backend) + sdk.NewContext; no live CometBFT node, no real IBC light clients (D-054)", "version pin (A-504, planner/GRILL confirms): cosmos-sdk v0.50.x LTS + ibc-go v8.x (stable); ibc-go v10 IBC-v2/Eureka is the documented pattern but a newer pin"] + + - id: mesh-engineer + active: true + phase_specific: true + reason: REACTIVATED for the bearer transport runtime in P2 (REQ-034). Owns the OY-SAT + OY-QR message handlers in x/bearers: MsgSendOYSATFrame, MsgReceiveOYSATFrame, MsgIssueOYQR, MsgConsumeOYQR, and the session lifecycle (Open/Active/Closed/Revoked). The v0.3 OYSATLink (surveillance-resistant=true locked) and OYQRCode (one-shot consumed flag) become the handler state objects. Key mesh-specific invariants: (1) OY-QR is one-shot — MsgConsumeOYQR flips consumed BEFORE the transfer effect (replay rejected idempotently, A-521); (2) the surveillance-resistant const is a runtime invariant — the handler must NOT emit geolocation or sender physical location (simtest asserts the event set has NO geolocation fields, a negative test); (3) the BearerTransport interface gains a store-backed impl (the keeper acts as the transport in simtest; no hardware/RF dep, D-054). Hardware integration is explicitly deferred. mesh-engineer is phase-specific (P2 only) — outside P2 the bearer transport territory reverts to backend-engineer. + frameworks: [Go 1.22, cosmos-sdk v0.50.x, Go testing, simtest, lexicon firewall] + territory: ["x/bearers/keeper/**", "x/bearers/types/msg_bearer*.go", "x/bearers/types/types.go", "x/bearers/module.go", "x/bearers/simtest/**"] + constraints: ["OY-QR one-shot: MsgConsumeOYQR flips consumed BEFORE the transfer effect (atomic per-tx; replay finds consumed==true and returns error idempotently, A-521)", "surveillance-resistant const is a runtime invariant — handler emits NO geolocation / sender physical location; simtest negative-test asserts the event set is geolocation-free", "BearerTransport interface gets a store-backed impl (keeper as transport in simtest); NO hardware/RF/LoRa/BLE/satellite Go libraries (D-054 — runtime = message-handling + session lifecycle, not hardware)", "session lifecycle mirrors the v0.2 Window lifecycle (Open/Active/Closed/Revoked) for consistency; frames received on Closed/Revoked sessions are rejected", "lexicon-safe: 'session', 'frame', 'bearer', 'QR', 'SAT' are safe; AVOID 'account'/'deposit' (use reach-id/Stash by ID)"] + +phase_specific_personas: + - id: data-engineer + active: true + phase_specific: true + reason: REACTIVATED for P4 (Hub API runtime) ONLY — owns the hub custody state via an in-memory test store (the memKeyring + the keeper's store-backed custody asset records). The custody asset records are the closest thing to a data store in v0.5; there is NO real database and NO migration (the SDK in-memory store is the substrate). data-engineer's role is narrow: ensure the custody state shape (assetID → custody entry + sig ref + key version) is consistent with the CustodyKeyring interface and supports rotation. Removed after P4 (the hub runtime ships; later phases do not touch custody state shape). This mirrors the v0.3 data-engineer pattern (genesis schemas) but scoped to the P4 custody store. + frameworks: [Go 1.22, cosmos-sdk v0.50.x store, Go testing] + territory: ["x/hub/keeper/keyring_mem*.go", "x/hub/keeper/custody_state*.go"] + constraints: ["in-memory test store ONLY — no real database, no migration (D-054 simtest grade)", "custody state shape consistent with CustodyKeyring interface (assetID → custody entry + sig ref + key version); supports rotation", "removed after P4 (hub runtime ships; later phases do not touch custody state shape)"] deactivated: - id: frontend-engineer - reason: INACTIVE for v0.4. The v0.3 docs site (docs/**, mkdocs.yml) is COMPLETE; v0.4 does not author or restructure docs content. The only docs-adjacent work is the CI workflow that BUILDS the existing site (REQ-032), which is infra/config territory owned by lead-developer, not frontend toolchain. Reactivate in v0.5+ if docs content is restructured or i18n is added. + reason: INACTIVE for v0.5. The v0.3 docs site (docs/**, mkdocs.yml) is COMPLETE; v0.5 has no UI/docs-content work. The docs build CI (REQ-032, v0.4) already covers docs-build on every push. Reactivate in v0.6+ if docs content is restructured or i18n is added. - id: docs-writer - reason: INACTIVE for v0.4. v0.3's docs-writer owned page content authoring; v0.4 has zero new docs pages. The only documentation work is the ARCHITECTURE.md divergence-decision section (REQ-031), which is lead-developer's architecture territory, not audience-content authoring. Reactivate if a future milestone adds docs pages. - - id: data-engineer - reason: INACTIVE for v0.4 (carried from v0.3). The project has zero external deps and no database; REQ-031 does not change genesis schemas (it documents divergence, no schema change). Reactivate if a future milestone adds a real store/migration. - - id: cosmos-engineer - reason: INACTIVE for v0.4. v0.4 has no new Cosmos-convention-alignment work (no new modules, no IBC, no governance runtime). Reactivate in v0.5+ if live-runtime promotion of the v0.3 Bearers skeletons lands. - - id: security-engineer - reason: INACTIVE for v0.4. v0.4 introduces no new Mission-Lock-class invariant; REQ-029/030/031 are refactor/test/docs, not security invariants. The existing locked-consts stay unchanged. Reactivate if a future milestone adds a new mission-locked const or a new clamp. + reason: INACTIVE for v0.5. Same reason as frontend-engineer — v0.3's docs-writer owned page content authoring; v0.5 has zero new docs pages. The only documentation work is the ARCHITECTURE.md v0.5 runtime section + this PERSONAS.md + RESEARCH.md, which is lead-developer/researcher architecture territory, not audience-content authoring. Reactivate if a future milestone adds docs pages. - id: ci-security-auditor - reason: Default deactivated; activate in P4 (final review/ship) for the v0.4 milestone audit and NFR purity gate enforcement. - - id: mesh-engineer - reason: Still not needed in v0.4 (no bearer hardware runtime; OY-SAT/OY-QR remain type stubs). Activate in v0.5+ for real bearer runtime. + reason: Default deactivated; activate in P8 (final review/audit/ship) for the v0.5 milestone audit and feature purity gate enforcement (no breaking schema changes; locked-const firewall intact; G-003 production firewall intact; G-006 controlled exception GRILL-ratified). custom_personas: [] --- -# Personas: OpenYield (oy) — v0.4 (Refinement — NFR) +# Personas: OpenYield (oy) — v0.5 (Bearers Runtime — Feature) -> This file supersedes the v0.3 PERSONAS.md for the v0.4 milestone. v0.4 is a -> refinement-only NFR milestone (D-047): zero `feat:` phases. The active -> roster is **backend-engineer + lead-developer** only. The v0.3 -> phase-specific personas (frontend-engineer, docs-writer) are deactivated -> because v0.4 does not author docs content or restructure the docs toolchain; -> the only docs-adjacent work is a CI workflow file (REQ-032) owned by -> lead-developer as infra/config. +> This file supersedes the v0.4 PERSONAS.md for the v0.5 milestone. v0.5 is a +> **feature** milestone (D-054): the v0.3 Bearers skeletons are promoted +> from types + in-memory keeper stubs + invariant tests to live keeper +> MsgServer message handlers + simtest-grade end-to-end flows. This is +> NOT mainnet — D-020 continues to govern network deployment; runtime = +> simtest-grade handlers, not live chain. +> +> The active roster is **backend-engineer + lead-developer + security- +> engineer (REACTIVATED) + cosmos-engineer (REACTIVATED) + mesh-engineer +> (REACTIVATED, P2 phase-specific)**. The v0.3 docs personas (frontend- +> engineer, docs-writer) are deactivated because v0.5 has no docs-content +> work (the docs site is complete from v0.3; the docs build CI is complete +> from v0.4). data-engineer is reactivated as a P4-phase-specific persona +> for the hub custody state (in-memory test store only; removed after P4). +> ci-security-auditor is default off; activate in P8 for the final audit. +> +> cosmos-sdk is now a load-bearing dependency (D-055 GRILL-approved +> controlled exception to G-006); go.mod gains cosmos-sdk v0.50.x + +> ibc-go v8.x (A-504, planner/GRILL confirms the exact pin). ## Active Roster | Persona | Active | Phase-specific | Territory | |---------|--------|-----------------|-----------| -| backend-engineer | yes | no (all phases) | `lexicon/**`, `lexicon_meta*`, `x/hub/types`, `x/bond/types`, `x/council/types` | +| backend-engineer | yes | no (all runtime phases P1..P7) | `x/{exit,bridge,bearers,partner,hub,services,bond,council}/**`, `lexicon*` | | lead-developer | yes | no (all phases) | `.ciagent/**`, `.gitea/workflows/**` | +| security-engineer | yes | no (all runtime phases) | `x/hub` keyring, `x/bond` keeper, `x/bridge` keeper, `x/council` keeper, `lexicon/**` | +| cosmos-engineer | yes | no (all runtime phases) | `keeper/**`, `types/msg_*.go`, `types/expected_keepers.go`, `module.go` across all 8 target modules | +| mesh-engineer | yes | yes (P2 only) | `x/bearers/keeper/**`, `x/bearers/types/msg_bearer*.go`, `x/bearers/simtest/**` | +| data-engineer | yes | yes (P4 only) | `x/hub/keeper/keyring_mem*.go`, `x/hub/keeper/custody_state*.go` | ## Phase-Persona Matrix | Phase | Personas | Work | |-------|----------|------| -| P0 (pre-execution) | lead-developer | spec/clarify/research/plan/grill/mvp-ux + ship | -| P1 (lexicon hardening) | backend-engineer | REQ-029 shared helper + REQ-030 cross-const test | -| P2 (lifecycle divergence) | backend-engineer (regression-guard test) + lead-developer (ARCHITECTURE.md docs) | REQ-031 | -| P3 (docs build CI) | lead-developer | REQ-032 `.gitea/workflows/docs-build.yml` | -| P4 (final review/ship) | lead-developer + ci-security-auditor (audit) | review + NFR purity gate + milestone ship | - -## D-001 Refinement-Only Filter (governs all v0.4 work) - -Every v0.4 change must pass the D-001 filter: -- **Accept**: refactor, test, docs, chore, perf, fix, quality, coverage, architecture (drift fix only), improvement (of existing). -- **Reject**: any `add_requirement` + `feat:`-class signal (new capability, new enum type, new production type, new CLI, new distribution channel, new backend). -- **Enforcement**: lead-developer reviews each phase's commit set; the final-phase audit runs the NFR purity gate (`git log --grep "^feat:" --all-match` on the milestone range must return zero). +| P0 (pre-execution) | lead-developer (spec/clarify/research/plan/grill/mvp-ux + ship) | this file + RESEARCH.md + ARCHITECTURE.md v0.5 sections; planner-escalation items surfaced | +| P1 (exit + bridge runtime) | backend-engineer + cosmos-engineer + security-engineer | REQ-033: `x/exit` DEX swap routing + `x/bridge` L2↔L1 IBC packet handlers (5 L2 chains, D-059); ibc-go IBCModule contract; Solana wormhole-adapter branch; replay/timeout simtest | +| P2 (bearers transport runtime) | backend-engineer + cosmos-engineer + mesh-engineer (phase-specific) | REQ-034: OY-SAT + OY-QR message handlers; session lifecycle; OY-QR one-shot consumed-before-transfer; surveillance-resistant invariant | +| P3 (anchors onboarding runtime) | backend-engineer + cosmos-engineer + security-engineer | REQ-035: `x/partner` Anchor credential issuance + revocation handlers; Watcher-quorum authz via expected-keeper shim; P3→P4 hub dep broken by HubKeeper interface shim | +| P4 (hub API B2B runtime) | backend-engineer + cosmos-engineer + security-engineer + data-engineer (phase-specific) | REQ-036: custody/lending/compliance handlers; CustodyKeyring interface + memKeyring (D-058); lending coupon clamp [0,800]; compliance-before-custody ordering; lexicon (avoid 'deposit' in Msg names) | +| P5 (services runtime) | backend-engineer + cosmos-engineer | REQ-037: Care/SIM/Vault/Mail service lifecycle handlers; per-kind Msg* (typed dispatch); window-grant checked on every op | +| P6 (bond market runtime) | backend-engineer + cosmos-engineer + security-engineer | REQ-038: Growth Bond issuance + secondary-market CLOB matching (D-057); per-match coupon clamp [0,800] (A-562 reject-above-cap, planner confirms); price-time priority FCFS (REQ-007); no AMM | +| P7 (council governance runtime) | backend-engineer + cosmos-engineer + security-engineer | REQ-039: Proposal/VoteOption enums (AUDIT §193 P1-1); Voice lifecycle handlers; MissionLockAmendment-Rejected rejected at ValidateBasic (A-572); Watcher Veto quorum (A-574, default 6); SignalKind stays 4 | +| P8 (final review/audit/ship) | lead-developer + ci-security-auditor (activated) | feature purity gate audit; locked-const firewall verification; G-003 + G-006 (D-055 exception) verification; milestone ship | ## Constraints Carried Forward -- **G-003** by-ID-string rule: preserved in PRODUCTION imports. Test-only cross-package imports are EXEMPT (REQ-030 relies on this exemption — `x/hub/types/cross_const_test.go` imports `x/bond/types` in a `_test.go` file only). -- **G-006** zero Go deps: `go.mod` stays zero-require. The CI workflow (REQ-032) may use build-only Python deps (mkdocs + mkdocs-material) in a separate CI job; this does not touch `go.mod`. -- **G-014** lexicon shared helper: REQ-029 closes the G-014 drift risk by adding `lexicon.SyntheticBannedStrings()` as the single source for the synthetic self-test table consumed by BOTH meta-tests. -- **Locked consts unchanged**: `SignalKindCount==4`, `LendingCouponCapBps==800`, `LendingCouponFloorBps==0`, `CouponCapBps==800`, `CouponFloorBps==0` — v0.4 does NOT change any locked const. REQ-030 asserts they stay in lockstep; REQ-031 asserts the 4-signal shape is intentional. +- **G-003 production firewall intact**: keeper-to-keeper cross-module calls use `expected_keepers.go` interface shims (ibc-go convention), NOT struct imports of `x//types`. The by-ID-string rule is preserved at the type level. Test-only cross-package imports remain exempt (the G-003 test exemption, used by REQ-030 in v0.4; simtest may import multiple `x/*/keeper` packages to wire shims). +- **G-006 controlled exception (D-055)**: `go.mod` gains `cosmos-sdk v0.50.x` + `ibc-go v8.x` (GRILL-ratified). Scoped to runtime phases P1..P7; P0 + P8 stay dep-neutral where possible. `types/` packages gain `sdk.Msg` imports for `Msg*` types (isolated in `types/msg_*.go`); invariant/lexicon tests stay stdlib-only and green. Exact version pin is A-504 (planner/GRILL confirms). +- **Locked-const invariants unchanged**: v0.5 ADDS `ProposalKind` (4) / `ProposalStatus` (5) / `VoteOption` (4) enums to `x/council/types` (AUDIT §193 P1-1 promotion, D-060) but does NOT change existing locked consts — `CouponCapBps=800` / `CouponFloorBps=0` (D-028), `BearerTypeCount=6`, `PartnerTierCount=4`, `MissionLockAmendable=false`, `SignalKindCount=4` (P1-2 defensible; v0.4 `TestSignalKindShapeIntentional` stays green), `BridgeStatusCount=4`, `ExitStatusCount=5`, `ServiceKindCount=4`, `HubServiceCount=3`, `CouncilKindCount=3`, etc. The REQ-030 cross-const test (`hub.LendingCouponCapBps==bond.CouponCapBps`) stays green. +- **Lexicon firewall stays green**: the `lexicon_meta_test.go` (x/**/*.go) + `lexicon_meta_docs_test.go` (docs) automatically cover the new `keeper/`, `msg_server.go`, `simtest/` files. The new `Msg*` struct names are the lexicon surface — AVOID "deposit" in `x/hub` custody message names (use `MsgCustodyReceiveAsset`/`MsgCustodyReleaseAsset`, A-542); "coupon" not "interest"/"yield" in `x/bond`; "session"/"frame" safe in `x/bearers`; "veto" safe in `x/council`. Per-module lexicon assertions added to each new `keeper/` package. +- **Simtest NOT mainnet (D-054)**: handlers exercised against in-memory `sdk.Context` + dbm in-memory store; no real IBC light clients, no real MPC, no real bearer hardware, no real DEX venues, no real Watcher attestations (all stubbed). The simtest does NOT assert front-running safety (out of scope for simtest-grade runtime; the CLOB handler is documented as NOT front-running-safe for mainnet, a Year-3+ concern). +- **≥80% coverage on runtime packages (D-033 carries forward)**: every `keeper/msg_server.go` + simtest must hit the bar; table-driven handler tests per `Msg*`. + +## Planner-Escalation Items (low-confidence assumptions, surfaced through the normal decision flow) + +These are NOT auto-decided; the planner must resolve them before the corresponding phase lands: + +1. **A-504** — cosmos-sdk / ibc-go version pin (proposed: cosmos-sdk v0.50.x + ibc-go v8.x; alternative: ibc-go v10 IBC-v2/Eureka). GRILL review. Confidence 0.78. +2. **A-562** — bond CLOB match above 800 bps: REJECT (fails closed, proposed) vs CLAMP-with-refund (D-057 says "clamp"). Resolve before P6. Confidence 0.70. +3. **A-572** — `MissionLockAmendment-Rejected` ProposalKind: reject at `ValidateBasic` (proposed, the message never reaches the handler) vs propose-then-fail (record Pending → auto-transition Failed with event). Resolve before P7. Confidence 0.80. +4. **A-574** — Watcher Veto quorum value (proposed default: 6, matching REQ-004 6-of-9). Resolve before P7. Confidence 0.75. ## Removal Notes -- frontend-engineer and docs-writer were `removed_after: P3` in v0.3. They are formally deactivated here for v0.4 (not just phase-removed) because v0.4 has no docs-content phase at all. -- No phase-specific personas are created for v0.4. The roster is stable across all phases. \ No newline at end of file +- frontend-engineer and docs-writer were deactivated in v0.4 (no docs-content phase); they remain deactivated in v0.5 for the same reason (the docs site is complete from v0.3; the docs build CI is complete from v0.4). They will reactivate in v0.6+ if docs content is restructured or i18n is added. +- cosmos-engineer, security-engineer, and mesh-engineer were deactivated in v0.3/v0.4 (lower Cosmos-convention / invariant density, no bearer hardware runtime); they are REACTIVATED in v0.5 because cosmos-sdk is now load-bearing (D-055), the runtime introduces new security-critical invariant surfaces (CustodyKeyring, CLOB clamp, IBC replay, Mission-Lock const firewall), and the bearer transport gets live handlers (P2). +- data-engineer is reactivated as a P4-phase-specific persona (hub custody state, in-memory test store only) and removed after P4. This mirrors the v0.3 genesis-schema pattern but scoped narrowly to the P4 custody store. +- ci-security-auditor is default off; activate in P8 for the final audit + feature purity gate. \ No newline at end of file diff --git a/.ciagent/oy/PLANS.md b/.ciagent/oy/PLANS.md index 8df6a07..6e6f9ec 100644 --- a/.ciagent/oy/PLANS.md +++ b/.ciagent/oy/PLANS.md @@ -1043,4 +1043,646 @@ The v0.4 deliverable MUST meet these explicit criteria (verified in P4 audit): 3. **REQ-031**: ARCHITECTURE.md has the Council lifecycle divergence subsection; `TestSignalKindShapeIntentional` in `x/council/types/types_test.go` passes and documents the 4-signal rationale; `SignalKindCount` unchanged (still 4); no production `.go` files modified in P2. 4. **REQ-032**: `.gitea/workflows/docs-build.yml` parses; runs `go test ./...` then `mkdocs build` (G-016: `docs-build` needs `go-test`); `go.mod` unchanged. 5. **NFR purity gate**: zero `feat:` commits in the v0.4 milestone range (P4 audit enforces). -6. **No regression**: `go test ./...` green; v0.3 coverage floor (93.3%) not reduced on any modified package. \ No newline at end of file +6. **No regression**: `go test ./...` green; v0.3 coverage floor (93.3%) not reduced on any modified package. + +--- + +# Plans: OpenYield (oy) — v0.5 (Bearers Runtime) + +> This section APPENDS the v0.5 milestone plan to the v0.1/v0.2/v0.3/v0.4 +> plans above. It does NOT rewrite or supersede the earlier content. v0.5 is +> the first **feature** milestone to ship executable behavior beyond +> invariant tests: the v0.3 Bearers skeletons (`x/exit`, `x/bridge`, +> `x/bearers`, `x/partner`, `x/hub`, `x/services`, `x/bond`, plus the +> cross-cutting `x/council`) are promoted from **types + in-memory keeper +> stubs + invariant tests** to **live keeper MsgServer message handlers + +> simtest-grade end-to-end flows** (D-054). This is NOT mainnet (D-020 +> continues to govern network deployment; D-054 ratifies runtime = +> simtest-grade handlers, not live chain). Tags run on the `v0.4.x` patch +> line (config.json `tag_base: v0.4.x`): P0 → `v0.4.0`; execution phases +> `v0.4.1..v0.4.7`; final phase P8 → `v0.4.8` IS the v0.5 milestone release +> (D-008 — final phase patch IS the milestone release; no separate minor +> tag). Branch names use NO `oy/` prefix (single-project mode: only `oy` +> exists; the slug prefix would be redundant — config `projects[]` length +> is 1). + +## Milestone Summary + +- **Milestone**: v0.5 — Bearers Runtime +- **Type**: Feature (all execution phases P1..P7 are `feat`; P8 is `final`) +- **Tag base**: `v0.4.x` patch line (P0 → `v0.4.0`; execution P1..P7 → `v0.4.1..v0.4.7`; final P8 → `v0.4.8` IS the v0.5 milestone release) +- **Phases**: 8 — P1..P7 (execution) + P8 (final review/audit/ship). Phase 0 (this PLAN) is in progress. +- **Depth**: runtime promotion (keeper MsgServer handlers + simtest-grade end-to-end flows, NOT mainnet — D-054/D-020 continues). The v0.3 skeleton `types/` contracts are NOT amended; runtime adds behavior on top. +- **Coverage target**: ≥80% on each runtime package (keeper + simtest; D-033 carries forward). +- **Modules promoted to runtime**: 8 (`x/exit`, `x/bridge`, `x/bearers`, `x/partner`, `x/hub`, `x/services`, `x/bond`, `x/council`). New enum types: 3 in `x/council` (`ProposalKind`, `ProposalStatus`, `VoteOption`) + `Proposal` struct (AUDIT §193 P1-1). New interface: `CustodyKeyring` (D-058). New dep: cosmos-sdk v0.50.x + ibc-go v8.x (D-055, GRILL-ratified G-006 controlled exception). +- **Phase ordering** (D-056): P1 exit+bridge → P2 bearers → P3 anchors → P4 hub → P5 services → P6 bond → P7 council → P8 final. Outer→inner dependency chain (fewest internal deps first; each phase independently shippable). +- **Personas** (from PERSONAS.md): backend-engineer (all P1..P7 handler + simtest work; spans all runtime phases), lead-developer (P0 + P8 + coordination + GRILL-ratification follow-through), security-engineer (CustodyKeyring D-058, CLOB clamp D-057, IBC replay, Mission-Lock const firewall — REACTIVATED), cosmos-engineer (MsgServer/expected-keepers/simtest scaffolding, cosmos-sdk dep D-055 — REACTIVATED), mesh-engineer (P2 bearer session lifecycle — REACTIVATED, P2 phase-specific), data-engineer (P4 hub custody state, in-memory test store — REACTIVATED, P4 phase-specific). ci-security-auditor activated in P8. + +### Cross-Phase Dependency Map (v0.5) + +``` +P1 (exit + bridge runtime) [outermost edge, fewest internal deps] + │ x/exit ──(BridgeKeeper interface)──► x/bridge [intra-P1; bridge keeper satisfies x/exit expected-keeper shim] + │ x/bridge ──(WatcherKeeper interface)──► x/watcher [v0.1 baseline; Attested transition + Solana adapter authz] + │ x/bridge ──(BreadKeeper interface)──► x/bread [v0.1 baseline; mint/release wrapped Bread on recv/timeout] + ▼ +P2 (bearers transport runtime) [routes through exit for off-mesh routing] + │ x/bearers ──(BreadKeeper interface)──► x/bread [OY-QR consume transfer effect] + ▼ +P3 (anchors onboarding runtime) [rides bearers for transport] + │ x/partner ──(WatcherKeeper interface)──► x/watcher [revocation authz, 6-of-9 quorum] + │ x/partner ──(HubKeeper interface)──► x/hub [P3→P4 hub dep BROKEN by expected-keeper shim; interface in P3, impl wired in P4] + ▼ +P4 (hub API B2B runtime) [custody backs anchors] + │ x/hub ──(PartnerKeeper interface)──► x/partner [operator must be Onboarded Anchor] + │ x/hub ──(lexicon-safe local consts)──► x/bond [LendingCouponCapBps/Floor cross-documented D-028/REQ-030, no struct import] + ▼ +P5 (services runtime) [sits on hub] + │ x/services ──(WindowKeeper interface)──► x/window [window-grant validity on every op] + │ x/services ──(VaultKeeper interface)──► x/vault [VaultService provisioning] + ▼ +P6 (bond market runtime) [uses hub lending primitive] + │ x/bond ──(StandKeeper interface)──► x/stand [GrowthBond issuer-stand-id] + ▼ +P7 (council governance runtime) [cross-cutting, lands last] + │ x/council ──(WatcherKeeper interface)──► x/watcher [Veto authz + quorum] + ▼ +P8 (final review/audit/ship) +``` + +**G-003 firewall (survives runtime promotion):** keepers use `expected_keepers.go` interface shims (ibc-go convention) for cross-module keeper calls — NO production struct imports across `x//types`. The P3→P4 hub dependency is broken this way (hub keeper INTERFACE exists in `x/partner/types/expected_keepers.go` in P3 territory; the hub keeper IMPL is wired in P4). Test-only cross-package imports remain exempt (the G-003 test exemption, used by REQ-030 in v0.4; simtest may import multiple `x/*/keeper` packages to wire shims). + +**G-006 controlled exception (D-055, GRILL-ratified):** `go.mod` gains `cosmos-sdk v0.50.x` + `ibc-go v8.x` (A-504, planner confirms the exact pin). Scoped to runtime phases P1..P7; P0 + P8 stay dep-neutral where possible. `types/` packages gain `sdk.Msg` imports for `Msg*` types (isolated in `types/msg_*.go`); invariant/lexicon tests stay stdlib-only and green. + +### Planner-Escalation Decisions (provisional, GRILL ratifies) + +The 4 planner-escalation items from RESEARCH §4 (low-confidence assumptions) are resolved here as provisional planner decisions D-062..D-065 (continuing the decision ID sequence from D-061). They are PROVISIONAL until the GRILL stage ratifies them. + +| ID | Decision | Rationale | Confidence | Alternatives | +|----|----------|-----------|------------|--------------| +| D-062 | **cosmos-sdk / ibc-go version pin = cosmos-sdk v0.50.x + ibc-go v8.x** (resolves A-504). v0.50.x is the LTS line (go 1.22-compatible); ibc-go v8.x is the stable pairing for cosmos-sdk v0.50. ibc-go v10 (IBC v2 / Eureka) is the documented target pattern but a newer pin — defer to a later upgrade. Mark GRILL-confirmed. | v0.50.x + ibc-go v8.x is the stable, widely-deployed pairing (Osmosis, dYdX-v4 lineage); v10 IBC-v2 is attractive but newer and risks churn in a runtime-promotion milestone. The IBC v2 patterns are documented in RESEARCH but the v0.5 impl uses the v8 stable interfaces. | 0.78 | [cosmos-sdk v0.50.x + ibc-go v10 (IBC v2/Eureka, newer); cosmos-sdk v0.47.x + ibc-go v7.x (older LTS)] | +| D-063 | **Bond CLOB match above 800 bps = REJECT (fails closed)**, NOT clamp-with-refund (resolves A-562). D-057 says "hard clamp on each match"; the runtime interpretation is reject-above-cap. A match whose implied coupon exceeds `CouponCapBps=800` bps is REJECTED — the trade fails closed, the resting order stays, the incoming order rests or is cancelled. The mission-lock is a hard invariant (a usury violation), not a soft cap to be clamped with a refund path. Matches within [0, 800] bps use the v0.3 `Clamp` helper (clamp-within-band is safe — no refund needed since the value is already in-band). | Reject is simpler (no refund path) and is the mission-lock-true choice: a trade above the cap is a usury violation, not a clampable excess. The Fee Covenant `Clamp` shape (clamp, not reject) applies to ISSUANCE (a coupon field set by the issuer), but MATCHING is a market-determined price — a match above the cap is a violation, not an input to clamp. REJECT fails closed (the safer choice for the highest-severity locked const). | 0.70 | [clamp-with-refund (the match clears at 800, excess refunded to seller) — adds a refund path, softens the mission-lock] | +| D-064 | **`MissionLockAmendment-Rejected` ProposalKind rejected at `ValidateBasic`** (resolves A-572). The `MsgSubmitProposal` `ValidateBasic` REJECTS a proposal of kind `MissionLockAmendment-Rejected` — the message never reaches the handler. The const `MissionLockAmendable = false` (v0.2 locked) is the firewall; the `ValidateBasic` is the gate. The proposal is unproposable, not propose-then-fail. The v0.2 `TestMissionLockAmendableFalse` regression test stays green. | The const is the firewall; the `ValidateBasic` gate is the dual firewall. Rejecting at `ValidateBasic` is the cleanest: the message never enters the keeper, no state record is created, no event is emitted. Propose-then-fail (record Pending → auto-transition Failed with event) would document the rejection on-chain but creates a state record for an unproposable proposal — unnecessary state growth. The Mission-Lock-non-amendable design intent is "unproposable", not "propose-then-fail". | 0.80 | [propose-then-fail (record Pending, auto-transition Failed with "Mission Lock non-amendable" event) — documents the rejection on-chain but creates dead state] | +| D-065 | **Watcher Veto quorum default = 6** (resolves A-574), matching REQ-004's 6-of-9 Watcher quorum. A single Veto does NOT block (anti-greed, vision §19); the proposal transitions to Failed only if `NoWithVeto >= WatcherVetoQuorum` (a `Params` field, NOT a locked const — the v0.2 Params struct was empty, v0.5 P7 adds `WatcherVetoQuorum` defaulting to 6). The quorum is a param (governance-tunable in a future milestone) rather than a locked const, to allow adjustment without a locked-const amendment. | REQ-004 fixes the Watcher quorum at 6-of-9; the Veto quorum mirrors it (a Watcher-coordinated veto requires the same quorum as a Watcher attestation). A single Veto blocking would violate the anti-greed principle (vision §19 — no single-actor veto gate). Defaulting to 6 (not locking as a const) lets a future governance vote adjust the quorum without a Mission-Lock-class amendment (Veto quorum is NOT a Mission-Lock const; the distinction is documented in v0.4 ARCHITECTURE.md). | 0.75 | [lock WatcherVetoQuorum=6 as a const (over-rigid; prevents future tuning); default 9 (requires all Watchers, too high a bar for a veto)] | + +> These 4 decisions are surfaced through the normal decision flow (planner → +> GRILL ratification). They are NOT auto-decided (the autonomy threshold for +> locked-const-shape and dep-pinning decisions is at the GRILL boundary per +> PERSONAS.md lead-developer constraints). The GRILL stage may ratify, amend, +> or reject them; if rejected, the planner re-resolves before the affected +> phase lands (D-062 before P1; D-063 before P6; D-064/D-065 before P7). + +--- + +## Phase P1 — Exit + Bridge Runtime + +- **Slug**: `exit-bridge-runtime` +- **Branch**: `phase/01-exit-bridge-runtime` +- **REQs covered**: REQ-033 (Exit layer runtime — `x/exit` DEX swap routing + `x/bridge` L2↔L1 IBC packet handlers for the 5 locked L2 chains per D-059) +- **Tag**: `v0.4.1` +- **Type**: `feat` +- **Goal**: Promote `x/exit` and `x/bridge` from v0.3 skeleton types to runtime: `x/exit` MsgServer (DEX swap routing handlers driving the `ExitStatus` lifecycle) + `x/bridge` MsgServer (IBC packet recv/ack/timeout for the 5 L2 chains, Solana via wormhole-adapter) + expected-keeper shims + simtest. This is the outermost edge (fewest internal deps); ships first per D-056. + +### Wave 1 — cosmos-sdk dep + x/bridge keeper + MsgServer (blocked-by D-062 GRILL) + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P1-01-01 | REQ-033, D-055 | cosmos-engineer | `go.mod`, `go.sum` | Add cosmos-sdk v0.50.x + ibc-go v8.x (D-062 pin). Run `go mod tidy`. Confirm the dep tree resolves under go 1.22. | `go build ./...` succeeds with the new deps; `go.mod` has require lines for cosmos-sdk + ibc-go; `go version` compatible (go 1.22+). | (D-062 GRILL ratification) | +| P1-02-01 | REQ-033 | cosmos-engineer | `x/bridge/types/msg_*.go`, `x/bridge/types/expected_keepers.go` | New `Msg*` types implementing `sdk.Msg`: `MsgAttestBridgeRoute` (Watcher-quorum-driven transition Pending→Attested, references `x/watcher` by ID via `WatcherKeeper` expected-keeper shim), `MsgActivateBridge`, `MsgCloseBridge`. `ValidateBasic` (stateless: non-empty route-id, valid status transition target) + `GetSigners`. `expected_keepers.go`: `WatcherKeeper` interface (methods `x/bridge` handler calls — e.g., `IsQuorumSigned(quorumID string, payload []byte) bool`), `BreadKeeper` interface (`MintWrappedBread`, `ReleaseWrappedBread` by ID-string). NO struct import of `x/watcher/types` or `x/bread/types` (G-003 intact). | `go build ./x/bridge/...` succeeds; `Msg*` implement `sdk.Msg` (ValidateBasic + GetSigners); `expected_keepers.go` defines INTERFACES only (no struct imports); lexicon green on new files. | P1-01-01 | +| P1-03-01 | REQ-033 | cosmos-engineer + backend-engineer | `x/bridge/keeper/keeper.go`, `x/bridge/keeper/msg_server.go`, `x/bridge/keeper/ibc_module.go`, `x/bridge/module.go` | Store-backed `Keeper` (wraps `sdk.KVStore` via `storeKey`); replaces the v0.3 in-memory stub (the stub may stay as a test helper). `MsgServer` struct wrapping the Keeper + expected-keeper shims; one `*Response, error` method per `Msg*`: `AttestBridgeRoute`, `ActivateBridge`, `CloseBridge`. IBC `IBCModule` contract: `OnRecvPacket` (parse ICS-20 v1 payload: denom, amount, sender, receiver; validate denom trace against v0.2 `WrappedBreadDenom` shape `transfer/channel-N/`; mint wrapped Bread via `BreadKeeper` shim; 4 EVM chains use timestamp-only timeouts, Solana branch verifies wormhole guardian sig set 2-of-N from state), `OnAcknowledgementPacket` (delete in-flight record on first ack — replay protection mirroring ibc-go; reject on second), `OnTimeoutPacket` (refund source-chain escrow via `BreadKeeper` shim exactly once). `module.go`: `AppModule` + `RegisterServices` registering the `MsgServer`. Handler state-machine ordering: ValidateBasic → keeper authz → state mutation → `ctx.EventManager().EmitEvent`. | `go build ./x/bridge/...` succeeds; `MsgServer` methods return `(*Response, error)`; IBC handlers implement the `OnRecvPacket`/`OnAcknowledgementPacket`/`OnTimeoutPacket` contract; replay protection (delete-on-ack) + timeout refund logic present; lexicon green. | P1-02-01 | + +### Wave 2 — x/exit keeper + MsgServer (blocked-by Wave 1 bridge for the BridgeKeeper shim) + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P1-04-01 | REQ-033 | cosmos-engineer | `x/exit/types/msg_*.go`, `x/exit/types/expected_keepers.go` | New `Msg*` types: `MsgSubmitExitRoute` (proposes an ExitRoute; `ValidateBasic`: non-empty holder-reach-id, source/dest-asset, amount > 0), `MsgExecuteDEXSwap` (executes the pre-computed venue-hops; `ValidateBasic`: non-empty route-id, route status == InProgress-or-Proposed), `MsgRefundExit` (on Failed; `ValidateBasic`: non-empty route-id, status == Failed). `expected_keepers.go`: `BridgeKeeper` interface (methods `x/exit` handler calls for cross-chain exits — e.g., `GetBridgeRoute(routeID string) (status, bridgeType, err)`). NO struct import of `x/bridge/types` (G-003 intact — the interface is defined in `x/exit/types`). `bridge-route-id` field stays a by-ID-string at the type level (G-003). | `go build ./x/exit/...` succeeds; `Msg*` implement `sdk.Msg`; `expected_keepers.go` defines `BridgeKeeper` INTERFACE (no struct import of `x/bridge/types`); lexicon green. | P1-01-01 | +| P1-05-01 | REQ-033 | cosmos-engineer + backend-engineer | `x/exit/keeper/keeper.go`, `x/exit/keeper/msg_server.go`, `x/exit/module.go` | Store-backed `Keeper`. `MsgServer`: `SubmitExitRoute` (creates ExitRoute status=Proposed), `ExecuteDEXSwap` (transition Proposed→InProgress→Settled/Failed; cross-chain exits invoke `BridgeKeeper` shim by ID; produces a `DEXSwap` record; Fee Covenant clamp invoked on `exit-fee-bps` at runtime per v0.5 interface extension), `RefundExit` (Failed→Refunded). `module.go`: `AppModule` + `RegisterServices`. State-machine ordering enforced. | `go build ./x/exit/...` succeeds; `MsgServer` methods present; cross-chain exit path uses the `BridgeKeeper` shim (no `x/bridge` struct import); Fee Covenant clamp invoked on exit-fee-bps; lexicon green. | P1-04-01, P1-03-01 | + +### Wave 3 — Simtest (blocked-by Wave 1 + 2) + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P1-06-01 | REQ-033 | cosmos-engineer + security-engineer | `x/exit/keeper/msg_server_simtest_test.go`, `x/bridge/keeper/msg_server_simtest_test.go` | Simtest (in-memory `sdk.Context` + dbm in-memory store; no real IBC light clients — D-054). `x/exit` simtest: full ExitStatus lifecycle (Proposed→InProgress→Settled; Failed→Refunded); cross-chain exit invokes the `BridgeKeeper` shim (wired to the real `x/bridge` keeper in the test setup — G-003 test exemption); Fee Covenant clamp event asserted; replay rejection (duplicate `MsgExecuteDEXSwap` on a Settled route is a no-op error). `x/bridge` simtest: `OnRecvPacket` mints wrapped Bread (assert `BreadKeeper.MintWrappedBread` called); `OnAcknowledgementPacket` deletes the in-flight record (first ack) and rejects the second (REPLAY PROTECTION — CVE-class pitfall, A-513); `OnTimeoutPacket` refunds the escrow exactly once (TIMEOUT-REFUND — second timeout is a no-op); Solana branch verifies a stub guardian sig set (2-of-N); denom trace parser pinned to ICS-20 v1 `transfer/channel-N/`. Coverage ≥80% on `x/exit/keeper` + `x/bridge/keeper`. | `go test ./x/exit/... ./x/bridge/...` passes; simtest covers lifecycle + replay + timeout-refund; coverage ≥80% on both keeper packages; lexicon green; G-003 import-invariant green (no production struct imports across x//types). | P1-05-01 | + +### Wave 4 — Phase verification + ship + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P1-99-01 | REQ-012, REQ-033 | lead-developer | (cross-cutting) | `go build ./...` + `go test ./...` green (incl. all v0.1..v0.4 baseline + P1 runtime); coverage ≥80% on `x/exit/keeper`, `x/bridge/keeper`; lexicon firewall (`lexicon_meta_test.go` + `lexicon_meta_docs_test.go`) green on new `keeper/` + `msg_*.go` + `module.go` files; G-003 import-invariant green; go.mod has cosmos-sdk + ibc-go (D-062 pin); tag `v0.4.1`. | `go test ./...` green; coverage ≥80% on both P1 keeper packages; both lexicon firewalls green; G-003 green; git tag `v0.4.1` created. | P1-06-01 | + +### P1 Must-Haves +- [ ] `go.mod` has cosmos-sdk v0.50.x + ibc-go v8.x (D-062 pin; D-055 G-006 controlled exception). +- [ ] `x/exit` + `x/bridge` each have `keeper/keeper.go` + `keeper/msg_server.go` + `types/msg_*.go` + `types/expected_keepers.go` + `module.go`. +- [ ] `go build ./...` and `go test ./...` green — including all v0.1..v0.4 baseline (no regression). +- [ ] ≥80% coverage on `x/exit/keeper`, `x/bridge/keeper`. +- [ ] `x/exit` `bridge-route-id` is by-ID-string (G-003 type-level); cross-chain exit uses `BridgeKeeper` expected-keeper shim (G-003 runtime-level — no `x/bridge/types` struct import in production `x/exit` code). +- [ ] `x/bridge` IBC handlers implement `OnRecvPacket`/`OnAcknowledgementPacket`/`OnTimeoutPacket`; Solana via wormhole-adapter branch. +- [ ] IBC replay protection (delete-on-ack, reject-on-second) + timeout-refund (exactly once) covered by simtest (A-513). +- [ ] Lexicon firewall green on new files; G-003 import-invariant green. +- [ ] Locked-consts unchanged: `ExitStatusCount=5`, `BridgeStatusCount=4`. +- [ ] Git tag `v0.4.1`. + +### P1 Risks & Mitigations +- **IBC ack/timeout handling (CVE-class pitfall, A-513)** → simtest MUST cover both replay (second ack rejected) and timeout-refund (exactly-once refund). security-engineer reviews the simtest. +- **Solana guardian sig set rotation** → handler reads CURRENT set from state, not hardcoded; simtest uses a frozen stub set; rotation test deferred (D-054). +- **Denom trace drift** → parser pinned to ICS-20 v1 `transfer/channel-N/` shape (v0.2 satellite); simtest asserts the trace parse. +- **cosmos-sdk version pin churn (D-062)** → v0.50.x + ibc-go v8.x is the stable choice; GRILL ratifies before P1 ships. + +--- + +## Phase P2 — Bearers Transport Runtime + +- **Slug**: `bearers-transport-runtime` +- **Branch**: `phase/02-bearers-transport-runtime` +- **REQs covered**: REQ-034 (Bearers transport runtime — `x/bearers` OY-SAT + OY-QR message handlers + session lifecycle) +- **Tag**: `v0.4.2` +- **Type**: `feat` +- **Goal**: Promote `x/bearers` from v0.3 skeleton types to runtime: `MsgSendOYSATFrame` / `MsgReceiveOYSATFrame` / `MsgIssueOYQR` / `MsgConsumeOYQR` + session lifecycle (Open/Active/Closed/Revoked) + store-backed `BearerTransport` impl + simtest. mesh-engineer leads (P2 phase-specific). + +### Wave 1 — x/bearers types + keeper + MsgServer + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P2-01-01 | REQ-034 | cosmos-engineer + mesh-engineer | `x/bearers/types/msg_bearer*.go`, `x/bearers/types/expected_keepers.go`, `x/bearers/types/session.go` | New `Msg*` types: `MsgSendOYSATFrame` (`ValidateBasic`: non-empty session-id, frame payload), `MsgReceiveOYSATFrame`, `MsgIssueOYQR` (`ValidateBasic`: non-empty issuer-reach-id, payload, expires-at > now), `MsgConsumeOYQR` (`ValidateBasic`: non-empty qr-id, consumer-reach-id), `MsgOpenSession`, `MsgCloseSession`, `MsgRevokeSession`. New `Session` struct (session-id, bearer-type, initiator-reach, peer-reach, status [Open/Active/Closed/Revoked], frames, ttl, opened-at, closed-at). `expected_keepers.go`: `BreadKeeper` interface (for OY-QR consume transfer effect — `TransferGrain(fromReach, toReach string, amount int64) error`). NO struct import of `x/bread/types` (G-003 intact). | `go build ./x/bearers/...` succeeds; `Msg*` implement `sdk.Msg`; `Session` struct present; `expected_keepers.go` defines `BreadKeeper` INTERFACE; lexicon green. | P1-99-01 | +| P2-02-01 | REQ-034 | cosmos-engineer + mesh-engineer | `x/bearers/keeper/keeper.go`, `x/bearers/keeper/msg_server.go`, `x/bearers/keeper/transport.go`, `x/bearers/module.go` | Store-backed `Keeper`. `MsgServer`: session lifecycle handlers (Open→Active on first frame ack→Closed on last frame or ttl expiry→Revoked out-of-band; frames received on Closed/Revoked are REJECTED). `MsgConsumeOYQR` is the canonical one-shot handler: load QR → assert `!consumed` → assert `expires-at > now` → FLIP `consumed=true` (state write FIRST — A-521) → emit transfer effect via `BreadKeeper` shim → emit event → return. A replay finds `consumed==true` and returns error (idempotent reject, NOT double-effect). `transport.go`: store-backed `BearerTransport` impl (the keeper IS the transport in simtest; `Send`/`Receive`/`Status` backed by the store; no hardware/RF dep — D-054). `module.go`: `AppModule` + `RegisterServices`. Surveillance-resistant invariant: handler emits NO geolocation / sender physical location (the `surveillance-resistant` locked const is a runtime invariant). | `go build ./x/bearers/...` succeeds; session lifecycle handlers present; `MsgConsumeOYQR` flips `consumed` BEFORE the transfer effect; store-backed `BearerTransport` impl present; no hardware Go libraries imported; lexicon green. | P2-01-01 | + +### Wave 2 — Simtest + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P2-03-01 | REQ-034 | mesh-engineer + security-engineer | `x/bearers/keeper/msg_server_simtest_test.go` | Simtest: full session lifecycle (Open→Active→Closed; Open→Active→Revoked; rejected-frame-on-Closed/Revoked); OY-QR one-shot (consume flips `consumed`, transfer effect via `BreadKeeper` shim; REPLAY finds `consumed==true` and returns error — A-521); OY-SAT frame send/receive round-trip; surveillance-resistant NEGATIVE test (assert the event set contains NO geolocation fields — A-522); `BearerTransport` store-backed impl round-trip. Coverage ≥80% on `x/bearers/keeper`. | `go test ./x/bearers/...` passes; simtest covers session lifecycle + OY-QR one-shot + replay rejection + surveillance-resistant negative test; coverage ≥80%; lexicon green; G-003 import-invariant green. | P2-02-01 | + +### Wave 3 — Phase verification + ship (mesh-engineer removed after P2) + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P2-99-01 | REQ-012, REQ-034 | lead-developer | (cross-cutting) | `go build ./...` + `go test ./...` green; coverage ≥80% on `x/bearers/keeper`; lexicon firewalls green; G-003 green; locked-const `BearerTypeCount=6` unchanged (regression); `OYSATLink.SurveillanceResistant=true` LOCKED (regression); tag `v0.4.2`. Remove mesh-engineer persona (P2 phase-specific). | `go test ./...` green; coverage ≥80%; both lexicon firewalls green; G-003 green; `BearerTypeCount=6` + `SurveillanceResistant=true` unchanged; git tag `v0.4.2`; mesh-engineer removed. | P2-03-01 | + +### P2 Must-Haves +- [ ] `x/bearers` has `keeper/keeper.go` + `keeper/msg_server.go` + `keeper/transport.go` + `types/msg_bearer*.go` + `types/session.go` + `types/expected_keepers.go` + `module.go`. +- [ ] `go build ./...` and `go test ./...` green (no regression). +- [ ] ≥80% coverage on `x/bearers/keeper`. +- [ ] OY-QR one-shot: `MsgConsumeOYQR` flips `consumed` BEFORE the transfer effect (A-521); replay rejected idempotently. +- [ ] Session lifecycle: Open→Active→Closed/Revoked; rejected-frame-on-Closed/Revoked. +- [ ] Surveillance-resistant negative test: event set has NO geolocation fields. +- [ ] `BearerTransport` store-backed impl (no hardware/RF Go libraries — D-054). +- [ ] Locked-consts unchanged: `BearerTypeCount=6`, `OYSATLink.SurveillanceResistant=true`. +- [ ] Lexicon firewall green; G-003 import-invariant green. +- [ ] Git tag `v0.4.2`. + +### P2 Risks & Mitigations +- **One-shot replay (A-521)** → `consumed` flip BEFORE transfer effect; simtest covers replay. +- **Surveillance-resistance runtime invariant** → negative test asserts no geolocation in events. +- **Session state machine ordering** → simtest covers all 4 transitions + rejected-frame case. + +--- + +## Phase P3 — Anchors Onboarding Runtime + +- **Slug**: `anchors-onboarding-runtime` +- **Branch**: `phase/03-anchors-onboarding-runtime` +- **REQs covered**: REQ-035 (Anchors onboarding runtime — `x/partner` Anchor credential issuance + revocation handlers) +- **Tag**: `v0.4.3` +- **Type**: `feat` +- **Goal**: Promote `x/partner` Anchor tier from v0.3 skeleton to runtime: `MsgIssueAnchorCredential` / `MsgOnboardAnchor` / `MsgSuspendAnchorCredential` / `MsgRevokeAnchorCredential` + credential lifecycle (Pending→Onboarded→Suspended→Revoked) + simtest. Depends on bearers (P2) for transport. The P3→P4 hub dependency is broken by the `HubKeeper` expected-keeper shim (interface in P3, impl wired in P4). + +### Wave 1 — x/partner types + keeper + MsgServer + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P3-01-01 | REQ-035 | cosmos-engineer | `x/partner/types/msg_anchor*.go`, `x/partner/types/expected_keepers.go` | New `Msg*` types: `MsgIssueAnchorCredential` (`ValidateBasic`: non-empty partner-id, partner must be Anchor tier — checked at handler via keeper, non-empty jurisdiction), `MsgOnboardAnchor` (`ValidateBasic`: non-empty partner-id, custody-provider-id, attestation-refs), `MsgSuspendAnchorCredential`, `MsgRevokeAnchorCredential` (`ValidateBasic`: non-empty partner-id). `expected_keepers.go`: `WatcherKeeper` interface (revocation authz — `IsQuorumSigned(quorumID string, payload []byte) bool`; 6-of-9 per REQ-004), `HubKeeper` interface (custody-provider-id validity — `CustodyServiceExists(serviceID string) bool`). NO struct import of `x/watcher/types` or `x/hub/types` (G-003 intact — the P3→P4 hub dep is broken here: the `HubKeeper` INTERFACE exists in P3 territory; the hub keeper IMPL is wired in P4). | `go build ./x/partner/...` succeeds; `Msg*` implement `sdk.Msg`; `expected_keepers.go` defines `WatcherKeeper` + `HubKeeper` INTERFACES (no struct imports); lexicon green. | P2-99-01 | +| P3-02-01 | REQ-035 | cosmos-engineer + backend-engineer | `x/partner/keeper/keeper.go`, `x/partner/keeper/msg_server.go`, `x/partner/module.go` | Store-backed `Keeper` (replaces the v0.3 in-memory `Keeper` stub in `types/types.go`; the stub may stay as a test helper). `MsgServer`: `IssueAnchorCredential` (credential starts Pending; issuer must be Watcher-authorized via `WatcherKeeper` shim), `OnboardAnchor` (Pending→Onboarded; asserts custody-provider-id references a live hub custody service via `HubKeeper` shim; attestation-refs populated), `SuspendAnchorCredential` (Onboarded→Suspended), `RevokeAnchorCredential` (→Revoked; only Watcher quorum or issuing party — authz via `WatcherKeeper` shim). `module.go`: `AppModule` + `RegisterServices`. | `go build ./x/partner/...` succeeds; `MsgServer` methods present; `HubKeeper` shim used for custody-provider-id validity (no `x/hub` struct import — G-003); `WatcherKeeper` shim for revocation authz; lexicon green. | P3-01-01 | + +### Wave 2 — Simtest + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P3-03-01 | REQ-035 | backend-engineer + security-engineer | `x/partner/keeper/msg_server_simtest_test.go` | Simtest: full credential lifecycle (Pending→Onboarded→Suspended→Revoked); `OnboardAnchor` asserts `HubKeeper.CustodyServiceExists` (wired to a stub hub keeper in the test setup — G-003 test exemption; the real hub keeper lands in P4); post-revocation rejection (a downstream custody action on a revoked credential returns `ErrCredentialRevoked`); revocation authz via `WatcherKeeper` shim (6-of-9 quorum check); non-Anchor partner rejection on `IssueAnchorCredential`. Coverage ≥80% on `x/partner/keeper`. | `go test ./x/partner/...` passes; simtest covers lifecycle + post-revocation rejection + authz; coverage ≥80%; lexicon green; G-003 import-invariant green. | P3-02-01 | + +### Wave 3 — Phase verification + ship + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P3-99-01 | REQ-012, REQ-035 | lead-developer | (cross-cutting) | `go build ./...` + `go test ./...` green; coverage ≥80% on `x/partner/keeper`; lexicon firewalls green; G-003 green; `PartnerTierCount=4` unchanged (regression); tag `v0.4.3`. | `go test ./...` green; coverage ≥80%; both lexicon firewalls green; G-003 green; `PartnerTierCount=4` unchanged; git tag `v0.4.3`. | P3-03-01 | + +### P3 Must-Haves +- [ ] `x/partner` has `keeper/keeper.go` + `keeper/msg_server.go` + `types/msg_anchor*.go` + `types/expected_keepers.go` + `module.go`. +- [ ] `go build ./...` and `go test ./...` green (no regression). +- [ ] ≥80% coverage on `x/partner/keeper`. +- [ ] Anchor credential lifecycle: Pending→Onboarded→Suspended→Revoked. +- [ ] P3→P4 hub dep broken by `HubKeeper` expected-keeper shim (interface in `x/partner/types/expected_keepers.go`; impl wired in P4). +- [ ] Revocation authz via `WatcherKeeper` shim (6-of-9 quorum, REQ-004). +- [ ] Post-revocation rejection (downstream custody action returns `ErrCredentialRevoked`). +- [ ] Locked-const `PartnerTierCount=4` unchanged. +- [ ] Lexicon firewall green; G-003 import-invariant green. +- [ ] Git tag `v0.4.3`. + +### P3 Risks & Mitigations +- **P3→P4 hub forward-dep (A-532)** → broken by `expected_keepers.go` shim; simtest wires a stub hub keeper; the real impl lands in P4. +- **Revocation race (A-531)** → handler checks status at tx start; SDK store is atomic per tx; simtest covers revoked-during-action. + +--- + +## Phase P4 — Hub API B2B Runtime + +- **Slug**: `hub-api-runtime` +- **Branch**: `phase/04-hub-api-runtime` +- **REQs covered**: REQ-036 (Hub API B2B runtime — `x/hub` custody/lending/compliance handlers + `CustodyKeyring` interface D-058) +- **Tag**: `v0.4.4` +- **Type**: `feat` +- **Goal**: Promote `x/hub` from v0.3 skeleton to runtime: `MsgRegisterCustodyService` / `MsgCustodyReceiveAsset` / `MsgCustodyReleaseAsset` / `MsgRecordLendingPrimitive` (coupon clamp) / `MsgRecordComplianceAttestation` + `CustodyKeyring` interface (D-058) + in-memory `memKeyring` test impl + simtest. data-engineer phase-specific for custody state. Depends on anchors (P3) — the `PartnerKeeper` shim is wired to the real `x/partner` keeper. + +### Wave 1 — CustodyKeyring interface + memKeyring + custody state + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P4-01-01 | REQ-036, D-058 | security-engineer + data-engineer | `x/hub/types/keyring.go` | `CustodyKeyring` Go interface (D-058): `Sign(ctx, assetID string, payload []byte) (sig []byte, err error)`, `Derive(ctx, assetID string) (pub PubKey, err error)`, `Status(ctx, assetID string) (KeyringStatus, error)`. `KeyringStatus` enum (Active, Rotated, Revoked). Supports key rotation (`Status` reports active key version; handler consults keyring per operation — no cross-block caching). | `go build ./x/hub/...` succeeds; `CustodyKeyring` interface compiles; `KeyringStatus` enum present; lexicon green. | P3-99-01 | +| P4-02-01 | REQ-036 | data-engineer + security-engineer | `x/hub/keeper/keyring_mem.go`, `x/hub/keeper/custody_state.go` | In-memory test-only `memKeyring` impl (signs with a throwaway ed25519 key per assetID; supports rotation by swapping the keymap entry; NO real MPC/HSM — D-054/D-058). `custody_state.go`: custody asset records (assetID → custody entry + sig ref + key version); in-memory test store ONLY (SDK in-memory store is the substrate; no real database, no migration — D-054 simtest grade). Shape consistent with `CustodyKeyring` interface; supports rotation. | `go build ./x/hub/...` succeeds; `memKeyring` implements `CustodyKeyring`; custody state shape (assetID → entry + sig ref + key version) present; no real DB; lexicon green. | P4-01-01 | + +### Wave 2 — x/hub types + keeper + MsgServer + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P4-03-01 | REQ-036 | cosmos-engineer | `x/hub/types/msg_*.go`, `x/hub/types/expected_keepers.go` | New `Msg*` types (lexicon-clean names — AVOID "deposit"; use `MsgCustodyReceiveAsset`/`MsgCustodyReleaseAsset` per A-542): `MsgRegisterCustodyService` (`ValidateBasic`: non-empty service-id, operator-partner-id, assets-supported), `MsgCustodyReceiveAsset` (`ValidateBasic`: non-empty asset-id, partner-id), `MsgCustodyReleaseAsset` (`ValidateBasic`: non-empty asset-id, holder-reach-id; authz), `MsgRecordLendingPrimitive` (`ValidateBasic`: non-empty service-id, coupon-bps), `MsgRecordComplianceAttestation` (`ValidateBasic`: non-empty partner-id, attestation-ref). `expected_keepers.go`: `PartnerKeeper` interface (operator must be Onboarded Anchor — `GetPartner(partnerID string) (tier, status, err)`, `IsAnchorOnboarded(partnerID string) bool`), `ComplianceKeeper` interface (compliance status check — `IsCompliant(partnerID string) bool`). NO struct import of `x/partner/types` (G-003 intact). | `go build ./x/hub/...` succeeds; `Msg*` implement `sdk.Msg`; `expected_keepers.go` defines `PartnerKeeper` + `ComplianceKeeper` INTERFACES; NO banned "deposit" in Msg names (A-542); lexicon green. | P4-01-01 | +| P4-04-01 | REQ-036 | cosmos-engineer + backend-engineer + security-engineer | `x/hub/keeper/keeper.go`, `x/hub/keeper/msg_server.go`, `x/hub/module.go` | Store-backed `Keeper` (wraps the custody state from P4-02-01). `MsgServer`: `RegisterCustodyService` (operator must be Onboarded Anchor — checked via `PartnerKeeper` shim), `CustodyReceiveAsset` (delegates signing to `CustodyKeyring`; records custody entry + sig ref), `CustodyReleaseAsset` (COMPLIANCE-BEFORE-CUSTODY ordering — A-544: checks compliance status via `ComplianceKeeper` shim BEFORE the custody debit; authz: holder or authorized Window grantee), `RecordLendingPrimitive` (CLAMPS coupon to `[LendingCouponFloorBps=0, LendingCouponCapBps=800]` — runtime echo of D-028/REQ-030; emits clamp event for simtest), `RecordComplianceAttestation` (records attestation ref against partner). `module.go`: `AppModule` + `RegisterServices`. | `go build ./x/hub/...` succeeds; `MsgServer` methods present; compliance-before-custody ordering enforced (A-544); lending coupon clamp invoked at runtime; `PartnerKeeper` + `ComplianceKeeper` shims used (no struct imports); lexicon green. | P4-03-01, P4-02-01 | + +### Wave 3 — Simtest + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P4-05-01 | REQ-036 | backend-engineer + security-engineer + data-engineer | `x/hub/keeper/msg_server_simtest_test.go` | Simtest: `RegisterCustodyService` with Onboarded Anchor (wired to real `x/partner` keeper — G-003 test exemption); `CustodyReceiveAsset` + `CustodyReleaseAsset` round-trip via `memKeyring` (sig recorded); `CustodyReleaseAsset` on a non-compliant partner REJECTED (compliance-before-custody — A-544); `RecordLendingPrimitive` coupon clamp event (coupon within [0, 800] bps; a coupon > 800 is clamped to 800 and the clamp event is emitted — A-543); `CustodyKeyring` rotation (swap keymap entry; `Status` reports the new active key version; a subsequent `Sign` uses the new key). Coverage ≥80% on `x/hub/keeper`. | `go test ./x/hub/...` passes; simtest covers custody lifecycle + compliance-before-custody + coupon clamp + keyring rotation; coverage ≥80%; lexicon green; G-003 import-invariant green. | P4-04-01 | + +### Wave 4 — Phase verification + ship (data-engineer removed after P4) + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P4-99-01 | REQ-012, REQ-036 | lead-developer | (cross-cutting) | `go build ./...` + `go test ./...` green; coverage ≥80% on `x/hub/keeper`; lexicon firewalls green; G-003 green; `HubServiceCount=3` unchanged; `LendingCouponCapBps=800` + `LendingCouponFloorBps=0` unchanged (REQ-030 cross-const test stays green); tag `v0.4.4`. Remove data-engineer persona (P4 phase-specific). | `go test ./...` green; coverage ≥80%; both lexicon firewalls green; G-003 green; `HubServiceCount=3` + `LendingCouponCapBps=800`/`LendingCouponFloorBps=0` unchanged; REQ-030 cross-const test green; git tag `v0.4.4`; data-engineer removed. | P4-05-01 | + +### P4 Must-Haves +- [ ] `x/hub` has `keeper/keeper.go` + `keeper/msg_server.go` + `keeper/keyring_mem.go` + `keeper/custody_state.go` + `types/keyring.go` + `types/msg_*.go` + `types/expected_keepers.go` + `module.go`. +- [ ] `CustodyKeyring` interface (D-058: Sign/Derive/Status) + `memKeyring` in-memory test impl. +- [ ] Custody message names AVOID "deposit" (use `MsgCustodyReceiveAsset`/`MsgCustodyReleaseAsset` — A-542). +- [ ] Compliance-before-custody ordering enforced (A-544). +- [ ] Lending coupon clamp at runtime [0, 800] bps (A-543); clamp event emitted. +- [ ] `CustodyKeyring` rotation supported (simtest covers it). +- [ ] `go build ./...` and `go test ./...` green (no regression). +- [ ] ≥80% coverage on `x/hub/keeper`. +- [ ] Locked-consts unchanged: `HubServiceCount=3`, `LendingCouponCapBps=800`, `LendingCouponFloorBps=0` (REQ-030 cross-const test green). +- [ ] Lexicon firewall green; G-003 import-invariant green. +- [ ] Git tag `v0.4.4`. + +### P4 Risks & Mitigations +- **Custody key rotation (D-058)** → `Status` reports active key version; handler consults keyring per operation (no cross-block caching); simtest covers rotation. +- **Compliance-before-custody race (A-544)** → handler checks compliance BEFORE the custody debit; simtest covers non-compliant rejection. +- **Coupon clamp runtime echo (A-543)** → handler clamps to [0, 800] bps; emits event; REQ-030 cross-const test stays green. + +--- + +## Phase P5 — Services Runtime + +- **Slug**: `services-runtime` +- **Branch**: `phase/05-services-runtime` +- **REQs covered**: REQ-037 (Services runtime — `x/services` Care/SIM/Vault/Mail service lifecycle handlers) +- **Tag**: `v0.4.5` +- **Type**: `feat` +- **Goal**: Promote `x/services` from v0.3 skeleton to runtime: `MsgRegisterService` / `MsgActivateService` / `MsgSuspendService` / `MsgRevokeService` + per-kind handlers (`MsgIssueCareGrant`, `MsgActivateSIM`, `MsgProvisionVault`, `MsgBindMailbox`) + simtest. Depends on hub (P4). The `window-id` grant is checked on EVERY op (A-552 — revoked Window invalidates ongoing service ops). + +### Wave 1 — x/services types + keeper + MsgServer + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P5-01-01 | REQ-037 | cosmos-engineer | `x/services/types/msg_*.go`, `x/services/types/expected_keepers.go` | New `Msg*` types (per-kind typed dispatch — A-551, NOT a generic `MsgInvokeService`): `MsgRegisterService` (`ValidateBasic`: non-empty service-id, operator-reach-id, window-id, kind), `MsgActivateService`, `MsgSuspendService`, `MsgRevokeService`, `MsgIssueCareGrant` (Care), `MsgActivateSIM` (SIM), `MsgProvisionVault` (Vault), `MsgBindMailbox` (Mail). `expected_keepers.go`: `WindowKeeper` interface (window-grant validity — `GetWindowStatus(windowID string) (status, err)`; called on EVERY op — A-552), `VaultKeeper` interface (`ProvisionVault(serviceID, quota int64) error` for VaultService). NO struct import of `x/window/types` or `x/vault/types` (G-003 intact). | `go build ./x/services/...` succeeds; `Msg*` implement `sdk.Msg`; per-kind typed dispatch (one Msg per ServiceKind); `expected_keepers.go` defines `WindowKeeper` + `VaultKeeper` INTERFACES; lexicon green. | P4-99-01 | +| P5-02-01 | REQ-037 | cosmos-engineer + backend-engineer | `x/services/keeper/keeper.go`, `x/services/keeper/msg_server.go`, `x/services/module.go` | Store-backed `Keeper`. `MsgServer`: `RegisterService` (operator-reach-id valid; `window-id` must reference an Active Window — checked via `WindowKeeper` shim), `ActivateService`, `SuspendService`, `RevokeService` (revocation requires Window grantor or Watcher quorum). Per-kind handlers: `IssueCareGrant` (Care), `ActivateSIM` (SIM), `ProvisionVault` (Vault, references `x/vault` by ID via `VaultKeeper` shim), `BindMailbox` (Mail). WINDOW-GRANT CHECKED ON EVERY OP (A-552 — a revoked Window invalidates the service; the handler checks `WindowKeeper.GetWindowStatus` before each op, not just registration). `module.go`: `AppModule` + `RegisterServices`. | `go build ./x/services/...` succeeds; `MsgServer` methods present; per-kind typed dispatch; window-grant checked on every op (A-552); `WindowKeeper` + `VaultKeeper` shims used (no struct imports); lexicon green. | P5-01-01 | + +### Wave 2 — Simtest + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P5-03-01 | REQ-037 | backend-engineer | `x/services/keeper/msg_server_simtest_test.go` | Simtest: full service lifecycle (Pending→Active→Suspended→Revoked); per-kind handler round-trips (Care/SIM/Vault/Mail); window-grant validity on EVERY op (a service registered against a Revoked Window is rejected; a service operating after its Window expired is rejected — A-552); VaultService provisioning via `VaultKeeper` shim (wired to real `x/vault` keeper in test setup — G-003 test exemption). Coverage ≥80% on `x/services/keeper`. | `go test ./x/services/...` passes; simtest covers lifecycle + per-kind + window-grant-on-every-op; coverage ≥80%; lexicon green; G-003 import-invariant green. | P5-02-01 | + +### Wave 3 — Phase verification + ship + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P5-99-01 | REQ-012, REQ-037 | lead-developer | (cross-cutting) | `go build ./...` + `go test ./...` green; coverage ≥80% on `x/services/keeper`; lexicon firewalls green; G-003 green; `ServiceKindCount=4` unchanged; tag `v0.4.5`. | `go test ./...` green; coverage ≥80%; both lexicon firewalls green; G-003 green; `ServiceKindCount=4` unchanged; git tag `v0.4.5`. | P5-03-01 | + +### P5 Must-Haves +- [ ] `x/services` has `keeper/keeper.go` + `keeper/msg_server.go` + `types/msg_*.go` + `types/expected_keepers.go` + `module.go`. +- [ ] Per-kind typed dispatch (one `Msg*` per ServiceKind — A-551; NOT a generic dispatch). +- [ ] Window-grant checked on EVERY service operation (A-552 — revoked Window invalidates ongoing ops). +- [ ] `go build ./...` and `go test ./...` green (no regression). +- [ ] ≥80% coverage on `x/services/keeper`. +- [ ] Locked-const `ServiceKindCount=4` unchanged. +- [ ] Lexicon firewall green; G-003 import-invariant green. +- [ ] Git tag `v0.4.5`. + +### P5 Risks & Mitigations +- **Window-grant validity (A-552)** → handler checks `WindowKeeper.GetWindowStatus` on every op; simtest covers expired-window-during-operation. +- **Type-unsafe generic dispatch (A-551)** → per-kind `Msg*` (typed dispatch, not generic); compile-time kind safety. + +--- + +## Phase P6 — Bond Market Runtime + +- **Slug**: `bond-market-runtime` +- **Branch**: `phase/06-bond-market-runtime` +- **REQs covered**: REQ-038 (Bond market depth runtime — `x/bond` Growth Bond issuance + secondary-market CLOB matching handlers; 8% cap / 0% floor per-match clamp per D-028/D-057) +- **Tag**: `v0.4.6` +- **Type**: `feat` +- **Goal**: Promote `x/bond` from v0.3 skeleton to runtime: `MsgIssueBond` / `MsgIssueGrowthBond` / `MsgTickGrowthBond` / `MsgPlaceSecondaryOrder` / `MsgCancelSecondaryOrder` / `MsgMatchSecondaryOrder` (CLOB matching engine, price-time priority FCFS per REQ-007, per-match coupon clamp [0, 800] bps — A-562 REJECT above cap per D-063) + simtest. Depends on hub lending (P4). + +### Wave 1 — x/bond types + keeper + MsgServer + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P6-01-01 | REQ-038 | cosmos-engineer | `x/bond/types/msg_*.go`, `x/bond/types/expected_keepers.go` | New `Msg*` types: `MsgIssueBond` (`ValidateBasic`: non-empty bond-id, issuer-stand-id, principal > 0, coupon-bps within [0, 800] — stateless clamp check), `MsgIssueGrowthBond` (`ValidateBasic`: same + growth-rate-bps), `MsgTickGrowthBond` (`ValidateBasic`: non-empty bond-id), `MsgPlaceSecondaryOrder` (`ValidateBasic`: non-empty order-id, bond-id, side ∈ {Buy, Sell}, price-bps, quantity > 0), `MsgCancelSecondaryOrder` (`ValidateBasic`: non-empty order-id), `MsgMatchSecondaryOrder` (`ValidateBasic`: non-empty incoming-order-id). `expected_keepers.go`: `StandKeeper` interface (GrowthBond issuer-stand-id validity — `StandExists(standID string) bool`). NO struct import of `x/stand/types` (G-003 intact). | `go build ./x/bond/...` succeeds; `Msg*` implement `sdk.Msg`; `expected_keepers.go` defines `StandKeeper` INTERFACE; lexicon green (no "interest"/"yield"/"deposit"/"savings" — use "coupon"/"growth"/"order"/"match"). | P4-99-01 | +| P6-02-01 | REQ-038, D-057, D-063 | cosmos-engineer + backend-engineer + security-engineer | `x/bond/keeper/keeper.go`, `x/bond/keeper/msg_server.go`, `x/bond/keeper/clob.go`, `x/bond/module.go` | Store-backed `Keeper` (resting book stored ordered by (price, sequence) for price-time priority FCFS — REQ-007). `MsgServer`: `IssueBond` (invokes v0.3 `Clamp` on coupon), `IssueGrowthBond` (`Clamp` + `ClampGrowth`), `TickGrowthBond` (applies growth, clamped), `PlaceSecondaryOrder`, `CancelSecondaryOrder`, `MatchSecondaryOrder` (CLOB match — `clob.go`: loads the resting book for the bond, matches the incoming order against the best opposing price until filled or the book is empty, writes `Filled` orders, emits a match event with the matched coupon CLAMPED to [0, 800] bps via v0.3 `Clamp`; per D-063, a match whose implied coupon EXCEEDS 800 bps is REJECTED — fails closed, the resting order stays, the incoming order rests or is cancelled; matches within [0, 800] use `Clamp` (in-band, no refund needed). PER-TX matching (dYdX-v4-shaped, no batch end-of-block matching in v0.5 simtest). Handler documented as NOT front-running-safe for mainnet (Year-3+ concern; simtest does NOT assert front-running safety — D-054). `module.go`: `AppModule` + `RegisterServices`. The 8%/0% consts are referenced directly (NOT a local copy) — A-563; the REQ-030 cross-const test stays green. | `go build ./x/bond/...` succeeds; `MsgServer` methods present; CLOB matching with price-time priority; per-match coupon clamp [0, 800] (A-562/D-063 REJECT above cap); consts referenced directly (A-563); no AMM (D-057/A-564); lexicon green. | P6-01-01 | + +### Wave 2 — Simtest + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P6-03-01 | REQ-038 | backend-engineer + security-engineer | `x/bond/keeper/msg_server_simtest_test.go` | Simtest: bond issuance (coupon clamped at issuance); GrowthBond issuance + tick (growth clamped); CLOB matching — full fill, partial fill + rest, no-match (order rests), cancel; PER-MATCH CLAMP: a match within [0, 800] bps clears (clamp event emitted); a match whose implied coupon EXCEEDS 800 bps is REJECTED (fails closed — D-063; the resting order stays, the incoming order rests); price-time priority FCFS (at the same price, the earlier resting order fills first — REQ-007); `StandKeeper` shim wired to real `x/stand` keeper in test setup (G-003 test exemption). Coverage ≥80% on `x/bond/keeper`. | `go test ./x/bond/...` passes; simtest covers issuance + growth + CLOB matching + per-match clamp (reject-above-cap) + price-time priority; coverage ≥80%; lexicon green; G-003 import-invariant green. | P6-02-01 | + +### Wave 3 — Phase verification + ship + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P6-99-01 | REQ-012, REQ-038 | lead-developer | (cross-cutting) | `go build ./...` + `go test ./...` green; coverage ≥80% on `x/bond/keeper`; lexicon firewalls green; G-003 green; D-028 regression: `CouponCapBps=800` + `CouponFloorBps=0` unchanged; REQ-030 cross-const test green; tag `v0.4.6`. | `go test ./...` green; coverage ≥80%; both lexicon firewalls green; G-003 green; `CouponCapBps=800`/`CouponFloorBps=0` unchanged; REQ-030 cross-const test green; git tag `v0.4.6`. | P6-03-01 | + +### P6 Must-Haves +- [ ] `x/bond` has `keeper/keeper.go` + `keeper/msg_server.go` + `keeper/clob.go` + `types/msg_*.go` + `types/expected_keepers.go` + `module.go`. +- [ ] CLOB matching engine (price-time priority FCFS per REQ-007; per-tx matching; NO AMM — D-057/A-564). +- [ ] Per-match coupon clamp [0, 800] bps via v0.3 `Clamp`; match above 800 REJECTED (fails closed — D-063/A-562). +- [ ] 8%/0% consts referenced directly (NOT a local copy — A-563); REQ-030 cross-const test green. +- [ ] `go build ./...` and `go test ./...` green (no regression). +- [ ] ≥80% coverage on `x/bond/keeper`. +- [ ] D-028 regression: `CouponCapBps=800` + `CouponFloorBps=0` unchanged. +- [ ] Locked-consts unchanged: `OrderSideCount=2`, `OrderStatusCount=3`. +- [ ] Lexicon firewall green (no "interest"/"yield"); G-003 import-invariant green. +- [ ] Git tag `v0.4.6`. + +### P6 Risks & Mitigations +- **Per-match clamp reject-vs-clamp (A-562, resolved by D-063)** → REJECT above cap (fails closed); simtest covers reject-above-cap. +- **CLOB front-running (out of scope for simtest — D-054)** → handler documented as NOT front-running-safe for mainnet; simtest does NOT assert front-running safety. +- **D-028 const regression** → consts referenced directly; REQ-030 cross-const test green; double firewall. + +--- + +## Phase P7 — Council Governance Runtime + +- **Slug**: `council-governance-runtime` +- **Branch**: `phase/07-council-governance-runtime` +- **REQs covered**: REQ-039 (Council governance runtime — `x/council` Proposal/VoteOption enums per AUDIT §193 P1-1/D-060 + Voice lifecycle handlers; Mission Lock const firewall intact) +- **Tag**: `v0.4.7` +- **Type**: `feat` +- **Goal**: Promote `x/council` to runtime: ADD `Proposal` struct + `ProposalKind` (4, incl. `MissionLockAmendment-Rejected`) + `ProposalStatus` (5) + `VoteOption` (4) enums (AUDIT §193 P1-1 promotion per D-060) + `MsgSubmitProposal` / `MsgVote` / `MsgTallyProposal` handlers + simtest. `MissionLockAmendment-Rejected` ProposalKind rejected at `ValidateBasic` (D-064/A-572 — never reaches handler). Watcher Veto quorum default 6 (D-065/A-574). `SignalKind` stays at 4 (P1-2 defensible; v0.4 `TestSignalKindShapeIntentional` regression-guard test stays green). Cross-cutting; depends on all prior. lands last. + +### Wave 1 — x/council enum additions + types + keeper + MsgServer + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P7-01-01 | REQ-039, D-060 | cosmos-engineer + security-engineer | `x/council/types/types.go` (EXTEND), `x/council/types/msg_*.go`, `x/council/types/expected_keepers.go` | EXTEND `x/council/types/types.go`: ADD `Proposal` struct (proposal-id, council-id, kind (ProposalKind), proposer-reach, submit-time, voting-deadline, status (ProposalStatus), tally (TallyResult)). ADD `ProposalKind` enum {Stand, Guild, Mesh, MissionLockAmendment-Rejected} — `ProposalKindCount = 4` locked-const. ADD `ProposalStatus` enum {Pending, Active, Succeeded, Failed, Executed} — `ProposalStatusCount = 5` locked-const. ADD `VoteOption` enum {Yes, No, Abstain, Veto} — `VoteOptionCount = 4` locked-const (Veto is Watcher-only). ADD `WatcherVetoQuorum` to `Params` (default 6 — D-065/A-574; a param, NOT a locked const). `MissionLockAmendable = false` (v0.2 locked const) UNCHANGED. `SignalKindCount = 4` UNCHANGED (P1-2 defensible; v0.4 `TestSignalKindShapeIntentional` stays green). New `Msg*` types: `MsgSubmitProposal` (`ValidateBasic`: non-empty proposal-id, council-id, kind ∈ ProposalKind; **MissionLockAmendment-Rejected kind REJECTED at ValidateBasic per D-064/A-572 — the message never reaches the handler**), `MsgVote` (`ValidateBasic`: non-empty proposal-id, voter-reach, option ∈ VoteOption; Veto requires the signer to be a Watcher — checked at handler via `WatcherKeeper` shim), `MsgTallyProposal` (`ValidateBasic`: non-empty proposal-id). `expected_keepers.go`: `WatcherKeeper` interface (Veto authz + quorum — `IsWatcher(reachID string) bool`, `CountWatchers() int`). NO struct import of `x/watcher/types` (G-003 intact). | `go build ./x/council/...` succeeds; `Proposal`/`ProposalKind`/`ProposalStatus`/`VoteOption` enums present with locked-const counts (4/5/4); `MissionLockAmendable=false` UNCHANGED; `SignalKindCount=4` UNCHANGED; `MsgSubmitProposal.ValidateBasic` REJECTS `MissionLockAmendment-Rejected` kind (D-064); `WatcherVetoQuorum` param default 6 (D-065); lexicon green. | P6-99-01 | +| P7-02-01 | REQ-039 | cosmos-engineer + backend-engineer + security-engineer | `x/council/keeper/keeper.go`, `x/council/keeper/msg_server.go`, `x/council/module.go` | Store-backed `Keeper`. `MsgServer`: `SubmitProposal` (validates kind — the `MissionLockAmendment-Rejected` kind never reaches here per D-064 `ValidateBasic` rejection; creates Proposal status=Pending), `Vote` (cast a Voice with a VoteOption; Veto requires Watcher authz via `WatcherKeeper` shim; vote on a non-Active proposal REJECTED; vote after voting-deadline REJECTED), `TallyProposal` (closes the voting deadline, computes the tally, transitions Succeeded/Failed; Veto semantics: a single Veto does NOT block — anti-greed, vision §19; the proposal transitions to Failed only if `NoWithVeto >= WatcherVetoQuorum` (default 6, D-065/A-574); the v0.2 `TallyResult.NoWithVeto` field (zero-locked in v0.2) is now populated by Watcher Vetos). Proposal EXECUTION (auto-executing a passed proposal) is NOT in v0.5 — the handler records the tally result but does NOT auto-execute (a v0.6+ concern). `module.go`: `AppModule` + `RegisterServices`. | `go build ./x/council/...` succeeds; `MsgServer` methods present; `MissionLockAmendment-Rejected` rejected at `ValidateBasic` (never reaches handler — D-064); Veto quorum-based (single Veto does NOT block — D-065); no auto-execution; lexicon green. | P7-01-01 | + +### Wave 2 — Simtest + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P7-03-01 | REQ-039 | backend-engineer + security-engineer | `x/council/keeper/msg_server_simtest_test.go`, `x/council/types/types_test.go` (EXTEND) | Simtest: full proposal lifecycle (Submit→Active→Vote→Tally→Succeeded/Failed); `MissionLockAmendment-Rejected` kind REJECTED at `ValidateBasic` (the message never reaches the handler — D-064/A-572; simtest asserts the `MsgSubmitProposal` with that kind fails `ValidateBasic` with a Mission-Lock error); Veto semantics (a single Veto does NOT block; `NoWithVeto >= WatcherVetoQuorum` (default 6) transitions to Failed — D-065/A-574; simtest covers single-Veto-no-block + quorum-Veto-fails); vote-on-non-Active REJECTED; tally-before-deadline REJECTED; Watcher authz for Veto via `WatcherKeeper` shim (wired to real `x/watcher` keeper in test setup — G-003 test exemption). `types_test.go` EXTEND: locked-const tests for `ProposalKindCount=4`, `ProposalStatusCount=5`, `VoteOptionCount=4`; `MissionLockAmendable==false` regression (v0.2 `TestMissionLockAmendableFalse` stays green); `SignalKindCount==4` regression (v0.4 `TestSignalKindShapeIntentional` stays green). Coverage ≥80% on `x/council/keeper` + `x/council/types`. | `go test ./x/council/...` passes; simtest covers lifecycle + MissionLockAmendment-reject + Veto quorum + vote/tally rejections; coverage ≥80%; locked-const tests for new enums + Mission Lock + SignalKind regression; lexicon green; G-003 import-invariant green. | P7-02-01 | + +### Wave 3 — Phase verification + ship + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P7-99-01 | REQ-012, REQ-039 | lead-developer | (cross-cutting) | `go build ./...` + `go test ./...` green; coverage ≥80% on `x/council/keeper` + `x/council/types`; lexicon firewalls green; G-003 green; `CouncilKindCount=3` unchanged; `MissionLockAmendable=false` unchanged (v0.2 regression green); `SignalKindCount=4` unchanged (v0.4 regression green); new locked-consts: `ProposalKindCount=4`, `ProposalStatusCount=5`, `VoteOptionCount=4`; tag `v0.4.7`. | `go test ./...` green; coverage ≥80%; both lexicon firewalls green; G-003 green; all locked-consts green (unchanged + new); git tag `v0.4.7`. | P7-03-01 | + +### P7 Must-Haves +- [ ] `x/council` has `keeper/keeper.go` + `keeper/msg_server.go` + `types/msg_*.go` + `types/expected_keepers.go` + `module.go`; `types/types.go` EXTENDED with `Proposal`/`ProposalKind`/`ProposalStatus`/`VoteOption`. +- [ ] `ProposalKindCount=4` (incl. `MissionLockAmendment-Rejected`), `ProposalStatusCount=5`, `VoteOptionCount=4` locked-consts added. +- [ ] `MissionLockAmendment-Rejected` ProposalKind rejected at `ValidateBasic` (D-064/A-572 — never reaches handler). +- [ ] Watcher Veto quorum default 6 (D-065/A-574); single Veto does NOT block (anti-greed). +- [ ] `MissionLockAmendable=false` UNCHANGED (v0.2 `TestMissionLockAmendableFalse` green). +- [ ] `SignalKindCount=4` UNCHANGED (v0.4 `TestSignalKindShapeIntentional` green); expansion to 5 deferred to v0.6+. +- [ ] No proposal auto-execution (handler records tally only; execution is v0.6+). +- [ ] `go build ./...` and `go test ./...` green (no regression). +- [ ] ≥80% coverage on `x/council/keeper` + `x/council/types`. +- [ ] `CouncilKindCount=3` unchanged. +- [ ] Lexicon firewall green; G-003 import-invariant green. +- [ ] Git tag `v0.4.7`. + +### P7 Risks & Mitigations +- **Mission Lock const firewall integrity (D-064/A-572)** → `ValidateBasic` rejects `MissionLockAmendment-Rejected` kind; the const is the firewall, `ValidateBasic` is the gate; v0.2 regression test green. +- **Veto semantics (D-065/A-574)** → single Veto does NOT block (anti-greed); quorum-based (default 6); simtest covers single-Veto-no-block + quorum-Veto-fails. +- **SignalKind 4-not-5 (A-573)** → UNCHANGED; v0.4 regression-guard test green; expansion deferred to v0.6+ governance vote (not a Mission-Lock const; a distinct locked const). + +--- + +## Phase P8 — Final Review + Audit + Milestone Ship + +- **Slug**: `final-review-audit-ship` +- **Branch**: `phase/08-final-review-audit-ship` +- **REQs covered**: all v0.5 REQs (REQ-033..REQ-039) — final coverage accounting; no new REQs (covers post-hoc fixes from REVIEW/AUDIT) +- **Tag**: `v0.4.8` (IS the v0.5 milestone release; D-008) +- **Type**: `final` +- **Personas**: lead-developer (review/ship) + ci-security-auditor (ACTIVATED for the v0.5 milestone audit + feature purity gate) +- **Goal**: Multi-persona review across P1..P7, audit (reconstruction test + feature purity gate: no breaking schema changes; locked-const firewall intact; G-003 production firewall intact; G-006 controlled exception GRILL-ratified), milestone ship (merge to main, tag `v0.4.8` = v0.5 milestone release, release, delete all milestone branches). + +### Wave 1 — Review + Audit (parallel) + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P8-01-01 | — | lead-developer (review) | `.ciagent/oy/REVIEW.md` (NEW for v0.5) | Multi-persona code review across P1..P7. Adversarial probes: (1) do the `expected_keepers.go` shims preserve G-003 (no production struct imports across `x//types`); (2) does the CLOB per-match clamp REJECT above 800 (D-063); (3) does `MissionLockAmendment-Rejected` get rejected at `ValidateBasic` (D-064); (4) does the OY-QR one-shot flip `consumed` BEFORE the transfer (A-521); (5) does compliance-before-custody ordering hold (A-544); (6) does the IBC replay/timeout protection mirror ibc-go (A-513). Auto-apply P0 fixes; flag P1+ for post-hoc. | REVIEW.md v0.5 section written; P0 issues (if any) fixed in P8; P1+ flagged. | P1..P7 | +| P8-02-01 | — | ci-security-auditor (audit) | `.ciagent/oy/AUDIT.md` (v0.5 section) | Audit: (1) reconstruction test (git log ↔ `.ciagent/` files for v0.5; each REQ-033..REQ-039 maps to shipped runtime); (2) file/branch/commit discipline (8 phase branches `phase/01-*`..`phase/07-*` + `phase/08-*`; 8 patch tags `v0.4.1`..`v0.4.8`; D-056 ordering respected); (3) **feature purity gate**: no breaking schema changes (the v0.3 `types/` contracts are NOT amended — runtime adds behavior on top); locked-const firewall intact (`CouponCapBps=800`/`CouponFloorBps=0` D-028, `BearerTypeCount=6`, `PartnerTierCount=4`, `MissionLockAmendable=false`, `SignalKindCount=4`, `BridgeStatusCount=4`, `ExitStatusCount=5`, `ServiceKindCount=4`, `HubServiceCount=3`, `CouncilKindCount=3` — all unchanged; new `ProposalKindCount=4`/`ProposalStatusCount=5`/`VoteOptionCount=4` added in P7 per D-060); G-003 production firewall intact (no struct imports across `x//types` in production code; `expected_keepers.go` shims are interfaces); G-006 controlled exception GRILL-ratified (D-055/D-062 cosmos-sdk v0.50.x + ibc-go v8.x); (4) coverage ≥80% on all 8 runtime keeper packages; (5) lexicon firewalls green on both `x/**/*.go` (incl. new `keeper/` + `msg_*.go` + `module.go`) + `docs/**/*.md`. | AUDIT.md v0.5 section written; feature purity gate GREEN (no breaking schema changes; locked-const firewall intact; G-003 intact; G-006 GRILL-ratified); reconstruction test passes. | P1..P7 | + +### Wave 2 — Ship (blocked-by Wave 1) + +| Task ID | REQ | Persona | Files | Deliverable | Must-have verification | Blocked-by | +|---|---|---|---|---|---|---| +| P8-03-01 | REQ-033..REQ-039 | lead-developer (ship) | `.ciagent/oy/REQUIREMENTS.md`, `.ciagent/oy/ROADMAP.md` | Update REQUIREMENTS.md: mark REQ-033..REQ-039 → Complete (runtime shipped). Update ROADMAP.md: mark v0.5 milestone COMPLETE; add the tag-line note that v0.5 shipped on the `v0.4.x` patch line (P0 → `v0.4.0`, P1..P7 → `v0.4.1..v0.4.7`, P8 → `v0.4.8` = milestone release, per D-008). | REQUIREMENTS.md status column updated for all 7 v0.5 REQs → Complete; ROADMAP.md v0.5 marked complete + tag-line note present. | P8-01-01, P8-02-01 | +| P8-03-02 | (milestone) | lead-developer | (cross-cutting) | Final ship: merge `phase/08` → `milestone/v0.5-bearers-runtime` → `main`; create milestone release tag `v0.4.8` (= v0.5 milestone release per D-008); delete the 8 phase branches (`phase/01-*`..`phase/08-*`) after merge; confirm `go build ./...` + `go test ./...` green at the `v0.4.8` tag. | `v0.4.8` tag created on main; `go test ./...` green at the tag; ROADMAP.md v0.5 complete; phase branches deleted; release notes reference v0.5 scope (8 modules promoted to runtime: exit, bridge, bearers, partner, hub, services, bond, council; cosmos-sdk + ibc-go deps added per D-055; CustodyKeyring interface D-058; CLOB matching D-057; AUDIT §193 P1-1 enums D-060). | P8-03-01 | + +### P8 Must-Haves +- [ ] REVIEW.md v0.5 section written; P0 fixes applied. +- [ ] AUDIT.md v0.5 section written; reconstruction test passes. +- [ ] **Feature purity gate GREEN**: no breaking schema changes (v0.3 `types/` contracts NOT amended); locked-const firewall intact (all v0.1..v0.4 consts unchanged; new P7 enums added per D-060); G-003 production firewall intact; G-006 controlled exception GRILL-ratified (D-055/D-062). +- [ ] Coverage ≥80% on all 8 runtime keeper packages (exit, bridge, bearers, partner, hub, services, bond, council). +- [ ] Both lexicon firewalls green (x/**/*.go incl. new keeper/module files + docs/**/*.md). +- [ ] G-003 import-invariant green (no production struct imports across x//types; expected_keepers.go shims are interfaces). +- [ ] REQUIREMENTS.md + ROADMAP.md mark v0.5 COMPLETE. +- [ ] Tag `v0.4.8` created (= v0.5 milestone release). +- [ ] Milestone branch merged to `main`. +- [ ] All 8 phase branches deleted (local + remote). + +### P8 Risks & Mitigations +- **Runtime promotion breaks v0.3 type contracts** → the v0.3 `types/` packages are NOT amended (runtime adds behavior on top); the feature purity gate verifies no struct field removal/enum rename. +- **G-006 dep exception churn** → D-055/D-062 GRILL-ratified; the pin (cosmos-sdk v0.50.x + ibc-go v8.x) is the stable choice; the audit verifies the dep is scoped to runtime phases. +- **Milestone versioning confusion (v0.5 milestone = v0.4.8 tag)** → lead-developer enforces D-008: final phase patch IS the milestone release; no separate minor tag. ROADMAP tag-line note (P8-03-01) prevents `v0.4.8`/`v0.5.0` confusion. + +--- + +## Coverage Targets (D-033) — v0.5 + +| Package | Phase | Target | Locked-const / invariant tests | +|---|---|---|---| +| `x/exit/keeper` | P1 | ≥80% | ExitStatusCount=5 (regression); cross-chain exit via BridgeKeeper shim; Fee Covenant clamp on exit-fee-bps | +| `x/bridge/keeper` | P1 | ≥80% | BridgeStatusCount=4 (regression); IBC replay (delete-on-ack) + timeout-refund; Solana wormhole-adapter branch | +| `x/bearers/keeper` | P2 | ≥80% | BearerTypeCount=6 (regression); OY-QR one-shot (consumed-before-transfer A-521); surveillance-resistant negative test; session lifecycle | +| `x/partner/keeper` | P3 | ≥80% | PartnerTierCount=4 (regression); Anchor credential lifecycle; revocation authz (Watcher 6-of-9); post-revocation rejection | +| `x/hub/keeper` | P4 | ≥80% | HubServiceCount=3 (regression); LendingCouponCapBps=800/LendingCouponFloorBps=0 (REQ-030 cross-const test green); CustodyKeyring rotation; compliance-before-custody (A-544); coupon clamp at runtime (A-543) | +| `x/services/keeper` | P5 | ≥80% | ServiceKindCount=4 (regression); per-kind typed dispatch (A-551); window-grant-on-every-op (A-552) | +| `x/bond/keeper` | P6 | ≥80% | CouponCapBps=800/CouponFloorBps=0 (D-028 regression; REQ-030 cross-const green); OrderSideCount=2/OrderStatusCount=3 (regression); CLOB price-time priority (REQ-007); per-match clamp REJECT above 800 (D-063/A-562); no AMM (D-057) | +| `x/council/keeper` + `x/council/types` | P7 | ≥80% | CouncilKindCount=3 (regression); MissionLockAmendable=false (v0.2 regression); SignalKindCount=4 (v0.4 regression); ProposalKindCount=4/ProposalStatusCount=5/VoteOptionCount=4 (new, D-060); MissionLockAmendment-Rejected rejected at ValidateBasic (D-064/A-572); Veto quorum default 6 (D-065/A-574); single-Veto-no-block (anti-greed) | + +**Lexicon assertions (REQ-012)**: the project-wide `lexicon_meta_test.go` (x/**/*.go) + `lexicon_meta_docs_test.go` (docs) automatically cover the new `keeper/`, `msg_server.go`, `module.go`, `simtest/` files. Per-module lexicon assertions added to each new `keeper/` package. Highest-risk surfaces: `x/hub` custody Msg names (AVOID "deposit" — use `MsgCustodyReceiveAsset`/`MsgCustodyReleaseAsset` A-542); `x/bond` (no "interest"/"yield" — use "coupon"/"growth"); `x/council` ("veto"/"VoteOption" safe). + +--- + +## Task Count Summary — v0.5 + +| Phase | Waves | Tasks | Modules promoted / New | +|---|---|---|---| +| P1 | 4 | 6 | x/exit, x/bridge → runtime; cosmos-sdk + ibc-go dep (D-055/D-062) | +| P2 | 3 | 4 | x/bearers → runtime; session lifecycle; store-backed BearerTransport | +| P3 | 3 | 4 | x/partner → runtime; Anchor credential lifecycle; P3→P4 hub shim | +| P4 | 4 | 6 | x/hub → runtime; CustodyKeyring interface + memKeyring (D-058); custody state | +| P5 | 3 | 4 | x/services → runtime; per-kind handlers; window-grant-on-every-op | +| P6 | 3 | 4 | x/bond → runtime; CLOB matching (D-057); per-match clamp REJECT (D-063) | +| P7 | 3 | 4 | x/council → runtime; Proposal/VoteOption enums (D-060); MissionLockAmendment ValidateBasic reject (D-064); Veto quorum (D-065) | +| P8 | 2 | 4 | (review + audit + ship; 0 new — audit + REQUIREMENTS/ROADMAP update + tag) | +| **Total** | — | **36** | **8 modules promoted to runtime + 3 new council enums + CustodyKeyring interface + cosmos-sdk/ibc-go dep** | + +## Per-Phase REQ Coverage — v0.5 + +| Phase | REQs | Components | +|---|---|---| +| P1 | REQ-033 | x/exit (DEX swap routing) + x/bridge (L2↔L1 IBC packet handlers, 5 L2 chains D-059) | +| P2 | REQ-034 | x/bearers (OY-SAT + OY-QR handlers; session lifecycle) | +| P3 | REQ-035 | x/partner (Anchor credential issuance + revocation) | +| P4 | REQ-036 | x/hub (custody/lending/compliance handlers; CustodyKeyring D-058) | +| P5 | REQ-037 | x/services (Care/SIM/Vault/Mail lifecycle handlers) | +| P6 | REQ-038 | x/bond (Growth Bond + secondary-market CLOB matching D-057) | +| P7 | REQ-039 | x/council (Proposal/VoteOption enums D-060; Voice lifecycle handlers) | +| P8 | all v0.5 REQs (audit) | Feature purity gate; locked-const firewall; G-003 + G-006 verification; milestone ship | + +## Cross-Phase Blockers (hard) — v0.5 + +- **P1-01-01 (cosmos-sdk + ibc-go dep, D-062 GRILL)** → blocks P1-02-01 and all subsequent runtime work (the dep must land before any `Msg*`/`sdk.Msg`/`MsgServer` compiles). GRILL ratifies D-062 before P1 ships. +- **P1-03-01 (x/bridge keeper)** → blocks P1-05-01 (x/exit keeper — `BridgeKeeper` shim wired to the real `x/bridge` keeper in simtest). +- **P1-99-01 (P1 ship)** → blocks P2-01-01 (bearers routes through exit; branch hygiene + G-003 import-invariant test scanning the new files). +- **P2-99-01 (P2 ship)** → blocks P3-01-01 (anchors ride bearers). +- **P3-01-01 (x/partner expected-keepers incl. HubKeeper shim)** → the P3→P4 hub dep is broken here (interface in P3; impl wired in P4). P4-04-01 wires the real `x/hub` keeper to the `PartnerKeeper` shim in `x/hub`'s expected-keepers. +- **P3-99-01 (P3 ship)** → blocks P4-01-01 (hub custody backs anchors; the `PartnerKeeper` shim in `x/hub/types/expected_keepers.go` is wired to the real `x/partner` keeper). +- **P4-99-01 (P4 ship)** → blocks P5-01-01 (services sit on hub) and P6-01-01 (bond uses hub lending primitive — the cross-const test guards the shared 800/0 consts). +- **P5-99-01 (P5 ship)** → blocks P6-99-01? No — P6 depends on P4 (hub lending), not P5. P5 and P6 are both blocked by P4; they could run in parallel if parallelization were enabled (config `parallelization.enabled: false` — serial). +- **P6-99-01 (P6 ship)** → blocks P7-01-01 (council is cross-cutting, lands last). +- **P7-99-01 (P7 ship)** → blocks P8-01-01 (P8 audit). +- **D-062 (cosmos-sdk pin)** → must be GRILL-ratified before P1 ships. +- **D-063 (bond match reject)** → must be resolved before P6 ships (resolved here, provisional until GRILL). +- **D-064 (MissionLockAmendment ValidateBasic reject)** → must be resolved before P7 ships (resolved here, provisional until GRILL). +- **D-065 (Watcher Veto quorum)** → must be resolved before P7 ships (resolved here, provisional until GRILL). + +## v0.5 Decisions Applied (D-054..D-065 + A-501..A-574) + +The v0.5 Phase 0 clarify/research stages produced 8 clarification decisions (D-054..D-061) and 4 planner-escalation decisions (D-062..D-065, provisional until GRILL), applied to this plan: + +| ID | Decision | Applied to | +|---|---|---| +| D-054 | Runtime = simtest-grade handlers, NOT mainnet | All P1..P7 simtest tasks; P8 audit | +| D-055 | cosmos-sdk dep GRILL-approved (G-006 controlled exception) | P1-01-01 (go.mod); all runtime phases | +| D-056 | Phase ordering P1 exit → P2 bearers → P3 anchors → P4 hub → P5 services → P6 bond → P7 council → P8 final | Cross-Phase Dependency Map; all phase goals | +| D-057 | Bond CLOB matching (not AMM); 8%/0% per-match clamp | P6-02-01 (CLOB), P6-03-01 (simtest) | +| D-058 | Hub custody = CustodyKeyring interface + memKeyring test impl (no real MPC) | P4-01-01, P4-02-01 | +| D-059 | IBC packet scope = 5 locked L2 chains; Solana via wormhole-adapter | P1-03-01 (bridge IBC handlers) | +| D-060 | Council governance: add Proposal/VoteOption enums (AUDIT §193 P1-1); MissionLockAmendment-Rejected kind; SignalKind stays 4 | P7-01-01, P7-02-01, P7-03-01 | +| D-061 | No IDEATE in v0.5 (no --ideate flag) | (no IDEATE stage run) | +| D-062 (provisional) | cosmos-sdk v0.50.x + ibc-go v8.x version pin (resolves A-504) | P1-01-01 (go.mod); GRILL ratifies | +| D-063 (provisional) | Bond match above 800 bps = REJECT (fails closed) (resolves A-562) | P6-02-01 (CLOB match), P6-03-01 (simtest); GRILL ratifies before P6 | +| D-064 (provisional) | MissionLockAmendment-Rejected rejected at ValidateBasic (resolves A-572) | P7-01-01 (MsgSubmitProposal.ValidateBasic), P7-03-01 (simtest); GRILL ratifies before P7 | +| D-065 (provisional) | Watcher Veto quorum default 6 (resolves A-574) | P7-01-01 (Params.WatcherVetoQuorum), P7-02-01 (tally), P7-03-01 (simtest); GRILL ratifies before P7 | + +### Research assumptions applied (A-501..A-574, selected) + +| ID | Assumption | Applied to | +|---|---|---| +| A-501 | Every v0.5 target module gains a `keeper/` subdir + `msg_server.go`; `types/` stays the locked-contract layer | All P1..P7 keeper tasks | +| A-505 | Keeper-to-keeper cross-module calls use `expected_keepers.go` interface shims; G-003 by-ID-string rule preserved | All `types/expected_keepers.go` tasks; G-003 import-invariant | +| A-513 | IBC ack/timeout replay protection mirrors ibc-go (delete-on-ack, refund-on-timeout); simtest covers both | P1-03-01, P1-06-01 (bridge simtest) | +| A-521 | OY-QR one-shot: MsgConsumeOYQR flips `consumed` before the transfer effect; replay rejected idempotently | P2-02-01, P2-03-01 (bearers simtest) | +| A-522 | BearerTransport gains a store-backed impl (keeper as transport in simtest); no hardware/RF dep | P2-02-01 | +| A-531 | Anchor credential lifecycle = Pending → Onboarded → Suspended → Revoked | P3-02-01 | +| A-532 | P3→P4 hub dependency broken by `expected_keepers.go` shim; hub runtime impl wired in P4 | P3-01-01 (HubKeeper interface), P4-04-01 (wired) | +| A-533 | Revocation authz delegates to `x/watcher` expected-keeper shim (6-of-9 quorum); no struct import | P3-01-01, P3-02-01 | +| A-541 | CustodyKeyring interface (Sign/Derive/Status) + memKeyring in-memory test impl | P4-01-01, P4-02-01 | +| A-542 | Custody message names AVOID "deposit"; use `MsgCustodyReceiveAsset`/`MsgCustodyReleaseAsset` | P4-03-01 | +| A-543 | Lending handler clamps coupon to [0, 800] bps at runtime; clamp event emitted | P4-04-01, P4-05-01 | +| A-544 | Compliance-before-custody ordering enforced (withdrawal checks compliance before debit) | P4-04-01, P4-05-01 | +| A-551 | Per-kind service message handlers (one `Msg*` per ServiceKind), NOT a generic dispatch | P5-01-01 | +| A-552 | `window-id` grant checked on EVERY service operation (revoked Window invalidates ops) | P5-02-01, P5-03-01 | +| A-561 | CLOB matching with price-time priority (FCFS at same price, REQ-007); per-tx matching in simtest | P6-02-01 | +| A-563 | 8%/0% consts referenced directly (not copied); REQ-030 cross-const test stays green | P6-02-01, P6-99-01 | +| A-564 | No AMM in v0.5 (D-057); CLOB is the only matching engine | P6-02-01 | +| A-571 | `Proposal` + `ProposalKind` (4) + `ProposalStatus` (5) + `VoteOption` (4) ADDED to `x/council/types` (AUDIT §193 P1-1) | P7-01-01 | +| A-573 | `SignalKind` stays at 4 (P1-2 defensible; v0.4 regression-guard test stays green) | P7-01-01, P7-03-01 | + +--- + +## MVP/UX Check (REQ-MVP-UX-001) — v0.5 Feature Milestone + +> Auto-generated at full autonomy per run.md §MVP/UX CHECK. v0.5 is a +> **feature** milestone (runtime promotion); "user-facing surface" is +> developer-facing (the MsgServer handlers, the simtest output, the +> `CustodyKeyring` interface, the CLOB matching engine) and the protocol +> semantics (Anchor credential lifecycle, council governance with the +> Mission-Lock const firewall). No end-user UI changes (the docs site is +> complete from v0.3; no new docs pages in v0.5). + +### User-Facing Surface + +1. **MsgServer handlers (developer-facing)**: each `x//keeper/msg_server.go` exposes one `*Response, error` method per `Msg*`. A developer invoking `go test ./x/.../keeper/...` sees the simtest exercise each handler against an in-memory `sdk.Context`. The green test output is the surface. +2. **CustodyKeyring interface (developer-facing)**: `x/hub/types/keyring.go` defines the `CustodyKeyring` interface (`Sign`/`Derive`/`Status`); a custody vendor integration team implements it. The interface is the contract surface. +3. **CLOB matching engine (developer-facing)**: `x/bond/keeper/clob.go` implements the price-time-priority CLOB; a developer invoking `MsgMatchSecondaryOrder` sees the resting book matched and the per-match coupon clamped (REJECT above 800 — D-063). The match event is the surface. +4. **Council governance (protocol-facing)**: `x/council` `MsgSubmitProposal` / `MsgVote` / `MsgTallyProposal` with the `MissionLockAmendment-Rejected` kind rejected at `ValidateBasic` (D-064) — a developer attempting to submit a Mission-Lock-amendment proposal sees a `ValidateBasic` error. The Watcher Veto quorum (default 6, D-065) is the anti-greed gate. + +### Happy Path + +**Scenario: a developer exercises the OY-QR one-shot bearer transfer end-to-end.** + +1. The developer writes a simtest in `x/bearers/keeper/msg_server_simtest_test.go` (or runs the existing one). +2. `MsgIssueOYQR` creates an `OYQRCode` with `consumed=false`, `expires-at` in the future, a payload, an issuer-reach-id. +3. `MsgConsumeOYQR` is invoked: the handler loads the QR, asserts `!consumed`, asserts `expires-at > now`, FLIPS `consumed=true` (state write FIRST — A-521), emits the transfer effect via the `BreadKeeper` shim, emits an event, returns. +4. A REPLAY (second `MsgConsumeOYQR` on the same qr-id) loads the QR, finds `consumed==true`, returns an error (idempotent reject — NOT double-effect). +5. The simtest asserts: the transfer effect happened exactly once; the replay returned an error; the event set contains NO geolocation fields (surveillance-resistant negative test — A-522). +6. The simtest is green; coverage on `x/bearers/keeper` ≥80%. + +**Scenario: a developer exercises the CLOB bond matching with the per-match clamp.** + +1. A resting Sell order is placed at a price implying a 750 bps coupon (within [0, 800] band). +2. A Buy order arrives matching the Sell; the match clears at 750 bps (within band — `Clamp` is a no-op); the match event asserts the coupon is 750. +3. A second resting Sell order is placed at a price implying a 900 bps coupon (above the 800 cap). +4. A Buy order arrives matching the second Sell; the match's implied coupon (900) EXCEEDS `CouponCapBps=800`; the match is REJECTED (fails closed — D-063/A-562); the resting Sell stays; the Buy rests or is cancelled. +5. The simtest asserts: the in-band match cleared; the above-cap match was rejected; the 8%/0% consts are unchanged (D-028 regression); the REQ-030 cross-const test (hub ↔ bond) is green. +6. The simtest is green; coverage on `x/bond/keeper` ≥80%. + +**Scenario: a developer attempts to submit a Mission-Lock-amendment proposal.** + +1. `MsgSubmitProposal` is constructed with `kind = MissionLockAmendment-Rejected`. +2. `ValidateBasic` runs (stateless gate): the kind is `MissionLockAmendment-Rejected`; `ValidateBasic` REJECTS the message with a Mission-Lock error (D-064/A-572 — the message never reaches the handler). +3. The handler is never invoked; no Proposal state record is created; no event is emitted. +4. The simtest asserts: `ValidateBasic` returned a Mission-Lock error; the keeper's Proposal store is empty; the v0.2 `TestMissionLockAmendableFalse` regression test is green (`MissionLockAmendable==false` unchanged). +5. The simtest is green; coverage on `x/council/keeper` + `x/council/types` ≥80%. + +### UX Acceptance Criteria + +The v0.5 deliverable MUST meet these explicit criteria (verified in P8 audit): + +1. **REQ-033**: `x/exit` + `x/bridge` each have `keeper/msg_server.go` + `types/msg_*.go` + `types/expected_keepers.go` + `module.go`; `go test ./x/exit/... ./x/bridge/...` passes; simtest covers ExitStatus lifecycle + IBC replay/timeout (A-513); coverage ≥80% on both keeper packages; G-003 intact (no `x/bridge/types` struct import in `x/exit` production code). +2. **REQ-034**: `x/bearers` has `keeper/msg_server.go` + `keeper/transport.go` + `types/msg_bearer*.go` + `types/session.go`; OY-QR one-shot (consumed-before-transfer A-521); surveillance-resistant negative test; `BearerTypeCount=6` unchanged. +3. **REQ-035**: `x/partner` has `keeper/msg_server.go` + `types/msg_anchor*.go` + `types/expected_keepers.go` (WatcherKeeper + HubKeeper shims); Anchor credential lifecycle (Pending→Onboarded→Suspended→Revoked); P3→P4 hub dep broken by shim; `PartnerTierCount=4` unchanged. +4. **REQ-036**: `x/hub` has `types/keyring.go` (CustodyKeyring interface D-058) + `keeper/keyring_mem.go` (memKeyring) + `keeper/custody_state.go` + `keeper/msg_server.go`; custody Msg names AVOID "deposit" (A-542); compliance-before-custody (A-544); lending coupon clamp [0, 800] at runtime (A-543); `HubServiceCount=3` + `LendingCouponCapBps=800`/`LendingCouponFloorBps=0` unchanged; REQ-030 cross-const test green. +5. **REQ-037**: `x/services` has `keeper/msg_server.go` + `types/msg_*.go` (per-kind typed dispatch A-551); window-grant-on-every-op (A-552); `ServiceKindCount=4` unchanged. +6. **REQ-038**: `x/bond` has `keeper/msg_server.go` + `keeper/clob.go`; CLOB price-time priority (REQ-007); per-match clamp REJECT above 800 (D-063/A-562); no AMM (D-057); `CouponCapBps=800`/`CouponFloorBps=0` unchanged (D-028); REQ-030 cross-const test green; `OrderSideCount=2`/`OrderStatusCount=3` unchanged. +7. **REQ-039**: `x/council` has `keeper/msg_server.go` + `types/msg_*.go`; `Proposal`/`ProposalKind`(4)/`ProposalStatus`(5)/`VoteOption`(4) enums added (D-060); `MissionLockAmendment-Rejected` rejected at `ValidateBasic` (D-064/A-572); Watcher Veto quorum default 6 (D-065/A-574); single-Veto-no-block (anti-greed); `MissionLockAmendable=false` unchanged (v0.2 regression green); `SignalKindCount=4` unchanged (v0.4 regression green); `CouncilKindCount=3` unchanged; no proposal auto-execution. +8. **Feature purity gate (P8)**: no breaking schema changes (v0.3 `types/` contracts NOT amended); locked-const firewall intact (all v0.1..v0.4 consts unchanged; new P7 enums per D-060); G-003 production firewall intact (`expected_keepers.go` are interfaces); G-006 controlled exception GRILL-ratified (D-055/D-062). +9. **No regression**: `go test ./...` green; v0.4 coverage floor (93.3% on `x/hub/types`, 96.4% on `x/council/types`) not reduced on the `types/` packages; v0.1..v0.4 baseline tests green. +10. **D-055/D-062 dep**: `go.mod` has cosmos-sdk v0.50.x + ibc-go v8.x (GRILL-ratified); `types/` packages gain `sdk.Msg` imports for `Msg*` (isolated in `types/msg_*.go`); invariant/lexicon tests stay stdlib-only and green. \ No newline at end of file diff --git a/.ciagent/oy/PROJECT.md b/.ciagent/oy/PROJECT.md index ca6108d..6160e72 100644 --- a/.ciagent/oy/PROJECT.md +++ b/.ciagent/oy/PROJECT.md @@ -61,6 +61,49 @@ OpenYield (OY) is a durable, anti-greed, jurisdiction-light financial layer — - D-009: Rebased history to fix v1.0 → v0.1 in ---ci--- blocks ## Milestone +v0.5 — Bearers Runtime (in progress; feature type; tags run on the v0.4.x patch line) + +### v0.5 Scope (Live-runtime promotions of the v0.3 Bearers skeletons) + +v0.5 promotes the v0.3 Bearers skeletons from type+keeper-stub layers to live +runtime behavior. This is the first milestone to ship executable behavior +beyond invariant tests — keepers gain message handlers, transactions, and +end-to-end flows. Sourced from the v0.3/v0.4 deferred items (D-050, +PROJECT.md v0.4 out-of-scope, ROADMAP Phase 3 "The Bearers" runtime subset). + +The skeleton-first pattern (D-020) continues to govern NEW components, but +v0.3-era modules (`x/exit`, `x/bridge`, `x/bearers`, `x/partner`, `x/hub`, +`x/services`, `x/bond`) gain runtime implementations this milestone. No live +chain launch (D-020 continues to apply to network deployment); runtime here +means keeper message handlers + simtest-grade end-to-end flows, not mainnet. + +- **REQ-033** Exit layer runtime — `x/exit` DEX swap routing + bridge message handlers; `x/bridge` L2↔L1 IBC packet handlers. Promotes REQ-010 from skeleton → runtime. Live DEX/IBC channels still deferred. +- **REQ-034** Bearers transport runtime — OY-SAT + OY-QR bearer transport message handlers in `x/bearers` (extends REQ-019). Hardware integration deferred; runtime = message-handling + session lifecycle in simtest. +- **REQ-035** Anchors onboarding runtime — `x/partner` Anchor tier credential issuance + revocation handlers (extends REQ-018). Real institutional onboarding deferred; runtime = credential lifecycle in simtest. +- **REQ-036** Hub API B2B runtime — `x/hub` custody, lending primitive, compliance message handlers. Real B2B suite deferred; runtime = keeper handlers + simtest. +- **REQ-037** Services runtime — `x/services` Care / SIM / Vault / Mail service lifecycle handlers. Live service integrations deferred; runtime = lifecycle handlers + simtest. +- **REQ-038** Bond market depth runtime — `x/bond` Growth Bonds + secondary-market matching handlers (extends REQ-021). Live market depth deferred; runtime = matching engine + simtest. +- **REQ-039** Council governance runtime — `x/council` Proposal/VoteOption enum types (AUDIT §193 P1-1, deferred from v0.4) + Voice lifecycle handlers. Mission Lock const firewall intact (G-003); runtime = governance message handlers + simtest. + +### Milestone Type +Feature (all execution phases are `feat`). Phase 0 → `v0.4.0`; execution phases `v0.4.1..v0.4.N`; final phase patch `v0.4.(N+1)` IS the v0.5 milestone release. No separate minor tag. The final-phase audit enforces the feature purity gate (no breaking schema changes; locked-const firewall intact). + +### Out of Scope (v0.5) +- Live chain launch / mainnet / real IBC channels / real bearer transports (D-020 pattern continues; runtime = simtest-grade message handlers) +- Real institutional Anchors onboarding (credential lifecycle in simtest only) +- Yield Token, Travel + 11 service categories (ROADMAP Phase 4 — Maturity) +- i18n / MkDocs internationalization +- Cover Pool seniority mechanics (still deferred per PROJECT.md Q7) +- Breaking schema changes / locked-const amendments (Mission Lock non-amendable) +- SignalKind 4→5 enum expansion (AUDIT §193 P1-2; defensible per current rationale, deferred to v0.6+ governance vote) + +### Prior Milestones +- v0.1 — OpenYield Foundation Init (COMPLETE; pre-MVP foundation skeleton; released as v0.0.9) +- v0.2 — The Mesh (COMPLETE; skeleton + tests; released as v0.1.5) +- v0.3 — Bearers & Documentation (COMPLETE; feature; released as v0.2.6) +- v0.4 — Refinement (COMPLETE; NFR; released as v0.3.4) + +## Prior Milestone v0.4 — Refinement (complete; NFR type; tags ran on the v0.3.x patch line) ### v0.4 Scope (Refinement-only NFR — v0.3 post-hoc forward-references) @@ -89,9 +132,6 @@ NFR (all phases are refactor/test/quality/chore). Phase 0 → `v0.3.0`; executio - v0.2 — The Mesh (COMPLETE; skeleton + tests; released as v0.1.5) - v0.3 — Bearers & Documentation (COMPLETE; feature; released as v0.2.6) -## Prior Milestone -v0.3 — Bearers & Documentation (complete; feature type; tags ran on the v0.2.x patch line) - ### v0.3 Scope (Bearers skeleton + Docs site — ROADMAP Phase 3 partial, plus a docs deliverable) This milestone bundles two parallel work-streams under one feature milestone: @@ -188,4 +228,19 @@ Auto-decided defaults logged per clarify workflow Step 4 (full autonomy → acce | D-050 | **REQ-031 lifecycle type shape-divergence review scope = DOCUMENT only, no code shape changes**. AUDIT §193 P1-1 (council Proposal/VoteOption absent) and P1-2 (SignalKind 4 vs 5 sources) are `feat:`-class additions (new enum types / locked-const shape changes) and are REJECTED by the D-001 refinement-only filter. v0.4 REQ-031 ships an ARCHITECTURE.md section documenting the divergence decisions (P1-2 defensible per AUDIT code rationale; P1-1 deferred to v0.5+ governance runtime) + a test asserting the current `SignalKindCount==4` locked-const shape is intentional (regression guard, not a shape change). | Adding Proposal/VoteOption enums is `feat:`; changing SignalKind 4→5 is a locked-const change. Both are out-of-scope for an NFR milestone. Documentation + a regression-guard test are NFR-eligible. | 0.82 | [add Proposal/VoteOption enums (feat:, deferred to v0.5+)] | | D-051 | **REQ-032 docs build CI = Gitea Actions workflow** at `.gitea/workflows/docs-build.yml` running `go test ./...` (lexicon firewall) + `mkdocs build` on every push; upload `site/` as a CI artifact. Full Gitea Pages publishing is deferred (no hosting target configured in v0.4). The workflow file itself is a `chore` (config, not feature). | D-046 forward-reference. `.github/workflows/` does not exist; Gitea Actions uses `.gitea/workflows/`. Build+artifact CI is `chore` (NFR-eligible); full Pages publish needs a hosting target (deferred). | 0.80 | [include full Gitea Pages publish (needs hosting target + secrets)] | | D-052 | **Phase ordering** (provisional, planner finalizes): P1 lexicon hardening (REQ-029 + REQ-030 — same `lexicon`/test territory, vertical slice) → P2 lifecycle divergence documentation + regression guard (REQ-031) → P3 docs build CI (REQ-032) → P4 final review + audit + milestone ship. Each phase independently shippable; P1 lands the firewall durability fixes first (highest-severity regression risk). | P1 bundles the two lexicon/const firewall fixes (same territory); P2 is documentation+test; P3 is CI config. Vertical slices. | 0.80 | [different wave ordering] | -| D-053 | **No IDEATE stage in v0.4** (no `--ideate` flag this run). The NFR scope was pre-seeded from v0.3 forward-references and ratified at CLARIFY. If `--ideate` is passed on a later v0.4 run, the D-001 refinement-only filter applies. | run.md §IDEATE is conditional on `--ideate`. This invocation has no `--ideate`. | 1.00 | [run IDEATE anyway] | \ No newline at end of file +| D-053 | **No IDEATE stage in v0.4** (no `--ideate` flag this run). The NFR scope was pre-seeded from v0.3 forward-references and ratified at CLARIFY. If `--ideate` is passed on a later v0.4 run, the D-001 refinement-only filter applies. | run.md §IDEATE is conditional on `--ideate`. This invocation has no `--ideate`. | 1.00 | [run IDEATE anyway] | + +## Clarification Decisions (Phase 0 v0.5 — CLARIFY, autonomy=full) + +Auto-decided defaults logged per clarify workflow Step 4 (full autonomy → accept defaults, log decisions). No `--ideate` flag this run; v0.5 scope is pre-seeded from PROJECT.md v0.4 out-of-scope + AUDIT §193 P1-1 + D-050 and ratified at CLARIFY. + +| ID | Decision | Rationale | Confidence | Alternatives | +|----|----------|-----------|------------|--------------| +| D-054 | **"Runtime" = simtest-grade keeper message handlers + end-to-end flows, NOT mainnet.** v0.5 ships executable keeper behavior (MsgServer handlers, keeper Set/Get/Remove, simtest `simtest`-package flows) for the v0.3 Bearers modules. No live chain launch, no real IBC channels, no real bearer transports, no real institutional onboarding (D-020 pattern continues to govern network deployment). | v0.3 skeletons are types + keeper stubs + invariant tests. The next increment is message handlers + simtest, which is the Cosmos-SDK standard pre-mainnet step. Mainnet deployment is a Year-3+ operational concern (Watchers + Root Basket backing required). | 0.88 | [full mainnet launch in v0.5; types-only with no handlers (stalls progress)] | +| D-055 | **Cosmos SDK dependency is GRILL-approved for v0.5.** `go.mod` gains `github.com/cosmos/cosmos-sdk` (and transitive deps) as the runtime substrate for keeper MsgServer handlers, `types.Msg`, `sdk.Context`, store, and simtest. This is a controlled exception to G-006 (zero-dep go.mod), escalated to GRILL for binding ratification. The exception is scoped to runtime promotion phases (P1..P7); P0 and the final phase remain dep-neutral where possible. | v0.3 skeletons used stub `keeper.go` files that already import cosmos-sdk (see LSP errors on `x/watcher/keeper/keeper.go` — pre-existing imports). Promoting to runtime makes the dependency load-bearing rather than stub-only. G-006's intent (zero-dep for skeleton durability) is preserved by isolating the dep to runtime phases and keeping types/invariants dep-free. | 0.80 | [stay zero-dep, hand-roll keeper store + message types (duplicates SDK, high risk); defer all runtime to v0.6+ (stalls)] | +| D-056 | **Phase ordering** (provisional, planner finalizes): P1 Exit+Bridge runtime (REQ-033, Layer 3 — outermost edge, fewest internal deps) → P2 Bearers transport runtime (REQ-034, depends on exit for off-mesh routing) → P3 Anchors runtime (REQ-035, depends on partner + bearers) → P4 Hub API runtime (REQ-036, depends on anchors for custody backing) → P5 Services runtime (REQ-037, depends on hub) → P6 Bond market runtime (REQ-038, depends on hub lending primitive) → P7 Council governance runtime (REQ-039, cross-cutting, lands last) → P8 final review + audit + milestone ship. Each phase independently shippable; P1 lands the outermost edge first (lowest internal coupling). | The dependency chain is outer→inner: exit needs nothing internal; bearers routes through exit; anchors ride bearers; hub custody backs anchors; services sit on hub; bond matching uses hub lending; governance is cross-cutting. Vertical slices, each phase shippable. | 0.82 | [governance-first; bond-first; single mega-phase] | +| D-057 | **Bond matching engine = central-limit order book (CLOB) with the 8% cap / 0% floor consts (D-028) as hard clamp on each match.** No AMM (constant-product or otherwise) in v0.5; AMM is a Year-4 Maturity concern. The CLOB matches Growth Bond bids/offers at the locked coupon cap; secondary-market trades clear at market price but the bond's *coupon* stays within the mission-locked band. REQ-038 ships the matching handler + simtest; live market depth deferred. | CLOB is the standard secondary-market primitive; AMM is for spot/swaps (Exit layer's DEX, deferred). The mission-lock clamp (D-028) is a per-match invariant, not a market-wide cap. CLOB lets the cap be enforced per-match. | 0.80 | [AMM (wrong fit for coupon-bearing bonds); batch auction (deferred to Maturity)] | +| D-058 | **Hub custody model = key-share abstraction (MPC-via-interface, not a concrete HSM/MPC vendor).** `x/hub` custody handlers expose a `CustodyKeyring` interface with `Sign`/`Derive` methods; v0.5 ships an in-memory test-only implementation. Real MPC/HSM backing is deferred (operational, Year 3+). This keeps v0.5 dep-neutral w.r.t. custody vendors while landing the handler surface. | Custody key management is operational, not protocol-level. An interface + test impl lets runtime handlers be exercised in simtest without committing to a vendor. GRILL reviews the interface boundary. | 0.78 | [commit to a specific MPC vendor (premature); hand-roll shamir (out of scope)] | +| D-059 | **IBC packet scope = the 5 L2 chains already in the v0.2 skeleton** (Polygon, Base, Arbitrum, Optimism, Solana per REQ-009/`x/satellite`). v0.5 `x/bridge` handlers implement IBC packet recv/ack for these 5 chains' `BridgeStatus` transitions. No new L2 chains in v0.5. Solana IBC uses the wormhole-style bridge adapter (already stubbed in `x/bridge` per D-021). | The 5 L2 chains are the locked-const set (REQ-009). Adding new chains is a Year-4 concern. Solana IBC was a v0.1 deferred item (D-021) now promoted. | 0.82 | [add 3+ new L2 chains (Year 4); defer Solana IBC again (stalls)] | +| D-060 | **Council governance shape (AUDIT §193 P1-1)**: add `Proposal` and `VoteOption` enum types to `x/council/types` (currently absent per AUDIT). `ProposalKind` enum = {Stand, Guild, Mesh, MissionLockAmendment-Rejected} (Mission Lock non-amendable → the enum value exists but the handler rejects it; documents the non-amendability in code). `VoteOption` enum = {Yes, No, Abstain, Veto} (Veto = Watcher-only, quorum rule). SignalKind stays at 4 sources (P1-2 defensible per AUDIT; expansion deferred to v0.6+ governance vote). Mission Lock const firewall (G-003) intact. | AUDIT P1-1 flagged the absence as a divergence. Adding the enums is `feat:` (deferred from v0.4 by D-001). P1-2 (SignalKind 4→5) is a locked-const change rejected by the audit rationale, so it stays at 4. | 0.82 | [add SignalKind 5th source (locked-const change, rejected); defer Proposal/VoteOption again (stalls)] | +| D-061 | **No IDEATE stage in v0.5** (no `--ideate` flag this run). The feature scope was pre-seeded from PROJECT.md v0.4 out-of-scope + AUDIT §193 P1-1 + D-050 and ratified at CLARIFY. The D-001 refinement-only filter does NOT apply (v0.5 is a feature milestone, not NFR). | run.md §IDEATE is conditional on `--ideate`. This invocation has no `--ideate`. | 1.00 | [run IDEATE anyway] | \ No newline at end of file diff --git a/.ciagent/oy/REQUIREMENTS.md b/.ciagent/oy/REQUIREMENTS.md index 63fca6a..8242988 100644 --- a/.ciagent/oy/REQUIREMENTS.md +++ b/.ciagent/oy/REQUIREMENTS.md @@ -73,6 +73,30 @@ refinement-only filter applies to any IDEATE stage. - Tags: v0.3.0 (P0) -> v0.3.1 (P1) -> v0.3.2 (P2) -> v0.3.3 (P3) -> v0.3.4 (P4 = v0.4 milestone release) - Tag-line note: v0.4 (NFR) ships on the v0.3.x patch line (config tag_base). The v0.3.4 milestone release IS the deliverable (D-008 — final phase patch IS the milestone release; no separate minor tag). +## v0.5 Milestone Requirements (Bearers Runtime — Feature) + +v0.5 promotes the v0.3 Bearers skeletons from type+keeper-stub layers to +live runtime behavior (keeper message handlers + simtest-grade end-to-end +flows). No live chain launch (D-020 continues to govern network deployment); +runtime = keeper handlers + simtest, not mainnet. Sourced from the v0.3/v0.4 +deferred items (D-050, PROJECT.md v0.4 out-of-scope, ROADMAP Phase 3 runtime). + +| ID | Requirement | Source | Class | Priority | Status | Phase | +|----|-------------|--------|-------|----------|--------|-------| +| REQ-033 | Exit layer runtime — `x/exit` DEX swap routing + `x/bridge` L2↔L1 IBC packet handlers; promotes REQ-010 from skeleton → runtime (simtest-grade message handlers; live DEX/IBC channels deferred) | PROJECT.md v0.4 OOS / D-050 | feat | High | pending | v0.5/P1 | +| REQ-034 | Bearers transport runtime — OY-SAT + OY-QR bearer transport message handlers in `x/bearers` (extends REQ-019/REQ-022); session lifecycle in simtest (hardware integration deferred) | PROJECT.md v0.4 OOS | feat | Medium | pending | v0.5/P2 | +| REQ-035 | Anchors onboarding runtime — `x/partner` Anchor tier credential issuance + revocation handlers (extends REQ-018/REQ-023); credential lifecycle in simtest (real institutional onboarding deferred) | PROJECT.md v0.4 OOS | feat | Medium | pending | v0.5/P3 | +| REQ-036 | Hub API B2B runtime — `x/hub` custody, lending primitive, compliance message handlers; keeper handlers + simtest (real B2B suite deferred) | PROJECT.md v0.4 OOS | feat | High | pending | v0.5/P4 | +| REQ-037 | Services runtime — `x/services` Care / SIM / Vault / Mail service lifecycle handlers; runtime handlers + simtest (live service integrations deferred) | PROJECT.md v0.4 OOS | feat | Medium | pending | v0.5/P5 | +| REQ-038 | Bond market depth runtime — `x/bond` Growth Bonds + secondary-market matching handlers (extends REQ-021/REQ-026); matching engine + simtest (live market depth deferred) | PROJECT.md v0.4 OOS | feat | High | pending | v0.5/P6 | +| REQ-039 | Council governance runtime — `x/council` Proposal/VoteOption enum types (AUDIT §193 P1-1, deferred from v0.4) + Voice lifecycle handlers; governance message handlers + simtest (Mission Lock const firewall intact per G-003; SignalKind 4→5 expansion deferred to v0.6+) | AUDIT §193 P1-1 / D-050 | feat | Medium | pending | v0.5/P7 | + +> REQ-033..REQ-039 are NEW in v0.5. All are `feat`-class (runtime promotion +> from skeleton). No breaking schema changes; locked-const firewall intact +> (Mission Lock non-amendable). The final-phase audit enforces the feature +> purity gate (no breaking schema changes; G-003 production firewall intact; +> G-006 go.mod unchanged unless a runtime dep is GRILL-approved). + ## IDEATE Traceability (Phase 0 — IDEATE stage, autonomy=full) The IDEATE stage ran the three ideation tiers (mechanical, backend-enriched, diff --git a/.ciagent/oy/RESEARCH.md b/.ciagent/oy/RESEARCH.md index ec5ff82..c519736 100644 --- a/.ciagent/oy/RESEARCH.md +++ b/.ciagent/oy/RESEARCH.md @@ -1288,4 +1288,910 @@ independent of the Bearers phases. **New modules: 4 (exit, bridge, hub, services). Extended modules: 3 (bearers, partner, bond). Docs surface: new (docs/, mkdocs.yml, README.md). Firewall: 1 -new sibling test. Total v0.3: 4 new + 3 extended + docs + 1 firewall test.** \ No newline at end of file +new sibling test. Total v0.3: 4 new + 3 extended + docs + 1 firewall test.** + +--- + +# Research: OpenYield (oy) — Phase 0 (v0.5 — Bearers Runtime) + +> This section appends v0.5 research to the v0.1/v0.2/v0.3/v0.4 baseline +> above. It does NOT rewrite or supersede the earlier content. v0.5 is the +> first **feature** milestone to ship executable behavior beyond invariant +> tests: the v0.3 Bearers skeletons (`x/exit`, `x/bridge`, `x/bearers`, +> `x/partner`, `x/hub`, `x/services`, `x/bond`, plus the cross-cutting +> `x/council`) are promoted from **types + in-memory keeper stubs + +> invariant tests** to **live keeper MsgServer message handlers + simtest- +> grade end-to-end flows**. This is NOT mainnet (D-020 continues to govern +> network deployment; D-054 ratifies runtime = simtest-grade handlers, not +> live chain). Tags run on the `v0.4.x` patch line (config.json `tag_base`). + +## v0.5 Scope Recap (from D-054..D-061) + +v0.5 promotes seven v0.3-era skeleton modules to runtime, plus the council +governance enum additions deferred from v0.4 (AUDIT §193 P1-1, D-050). The +promotion pattern is uniform: each module gains a `keeper/` subdir with a +Cosmos-SDK-style `MsgServer` (message handlers consuming `sdk.Context` + +the store), `types.Msg*` message types implementing `sdk.Msg` (ValidateBasic + +GetSigners), and a `simtest/` (or module-internal `*_simtest_test.go`) end-to- +end flow exercising the handler against an in-memory keeper. The existing +`types/` packages stay as the lexicon-locked type contracts (their locked +consts, enums, and structs are NOT amended — the runtime layer adds behavior +on top, not changes to the contract). + +| REQ | Module(s) | Promotion (skeleton → runtime) | Phase (D-056) | +|---|---|---|---| +| REQ-033 | `x/exit`, `x/bridge` | DEX swap routing handlers + L2↔L1 IBC packet recv/ack/timeout handlers for the 5 locked L2 chains | P1 | +| REQ-034 | `x/bearers` | OY-SAT + OY-QR message handlers; session lifecycle in simtest | P2 | +| REQ-035 | `x/partner` | Anchor tier credential issuance + revocation handlers | P3 | +| REQ-036 | `x/hub` | Custody / LendingPrimitive / Compliance message handlers; `CustodyKeyring` interface + in-memory test impl | P4 | +| REQ-037 | `x/services` | Care / SIM / Vault / Mail service lifecycle handlers | P5 | +| REQ-038 | `x/bond` | Growth Bond issuance + secondary-market CLOB matching handlers; 8% cap / 0% floor per-match clamp (D-057) | P6 | +| REQ-039 | `x/council` | `Proposal` + `VoteOption` enum types (AUDIT §193 P1-1) + Voice lifecycle handlers; Mission Lock const firewall intact | P7 | + +**Dependency direction (D-056):** P1 exit (outermost edge, fewest internal +deps) → P2 bearers (routes through exit) → P3 anchors (rides bearers) → P4 +hub (custody backs anchors) → P5 services (sits on hub) → P6 bond (uses hub +lending primitive) → P7 council (cross-cutting, lands last) → P8 final +review/audit/ship. Each phase independently shippable (vertical-slice +integrity, same as v0.1..v0.4). + +--- + +## v0.5 §1. Runtime Promotion Pattern — Skeleton-to-Runtime Delta + +The v0.3 skeleton baseline (verified against the current tree): each target +module has only a `types/` subdir containing `types.go` (pure-Go structs + +locked consts + enums), `genesis.go` (ValidateGenesis), and `*_test.go` +(invariant + lexicon tests). The in-memory `Keeper` stub lives INSIDE +`types/types.go` (e.g., `x/partner/types/types.go:101 type Keeper struct{...}`, +`NewKeeper()` returns `&Keeper{partners: make(map[string]Partner)}`). There +is NO `keeper/` subdir, NO `msg_server.go`, NO `types.Msg*`, NO `sdk.Context`, +and NO cosmos-sdk import anywhere in `x/` (grep for `cosmos-sdk` / +`sdk.Context` / `cosmos/cosmos` returns zero matches — verified). + +### v0.5 §1.1 The MsgServer promotion pattern (Cosmos SDK convention) + +**Prior art / ecosystem references:** +- **Cosmos SDK `MsgServer`** — the canonical pattern since cosmos-sdk v0.40+ + (the Stargate refactor). Each module's `keeper/` package exposes a + `MsgServer` struct that wraps the module `Keeper` and implements one method + per message type (e.g., `func (ms msgServer) Send(ctx, msg) (*MsgSendResponse, + error)`). The `types.Msg*` structs implement `sdk.Msg` (`ValidateBasic()`, + `GetSigners()`), are registered with the module's codec, and routed by the + base app's `MsgServiceRouter`. This is the universal pre-mainnet step in the + Cosmos-SDK module lifecycle (every ibc-go, Osmosis, Celestia, dYdX module + follows it). +- **`types.Msg*` + `ValidateBasic`** — the message validation gate runs + statelessly before the handler; `ValidateBasic` rejects malformed messages + early (negative amounts, empty ids, invalid enums). The handler then does + stateful validation against `sdk.Context` (duplicate-id checks, authz, + capacity). +- **`sdk.Context` + store** — the keeper reads/writes the module's prefixed + `sdk.KVStore` via `ctx.KVStore(ms.storeKey)`. The v0.3 in-memory + `map[string]T` keeper is replaced by (or wrapped behind) a store-backed + keeper; simtest exercises it against an in-memory `commit-db` (the SDK's + `dbm` in-memory backend), not a real CometBFT store. + +**Skeleton-to-runtime delta (uniform across all 8 target modules):** + +| Aspect | v0.3 skeleton | v0.5 runtime | +|---|---|---| +| Keeper location | `types/types.go` (in-memory `map[string]T`) | `keeper/keeper.go` (store-backed, wraps `sdk.KVStore`); the v0.3 in-memory stub may stay as a test helper or be retired | +| Message types | none | `types/msg_*.go` with `Msg*` structs implementing `sdk.Msg` (`ValidateBasic`, `GetSigners`, proto-ish `ProtoMessage` via legacy amino or the SDK's `codec.JSONCodec`) | +| Message handlers | none | `keeper/msg_server.go` with `MsgServer` + one `*Response, error` method per `Msg*` | +| Module wiring | none | `module.go` (` AppModuleBasic` / `AppModule` with `RegisterServices` registering the `MsgServer`) — simtest may use a lighter `ModuleManager` shim | +| End-to-end test | invariant tests only | `simtest/` (or `keeper/msg_server_simtest_test.go`) exercising each handler against an in-memory `sdk.Context` | +| Dependency | stdlib only | `github.com/cosmos/cosmos-sdk` (+ transitive) per D-055 (GRILL-approved controlled exception to G-006) | + +**Pitfall — state machine ordering:** the handler must do stateful validation +in a stable order (authz → capacity/lock → state transition → event emit). +Reordering causes double-spend / replay. The Cosmos-SDK convention is: +(1) `ValidateBasic` (stateless, in the msg), (2) keeper authz check, (3) +state mutation under the store, (4) `ctx.EventManager().EmitEvent(...)`. Each +v0.5 handler follows this order; simtest asserts the event is emitted and the +state is durably written. + +**Pitfall — replay protection:** `sdk.Msg` carries `GetSigners()`; the base +app deduplicates by tx hash + sequence. For messages that MUST be one-shot +(e.g., OY-QR consume, Anchor credential revocation, bond matching fill), the +handler additionally flips an idempotency flag in state (e.g., `consumed = +true` on the OYQRCode, already in the v0.3 skeleton) so a replayed tx is a +no-op rather than a double-effect. This mirrors ibc-go's packet-replay +protection (the sequence + packet-commitment pair). + +**Confidence (planner-relevant):** +- A-501: every v0.5 target module gains a `keeper/` subdir + `msg_server.go`, + leaving the existing `types/` package as the locked-contract layer. **0.92** +- A-502: the v0.3 in-memory `Keeper` stub (in `types/types.go`) is retained + as a test-only construct or replaced by the store-backed keeper; the + `types/` package's public API is NOT broken (no struct field removal). **0.80** +- A-503: simtest uses the SDK in-memory store (not a real CometBFT node); + no live chain, no real IBC light clients (D-054). **0.90** + +--- + +## v0.5 §2. Per-REQ Runtime Research + +### v0.5 §2.1 REQ-033 — Exit + Bridge runtime (P1) + +**What it is:** `x/exit` gains DEX swap routing handlers +(`MsgSubmitExitRoute` → status transitions through the v0.3 `ExitStatus` +enum; `MsgExecuteDEXSwap` → venue-hop execution producing a `DEXSwap` record +and a `Settled`/`Failed`/`Refunded` terminal state). `x/bridge` gains L2↔L1 +IBC packet handlers for the 5 locked L2 chains (Polygon/Base/Arbitrum/ +Optimism/Solana per REQ-009/D-059): `OnRecvPacket`, `OnAcknowledgementPacket`, +`OnTimeoutPacket` driving the v0.3 `BridgeStatus` lifecycle (Pending → +Attested → Active → Closed). + +**Prior art / ecosystem references:** +- **ibc-go `IBCModule` / `applications/transfer`** — the canonical + application-layer pattern: a module implements + `ibcapp.PacketExecutor` (v2) or the legacy `IBCModule` interface + (`OnRecvPacket`, `OnAcknowledgementPacket`, `OnTimeoutPacket`, + `OnChanOpenInit/Confirm/Ack`). IBC v2 (ibc-go v10, D-021's reference) uses + typed `Payload`s and timestamp-only timeouts; the recv handler returns an + acknowledgement byte sequence (the "ack"); a failed ack is non-replayable. + OY's `x/bridge` recv handler mirrors this contract but operates on the + v0.3 `BridgeRoute` + a payload shape pinned to ICS-20 v1 (the v0.2 + satellite packet shape). +- **IBC Eureka** — ibc-go v10's Eureka path (Cosmos↔Ethereum-style) is + relevant for the 4 EVM L2 chains (Polygon/Base/Arbitrum/Optimism); the + timestamp-only timeout model avoids the height-timeout ambiguity that + breaks EVM chains without a reliable block-height clock. +- **Wormhole-style adapter for Solana** — Solana lacks native IBC (v0.1 + RESEARCH §1.1; D-021). D-059 promotes the v0.1 stubbed wormhole-style + adapter to a runtime handler: Solana packets are recv'd via a + wormhole-portal-shaped envelope (VAA — Verified Action Approval) and the + `x/bridge` handler verifies the guardian signature set (a 2-of-N quorum, + N being the wormhole guardian set) before transitioning the route. The + adapter is typed in v0.3 (bridge-type opaque string "wormhole"); v0.5 + adds the verification handler. No live wormhole integration (D-054) — the + simtest uses a stub guardian set. +- **DEX routing** — 1inch / 0x API / Paraswap path optimization is the off- + chain analog; the on-chain handler executes a PRE-COMPUTED hop sequence + (the v0.3 `ExitRoute.venue-hops []string`), it does not run an off-chain + pathfinder. dYdX v4 / Sei v2 execute pre-routed swaps similarly. + +**Skeleton-to-runtime delta:** +- `x/exit`: v0.3 had `ExitRoute` + `DEXSwap` structs + `ExitStatus` enum + + keeper stub (AddExitRoute/GetExitRoute/ListByHolder). v0.5 adds + `MsgSubmitExitRoute`, `MsgExecuteDEXSwap`, `MsgRefundExit` (on Failed) + implementing `sdk.Msg`; a `keeper/msg_server.go` with the three handlers + driving status transitions; a simtest asserting the full + Proposed→InProgress→Settled/Failed/Refunded path. The `bridge-route-id` + field (by-ID-string ref to x/bridge per G-003) is now ACTED on: a cross- + chain exit invokes the `x/bridge` recv path (via the module's keeper + interface, NOT a struct import — the G-003 by-ID-string rule survives the + runtime promotion; the keeper-to-keeper call uses a typed interface defined + in a shared `types/expected_keepers.go` shim, the ibc-go convention). +- `x/bridge`: v0.3 had `BridgeRoute` + `BridgeStatus` enum + keeper stub. + v0.5 adds `MsgAttestBridgeRoute` (Watcher-quorum-driven transition + Pending→Attested, referencing `x/watcher` by ID), `MsgActivateBridge`, + `MsgCloseBridge`, and the IBC trio `OnRecvPacket` / `OnAcknowledgementPacket` + / `OnTimeoutPacket`. The recv handler parses the ICS-20 v1 payload + (denom, amount, sender, receiver), validates the denom trace against the + v0.2 `WrappedBreadDenom`, and mints/releases wrapped Bread via the + `x/bread` keeper interface (expected-keeper shim). Solana packets go + through the wormhole-adapter verification branch. + +**Pitfalls:** +- **IBC ack/timeout handling:** a timeout MUST revert the escrow (ibc-go's + `OnTimeoutPacket` refunds the source-chain escrow). Forgetting the refund + is a classic ibc-go bug class (CVE-class). The simtest MUST include a + timeout-replay case asserting the escrow is refunded exactly once. +- **Ack idempotency:** a duplicate ack (relayer replay) must be a no-op; + ibc-go tracks the packet commitment and deletes it on ack, so a second ack + finds no commitment and returns. v0.5's handler must mirror this (delete + the in-flight record on first ack; reject on second). +- **Denom trace drift:** if the `WrappedBreadDenom` parsing diverges from + ibc-go's `Denom.Trace` path-split, the bridge mis-attributes wrapped + tokens. Pin the trace parser to the v0.2 satellite shape (ICS-20 v1, + `transfer/channel-N/`). +- **Solana adapter signature-set rotation:** the wormhole guardian set + rotates; the handler must read the CURRENT guardian set from state, not a + hardcoded one. Simtest uses a frozen guardian set; a rotation test is a + later concern (deferred, D-054). + +**Confidence-scored conclusions:** +- A-511: `x/bridge` IBC handlers implement the ibc-go `OnRecvPacket` / + `OnAcknowledgementPacket` / `OnTimeoutPacket` contract operating on the + v0.3 `BridgeRoute` + ICS-20 v1 payload; Solana via the wormhole-adapter + branch. **0.82** +- A-512: keeper-to-keeper cross-module calls use `expected_keepers.go` + interface shims (ibc-go convention), NOT struct imports — G-003 by-ID- + string rule is preserved at the type level; the runtime adds interface- + typed keeper dependencies. **0.85** +- A-513: IBC ack/timeout replay protection mirrors ibc-go (delete-on-ack, + refund-on-timeout); the simtest MUST cover both replay and timeout-refund + cases. **0.90** + +--- + +### v0.5 §2.2 REQ-034 — Bearers transport runtime (P2) + +**What it is:** `x/bearers` gains OY-SAT and OY-QR message handlers +(`MsgSendOYSATFrame`, `MsgReceiveOYSATFrame`, `MsgIssueOYQR`, `MsgConsumeOYQR`) +and a session-lifecycle keeper. Hardware integration is deferred (D-054); +runtime = message-handling + session lifecycle in simtest. The v0.3 +`OYSATLink` / `OYQRCode` structs (with the `surveillance-resistant` locked +flag and the one-shot `consumed` flag) become the handler state objects. + +**Prior art / ecosystem references:** +- **Session lifecycle in messaging transports** — XMTP / Session / + Matrix (DTN-style store-and-forward) model a session as a sequence of + frames bound by a session-id, with send/receive/deliver states. OY's + bearer session is closest to a DTN bundle (RFC 5050) with a ttl + a + delivery-confirmation ack. The `BearerTransport` interface (v0.2 stub) + gains a `Send`/`Receive`/`Status` runtime impl backed by the keeper store. +- **Bearer token revocation** — OAuth 2.0 token revocation (RFC 7009): + a token can be revoked before expiry. OY's session has a `Revoke` handler + (mirrors the v0.2 Window `Revoke`); a revoked session rejects further + `Receive` calls. Macaroon caveat revocation (Google macaroons) is the + closest ocap analog. +- **One-shot QR / NFC bearer** — Bolt Card (NFC + QR Lightning) is the + closest production analog: a QR/NFC payload is consumed on first scan; + a replay is rejected. The v0.3 `OYQRCode.consumed` flag is the runtime + idempotency gate; the `MsgConsumeOYQR` handler flips it atomically. +- **Surveillance-resistant transports** — Helium (LoRa coverage, public), + Nodle (BLE mesh), B.A.T.M.A.N. routing. OY-LR/OY-SAT are surveillance- + resistant (vision §14, locked flag); the handler does NOT log + geolocation or sender identity beyond the reach-id (the lexicon-clean + holder identifier). The `surveillance-resistant` const is a runtime + invariant (a handler that emits geolocation data violates it). + +**Skeleton-to-runtime delta:** +- v0.3: `OYSATLink` + `OYQRCode` structs + `BearerTransport` interface + (shape stubs, no impl) + locked-const test that `BearerOYSAT` / + `BearerOYQR` are in `AllBearers()` (6 bearers, locked since v0.1). +- v0.5: a `keeper/` with `MsgServer` implementing the four messages; a + `Session` struct (session-id, bearer-type, initiator-reach, peer-reach, + status [Open/Active/Closed/Revoked], frames []Frame, ttl, opened-at, + closed-at) stored under the keeper; the `BearerTransport` interface gains + a store-backed impl (the keeper IS the transport for simtest purposes — + no hardware). `MsgConsumeOYQR` is the canonical one-shot handler: it + loads the QR, asserts `!consumed`, asserts `expires-at > now`, flips + `consumed`, emits the transfer effect (a `x/bread` grain transfer via + the expected-keeper shim), emits an event, returns. A replay finds + `consumed == true` and returns an error (idempotent reject, not double- + effect). + +**Pitfalls:** +- **Session state machine ordering:** Open → Active (on first frame ack) → + Closed (on last frame or ttl expiry) → Revoked (out-of-band). A frame + received on a Closed/Revoked session MUST be rejected. Simtest covers all + four transitions + the rejected-frame case. +- **One-shot replay:** the `consumed` flag is the only replay firewall for + OY-QR. If the handler does the transfer BEFORE flipping `consumed`, a + crash between the two leaves a double-spend window. The handler MUST flip + `consumed` FIRST (state write), THEN do the transfer (the SDK store is + atomic per tx — a panic rolls back the whole tx, so the order is safe; + but the order documents intent and matches the ibc-go delete-before-mint + convention). +- **Surveillance-resistance invariant:** the `surveillance-resistant` + const is a compile-time lock; a runtime handler that emits a + geolocation event or logs the sender's physical location violates the + spirit. Simtest asserts the event set contains NO geolocation fields + (a negative test). +- **Lexicon:** "session", "frame", "bearer", "QR", "SAT" are lexicon-safe. + Avoid "account"/"deposit" (use reach-id/Stash by ID). + +**Confidence-scored conclusions:** +- A-521: OY-QR is one-shot; `MsgConsumeOYQR` flips `consumed` before the + transfer effect; replay is rejected idempotently. **0.88** +- A-522: the `BearerTransport` interface gains a store-backed impl (the + keeper acts as the transport in simtest); no hardware/RF dep is added + (D-054). **0.85** +- A-523: session lifecycle mirrors Window's lifecycle (Open/Active/Closed/ + Revoked) for consistency with the v0.2 Window primitive. **0.78** + +--- + +### v0.5 §2.3 REQ-035 — Anchors onboarding runtime (P3) + +**What it is:** `x/partner` Anchor tier gains credential issuance and +revocation handlers. The v0.3 `AnchorCredential` struct (partner-id, +jurisdiction, custody-provider-id, attestation-refs, onboarded-at) becomes +the state object of a credential lifecycle: `Pending → Onboarded → +Suspended → Revoked` (reusing the v0.2 PartnerStatus 4-state shape). + +**Prior art / ecosystem references:** +- **Verifiable Credential lifecycle** — W3C VC Data Model ( issuance → + verification → revocation); the revocation is the highest-risk operation + (a revoked credential used post-revocation is a fraud vector). OY's + revocation handler flips the status; downstream consumers (hub custody, + services) check the status before acting. +- **Institutional onboarding** — MakerDAO RWA arrangers (legal repr + + off-chain agreements), Centrifuge tier sponsors, Maple underwriting, + Ondo institutional wrappers. OY's Anchor is the protocol-level abstraction; + the v0.5 runtime is the credential lifecycle, NOT the real off-chain + legal onboarding (deferred). +- **Credential revocation lists** — CRL (X.509), OCSP (online status), + Verifiable Credential revocation (BitstringStatusList). OY's keeper + stores the status in-state; downstream queries check the keeper (an + OCSP-like online check, no separate CRL distribution). + +**Skeleton-to-runtime delta:** +- v0.3: `AnchorCredential` struct + `Keeper.AddAnchorCredential` / + `ListByTier(TierAnchor)` stub (in-memory). +- v0.5: `MsgIssueAnchorCredential` (issuer must be a Watcher-authorized + onboarding party — checked via the `x/watcher` expected-keeper shim by + ID; the credential starts `Pending`), `MsgOnboardAnchor` (transitions to + `Onboarded` after attestation-refs are populated + custody-provider-id is + set — the hub custody service must exist, checked via the `x/hub` + expected-keeper shim by ID), `MsgSuspendAnchorCredential`, `MsgRevokeAnchor + Credential` (only a Watcher quorum or the issuing party can revoke — + authz via the watcher shim). Simtest covers the full lifecycle and the + post-revocation rejection (a downstream custody action on a revoked + credential returns `ErrCredentialRevoked`). + +**Pitfalls:** +- **Revocation race:** a credential revoked while a custody action is in- + flight. The handler checks status at tx start; the SDK store is atomic + per tx, so a concurrent revocation is serialized (one tx wins). Simtest + covers the revoked-during-action case (the action sees the post-revocation + status because txs are serial). +- **Authz:** who can issue / revoke is a Watcher concern (6-of-9 quorum, + REQ-004). The handler delegates the authz check to the `x/watcher` + expected-keeper interface; it does NOT import `x/watcher/types` (G-003 + intact). The interface is defined in `x/partner/types/expected_keepers.go`. +- **Custody-provider-id validity:** the `OnboardAnchor` handler asserts + the custody-provider-id references a live `x/hub` custody service. This + is a P3→P4 cross-phase edge (anchors P3 depend on hub P4). D-056 orders + P3 before P4, so the hub custody keeper interface must EXIST (as a shim) + before P3, even if the hub runtime lands in P4. The shim is a typed + interface in `x/partner/types/expected_keepers.go`; the real impl is + wired in P4. This is the standard ibc-go "expected keepers" pattern for + breaking cross-module dep cycles. + +**Confidence-scored conclusions:** +- A-531: Anchor credential lifecycle = Pending → Onboarded → Suspended → + Revoked (reusing the v0.2 PartnerStatus 4-state shape). **0.82** +- A-532: the P3→P4 hub dependency is broken by an `expected_keepers.go` + interface shim (ibc-go convention); the hub runtime impl is wired in P4. + **0.85** +- A-533: revocation authz delegates to the `x/watcher` expected-keeper + shim (6-of-9 quorum check); no `x/watcher/types` struct import (G-003 + intact). **0.88** + +--- + +### v0.5 §2.4 REQ-036 — Hub API B2B runtime (P4) + +**What it is:** `x/hub` gains custody / lending-primitive / compliance +message handlers. The v0.3 `HubService` enum (3 services) + per-service +struct stubs become the state objects of service lifecycle + per-service +operations. The custody service introduces the `CustodyKeyring` interface +(D-058) with an in-memory test-only impl. + +**Prior art / ecosystem references:** +- **Custody keyring abstractions** — Fireblocks / Anchorage / BitGo expose + MPC / HSM-backed signing via an API; the chain-side abstraction is a + `Keyring` interface (`Sign`, `Derive`, `PubKey`). Cosmos SDK has a + `crypto.Keyring` interface (used for validator keys) — OY's + `CustodyKeyring` is a sibling interface scoped to custody assets, NOT + validator keys. The MPC vendor abstraction is the same shape: the chain + does not hold the private key; it holds a handle that delegates signing + to the MPC cluster. OY's v0.5 ships the interface + an in-memory test + impl (a `memKeyring` that signs with a throwaway ed25519 key); real MPC + is operational, Year 3+. +- **B2B API backbones** — Stripe API (modular resources), Plaid (the + anti-bank analog is structural only), Coinbase Prime (institutional + custody + prime). OY's hub is the on-chain B2B service registry; the + handlers are the on-chain operations, the off-chain B2B integration is + deferred. +- **Lending primitive** — Aave / Compound protocol-level lending; OY's + lending primitive is a Hub-service type, the protocol-level primitive + (not a live market). The 8% cap / 0% floor (D-028) is cross-documented as + the local consts `LendingCouponCapBps = 800` / `LendingCouponFloorBps = 0` + (v0.3, A-304); v0.5's lending handler CLAMPS the coupon to this band per + operation (the REQ-030 cross-const test already guards drift between hub + and bond consts; the v0.5 handler asserts the clamp at runtime too). +- **Compliance** — on-chain compliance attestations (TRM Labs, Elliptic, + Chainalysis). OY's compliance service handler records an attestation ref + (opaque URI) against a partner; a downstream custody action checks the + compliance status before acting. + +**Skeleton-to-runtime delta:** +- v0.3: `HubService` enum (3) + `HubServiceInfo` + per-service structs + (`CustodyService`, `LendingPrimitiveService`, `ComplianceService`) + + keeper stub (AddService/GetService/ListByKind) + local consts + `LendingCouponCapBps`/`LendingCouponFloorBps` (cross-documented to + D-028). +- v0.5: `CustodyKeyring` Go interface in `x/hub/types/keyring.go`: + ```go + type CustodyKeyring interface { + Sign(ctx context.Context, assetID string, payload []byte) ([]byte, error) + Derive(ctx context.Context, assetID string) (PubKey, error) + Status(ctx context.Context, assetID string) (KeyringStatus, error) + } + ``` + + `memKeyring` impl (test-only, in `x/hub/keeper/keyring_mem.go` or + `x/hub/types/keyring_mem_test.go`). Handlers: `MsgRegisterCustodyService` + (operator must be an Onboarded Anchor — checked via `x/partner` + expected-keeper shim), `MsgDepositCustodyAsset` (delegates signing to the + `CustodyKeyring` — the in-memory impl returns a stub signature; the + handler records the custody entry + the sig ref), `MsgWithdrawCustodyAsset` + (authz: only the holder or an authorized Window grantee; checks + compliance status via the compliance expected-keeper shim), + `MsgRecordLendingPrimitive` (clamps coupon to [LendingCouponFloorBps, + LendingCouponCapBps] — the runtime echo of the D-028 / REQ-030 firewall), + `MsgRecordComplianceAttestation`. + +**Pitfalls:** +- **Custody key rotation:** the `CustodyKeyring` interface MUST support a + rotation (a new key takes over for an assetID). The in-memory impl can + rotate trivially (swap the keymap entry); the interface shape must allow + it (the `Status` method reports the active key version). A handler that + caches the pubkey across txs breaks rotation — keepers are per-tx, so + the keyring is consulted per operation (no caching across blocks). +- **Compliance-before-custody ordering:** a withdrawal on a non-compliant + partner MUST be rejected. The handler checks compliance status BEFORE the + custody debit; reversing creates a withdrawal-then-reject race (the debit + lands, the reject fires after). Same state-machine-ordering pitfall as + the general handler pattern (§1.1). +- **Coupon clamp at runtime:** the lending handler clamps the coupon; if a + later change moves the consts (a locked-const amendment, which is + rejected by the Mission Lock firewall), the handler's clamp silently + uses the new value. The REQ-030 cross-const test catches drift between + hub and bond consts; the v0.5 handler additionally emits an event with + the clamped value so simtest can assert the clamp ran. +- **Lexicon:** "custody", "lending", "coupon", "compliance" are safe. + "interest"/"yield"/"deposit"/"savings" banned (use coupon/lending/ + custody/grain). The `MsgDepositCustodyAsset` name is borderline — + "deposit" is banned. **Rename to `MsgCustodyReceiveAsset`** to keep the + message type name lexicon-clean. (This is a v0.5 discovery; the v0.3 + struct fields use "custody" safely, but the message name must not import + "deposit".) + +**Confidence-scored conclusions:** +- A-541: `CustodyKeyring` interface (`Sign`/`Derive`/`Status`) + + `memKeyring` in-memory test impl; real MPC/HSM deferred. **0.88** +- A-542: the custody message names AVOID the banned "deposit" (use + `MsgCustodyReceiveAsset` / `MsgCustodyReleaseAsset`); the v0.3 struct + fields stay (they already use "custody"). **0.85** +- A-543: lending handler clamps the coupon to [0, 800] bps at runtime + (D-028/REQ-030 runtime echo); the clamp event is emitted for simtest. **0.82** +- A-544: compliance-before-custody ordering is enforced (the withdrawal + handler checks compliance status before the custody debit). **0.85** + +--- + +### v0.5 §2.5 REQ-037 — Services runtime (P5) + +**What it is:** `x/services` gains Care / SIM / Vault / Mail service +lifecycle handlers. The v0.3 `ServiceKind` enum (4) + `ServiceInfo` + per- +service structs become the state objects of a service lifecycle: `Pending → +Active → Suspended → Revoked` (reusing the 4-state shape from v0.2 +PartnerStatus / v0.3 HubServiceStatus). + +**Prior art / ecosystem references:** +- **Service registry patterns** — Kubernetes Service (a logical name + + endpoints), Consul / etcd service discovery, Cosmos SDK `x/params` (a + light registry). OY's services are a typed registry: a service has a + kind, an operator, a status, and a window-id grant (the v0.3 + `ServiceInfo.window-id` field, by-ID-string ref to `x/window`). +- **Care / mutual-aid** — Gitcoin Grants rounds (care as public-good + funding); OY Care is a community-care service a Stand/Guild operates. +- **SIM / connectivity** — Helium Mobile (DePIN connectivity), Pollen, + Andrena. OY SIM is a connectivity service; the handler records a SIM + activation against a window-grant. +- **Vault / storage** — the v0.2 `x/vault` is the Stand-level storage pool; + the OY Vault service (ServiceKind=Vault) is a higher-level storage + offering (backup, attested storage). The handler references `x/vault` + by ID-string (G-003). +- **Mail / messaging** — Session, Status, XMTP (decentralized messaging); + OY Mail is a bearer-routed messaging service. The handler records a + mailbox binding against a window-grant. + +**Skeleton-to-runtime delta:** +- v0.3: `ServiceKind` enum (4) + `ServiceInfo` + per-service structs + + keeper stub. +- v0.5: `MsgRegisterService` (operator-reach-id must be valid; the + `window-id` must reference an Active Window — checked via the `x/window` + expected-keeper shim), `MsgActivateService`, `MsgSuspendService`, + `MsgRevokeService` (revocation requires the Window grantor or a + Watcher quorum). Per-kind handlers: `MsgIssueCareGrant` (Care), + `MsgActivateSIM` (SIM), `MsgProvisionVault` (Vault, references `x/vault` + by ID), `MsgBindMailbox` (Mail). Simtest covers the lifecycle + the + window-grant check (a service registered against a non-existent or + Revoked Window is rejected). + +**Pitfalls:** +- **Window-grant validity:** the `window-id` is the service's authority + boundary; a revoked Window invalidates the service. The handler checks + the Window status on every operation (not just registration) — a service + operating after its Window expired is a Window-violation. Simtest covers + the expired-window-during-operation case. +- **Service-kind dispatch:** the per-kind handlers are distinct messages + (`MsgActivateSIM` vs `MsgBindMailbox`); a generic `MsgInvokeService(kind, + payload)` would be a type-unsafe dispatch (a kind mismatch is a runtime + error, not a compile-time one). The v0.5 handlers are per-kind (one Msg + per ServiceKind) to keep the dispatch typed. This mirrors the v0.3 + per-service struct pattern. +- **Coverage:** services are the lowest-priority REQ (Medium); the + ≥80% coverage target (D-033 carries forward) is achievable with table- + driven handler tests per kind. + +**Confidence-scored conclusions:** +- A-551: per-kind message handlers (one `Msg*` per ServiceKind), NOT a + generic dispatch — keeps the dispatch typed. **0.80** +- A-552: the `window-id` grant is checked on EVERY service operation, not + just registration (a revoked Window invalidates ongoing service ops). **0.82** +- A-553: Vault service references `x/vault` by ID-string (G-003); the + handler uses an `expected_keepers.go` shim for the vault keeper. **0.85** + +--- + +### v0.5 §2.6 REQ-038 — Bond market depth runtime (P6) + +**What it is:** `x/bond` gains Growth Bond issuance handlers and a +secondary-market **central-limit order book (CLOB)** matching engine +(D-057). The 8% cap / 0% floor consts (D-028, locked since v0.2) are a +**hard per-match clamp**: every match's resulting coupon stays within +[0, 800] bps. No AMM (D-057 rejects AMM as a Year-4 concern). + +**Prior art / ecosystem references:** +- **CLOB matching engines** — dYdX v4 (a fully on-chain CLOB in a Cosmos + app-chain, price-time priority, batch matching), Sei v2 (order-book + matching with parallel execution), 0x Mesh (off-chain order relay + + on-chain settlement). OY's CLOB is closest to dYdX v4's per-tx matching + (the handler matches a new order against the resting book in the same + tx; no asynchronous matching). The matching is price-time priority (FCFS + at the same price — REQ-007 FCFS principle). +- **Fixed-income secondary markets** — Centrifuge Tinlake secondary + (tokenized tranches trade on an order book); OY's secondary market + trades issued bonds (the v0.3 `SecondaryOrder` struct, Buy/Sell side). +- **Coupon caps as per-match clamp** — the 8% cap (D-028) is mission- + locked; the matching engine enforces it PER MATCH (the matched + coupon is clamped to [0, 800] before the trade record is written). This + is distinct from a market-wide price cap: the secondary-market TRADE + PRICE (the fraction of principal) is market-determined, but the bond's + COUPON stays in the locked band. A trade that would imply a coupon + above 800 bps is rejected (or the coupon is clamped to 800 and the + excess is refunded — design choice, see pitfall below). +- **Growth Bond coupon growth** — the v0.3 `GrowthBond.GrowthRateBps` + + `ClampGrowth` helper ensure the post-growth coupon ≤ 800. v0.5's + issuance handler invokes `ClampGrowth` at issuance and on each growth + tick (a `MsgTickGrowthBond` handler, simtest-driven, not real-time). + +**Skeleton-to-runtime delta:** +- v0.3: `Bond` + `GrowthBond` + `SecondaryOrder` structs + `OrderSide`/ + `OrderStatus` enums + `Clamp`/`ClampGrowth` helpers + keeper stub + (AddOrder/GetOrder/ListByBond/CancelOrder — no matching). +- v0.5: `MsgIssueBond`, `MsgIssueGrowthBond` (invokes `Clamp` on the + coupon, then `ClampGrowth` if a growth rate is set), `MsgTickGrowthBond` + (applies the growth, clamped), `MsgPlaceSecondaryOrder` (Buy/Sell side, + price in bps, quantity in grain), `MsgCancelSecondaryOrder`, + `MsgMatchSecondaryOrder` (the CLOB match: loads the resting book for + the bond, matches the new order against the best opposing price until + filled or the book is empty, writes `Filled` orders, emits a match + event with the matched coupon clamped to [0, 800]). The matching is + price-time priority (FCFS at the same price — REQ-007). Simtest covers: + full fill, partial fill + rest, no-match (order rests), cancel, and the + clamp-on-match (a match that would imply coupon > 800 is clamped, the + excess refunded or rejected — see pitfall). + +**Pitfalls:** +- **CLOB front-running:** in a single-validator simtest there is no MEV; + but the handler design must not ENABLE front-running in a real chain. + The dYdX v4 pattern (batch matching at end-of-block) mitigates MEV; OY's + v0.5 is per-tx matching (simtest only), but the handler MUST be + documented as NOT front-running-safe for mainnet (a Year-3+ concern). + The simtest does NOT assert front-running safety (it's out of scope for + simtest-grade runtime per D-054). +- **Per-match clamp semantics:** two designs — (a) REJECT a match whose + implied coupon exceeds 800 (the trade fails, the order rests), or (b) + CLAMP the coupon to 800 and refund the excess to the seller. D-057 says + "hard clamp on each match"; "clamp" suggests (b), but rejecting is + simpler and safer (no refund path). **Recommendation: REJECT** (the + match fails closed; the resting order stays). This matches the Fee + Covenant's `Clamp` shape (which clamps, not rejects) for issuance, but + for matching the reject is the mission-lock-true choice (a trade above + the cap is a usury violation, not a clampable excess). Flag as a + planner decision (D-057 is "clamp"; the runtime interpretation is + "reject above cap, clamp within band"). **Confidence 0.70** — the + planner should resolve reject-vs-clamp before P6. +- **Mission-lock const firewall:** the 8%/0% consts (D-028) are the + highest-severity locked consts in the bond module. The matching handler + MUST reference the consts (not a local copy); the v0.3 `Clamp` already + uses the consts; the v0.5 match handler reuses `Clamp` on the matched + coupon. The REQ-030 cross-const test (hub lending consts == bond consts) + stays green. +- **FCFS at same price:** REQ-007 mandates FCFS. The CLOB matches at + price-time priority: at the same price, the earlier resting order + fills first (by sequence). The keeper stores the resting book ordered + by (price, sequence); the match iterates in that order. +- **Lexicon:** "coupon", "growth", "secondary", "order", "match", "fill", + "cancel" are safe. "interest"/"yield"/"deposit"/"savings" banned. The + `MsgTickGrowthBond` handler is the lexicon risk ("growth" is safe; + "tick" is safe). + +**Confidence-scored conclusions:** +- A-561: CLOB matching with price-time priority (FCFS at same price per + REQ-007); per-tx matching in simtest (dYdX-v4-shaped, no batch + end-of-block matching in v0.5). **0.82** +- A-562: per-match coupon clamp to [0, 800] bps via the v0.3 `Clamp` + helper; a match above 800 is REJECTED (fails closed), not clamped-with- + refund (the safer mission-lock-true choice). **0.70** — planner to + confirm reject-vs-clamp before P6. +- A-563: the 8%/0% consts are referenced directly (not copied); the + REQ-030 cross-const test stays green. **0.90** +- A-564: no AMM (D-057); the CLOB is the only matching engine in v0.5. **0.95** + +--- + +### v0.5 §2.7 REQ-039 — Council governance runtime (P7) + +**What it is:** `x/council` gains `Proposal` and `VoteOption` enum types +(AUDIT §193 P1-1, deferred from v0.4 by D-050/D-001) and Voice lifecycle +handlers. `SignalKind` stays at 4 sources (P1-2 defensible per AUDIT; +expansion to 5 deferred to v0.6+ governance vote). Mission Lock const +firewall (G-003) intact — `MissionLockAmendable = false` is unchanged. + +**Prior art / ecosystem references:** +- **Governance proposal lifecycle** — OpenZeppelin Governor (Pending → + Active → Succeeded → Queued → Executed → Defeated), Compound Bravo, + Cosmos SDK `x/gov` (`ProposalStatus`: DepositPeriod → VotingPeriod → + Passed → Rejected → Failed). OY's council governance is a federated + variant (three councils, each with its own voter base); D-060 specifies + `ProposalKind` = {Stand, Guild, Mesh, MissionLockAmendment-Rejected} + (the last value exists but the handler REJECTS it — documenting the + non-amendability in code). This mirrors `x/gov`'s `ProposalType` but + with the Mission-Lock rejection encoded as a kind. +- **VoteOption enum** — `x/gov` uses {Yes, No, NoWithVeto, Abstain}; + OZ Governor uses {For, Against, Abstain}. D-060 specifies {Yes, No, + Abstain, Veto} where Veto is Watcher-only (a quorum rule). The + `TallyResult` (v0.2, already x/gov-shaped with `NoWithVeto` kept as a + zero-locked field) is reused; the Veto option populates the + `NoWithVeto` field (it was zero-locked in v0.2 because there was no + VoteOption enum; v0.5 un-locks it to a Watcher-only option, but the + ANTI-GREED principle means a single Veto does NOT block — it requires + a Watcher quorum, not a single veto). This is a careful un-locking: + the v0.2 `NoWithVeto` field was `0` always; v0.5 lets Watchers cast + it, but the tally rule is quorum-based, not single-veto. +- **Mission Lock const firewall** — MakerDAO's immutable governance + pauses (the "End" / "ESM" emergency shutdown is one-way); Compound's + governor has a `Guardian` that can pause. OY's Mission Lock is a + compile-time `const bool MissionLockAmendable = false` (v0.2); the v0.5 + `ProposalKind.MissionLockAmendment-Rejected` handler asserts the + const is false and rejects the proposal (the const is the firewall; the + handler is the documentation). A future agent flipping the const breaks + the v0.2 `TestMissionLockAmendableFalse` test (the regression firewall). +- **DAO dao / Aragon** — modular governance with proposal + vote + tally + + execution; the execution step is the key (OY's v0.5 ships up to the + tally; execution of a passed proposal is a v0.6+ concern, deferred — + the v0.5 handler records the result but does NOT auto-execute). + +**Skeleton-to-runtime delta:** +- v0.3/v0.4: `Council` + `CouncilMember` + `Voice` + `SignalKind` (4) + + `TallyResult` + `MissionLockAmendable = false` const. NO `Proposal` / + `ProposalStatus` / `VoteOption` (AUDIT §193 P1-1 absence). +- v0.5: ADD `Proposal` struct (proposal-id, council-id, kind + (ProposalKind), proposer-reach, submit-time, voting-deadline, status + (ProposalStatus), tally (TallyResult)). ADD `ProposalKind` enum {Stand, + Guild, Mesh, MissionLockAmendment-Rejected} with a locked-const + `ProposalKindCount = 4` (the last value is the rejected kind — its + existence documents the non-amendability; the handler rejects it). ADD + `ProposalStatus` enum {Pending, Active, Succeeded, Failed, Executed} + with `ProposalStatusCount = 5` (mirrors OZ Governor / x/gov). ADD + `VoteOption` enum {Yes, No, Abstain, Veto} with `VoteOptionCount = 4` + (Veto is Watcher-only; the handler checks the signer is a Watcher via + the `x/watcher` expected-keeper shim). ADD handlers: `MsgSubmitProposal` + (validates kind; MissionLockAmendment-Rejected kind is recorded as + Pending but auto-transitions to Failed with an event "Mission Lock + non-amendable" — OR the ValidateBasic rejects it; design choice, see + pitfall), `MsgVote` (cast a Voice with a VoteOption; Veto requires + Watcher authz), `MsgTallyProposal` (closes the voting deadline, + computes the tally, transitions Succeeded/Failed). `SignalKind` stays + at 4 (P1-2 defensible; the v0.4 regression-guard test + `TestSignalKindShapeIntentional` stays green; the count is NOT changed + to 5). + +**Pitfalls:** +- **Mission Lock const firewall integrity:** the + `MissionLockAmendment-Rejected` ProposalKind is the highest-risk + addition. Two designs: (a) `ValidateBasic` rejects the message (the + proposal never enters the keeper; cleanest), or (b) the handler accepts + it, records it as Pending, auto-transitions to Failed with an event + (documents the rejection on-chain). **Recommendation: (a)** — reject + at `ValidateBasic` so the message never reaches the handler (the const + is the firewall; the ValidateBasic is the gate). This matches the + Mission-Lock-non-amendable design (the proposal is unproposable, not + propose-then-fail). Flag as a planner decision. **Confidence 0.80**. +- **Veto semantics:** a single Veto must NOT block (anti-greed; vision + §19). The Veto option is Watcher-only and the tally rule is a Watcher + quorum (e.g., 6-of-9 Watchers casting Veto transitions the proposal to + Failed; a single Veto is recorded but does not fail the proposal). The + handler enforces this: a Veto is counted in `NoWithVeto`, but the + proposal fails only if `NoWithVeto >= WatcherVetoQuorum` (a param, NOT a + locked const — defer the exact value to a v0.5 planner decision; the + v0.2 Params struct was empty, v0.5 adds a `WatcherVetoQuorum` param + defaulting to 6 to match REQ-004's 6-of-9). **Confidence 0.75** — the + quorum value is a planner decision. +- **Proposal lifecycle ordering:** a vote on a non-Active proposal MUST be + rejected (the voting-deadline check). A tally on an Active proposal + before the deadline MUST be rejected. The handler checks status + + deadline in order (authz → status → deadline → tally). Simtest covers + the vote-after-deadline and tally-before-deadline rejections. +- **SignalKind 4-not-5:** the AUDIT P1-2 rationale (Freeholder is + eligibility, Guild is a council tier, Capital is the 4th signal) is + documented in v0.4 ARCHITECTURE.md + the regression-guard test. v0.5 + does NOT change `SignalKindCount`; the 4-source shape stays. A v0.6+ + governance vote could expand it, but that requires a locked-const + amendment (a `feat:` that the Mission Lock firewall does NOT block + because SignalKind is not a Mission-Lock const — it's a locked-const + but not the Mission-Lock const; the distinction is documented in v0.4). +- **Coverage:** the council runtime is the cross-cutting P7; ≥80% + coverage on the new enum types + handlers. + +**Confidence-scored conclusions:** +- A-571: `Proposal` + `ProposalKind` (4, including the rejected + MissionLockAmendment kind) + `ProposalStatus` (5) + `VoteOption` (4) + are ADDED to `x/council/types` (AUDIT §193 P1-1 promotion); locked-const + tests added. **0.85** +- A-572: `MissionLockAmendment-Rejected` proposals are rejected at + `ValidateBasic` (the message never reaches the handler); the const + firewall + the ValidateBasic gate are the dual firewall. **0.80** — + planner to confirm reject-at-ValidateBasic vs propose-then-fail. +- A-573: `SignalKind` stays at 4 (P1-2 defensible; v0.4 regression-guard + test stays green); 5-source expansion deferred to v0.6+ governance + vote. **0.95** +- A-574: Veto is Watcher-only, quorum-based (default `WatcherVetoQuorum + = 6` matching REQ-004 6-of-9); a single Veto does NOT block (anti-greed). + **0.75** — the quorum param value is a planner decision. + +--- + +## v0.5 §3. Cross-Cutting Concerns + +### v0.5 §3.1 G-006 controlled exception (cosmos-sdk dep, D-055) + +`go.mod` gains `github.com/cosmos/cosmos-sdk` (+ transitive deps) as the +runtime substrate. This is a GRILL-approved controlled exception to G-006 +(zero-dep go.mod). The exception is scoped: +- **Runtime phases (P1..P7):** `keeper/`, `msg_server.go`, `module.go`, + `simtest/` import cosmos-sdk. The dep is load-bearing. +- **P0 (pre-execution) + P8 (final):** stay dep-neutral where possible + (this RESEARCH.md, PERSONAS.md, the planner's PLAN — no Go code). +- **`types/` packages:** stay dep-free where possible. The v0.3 `types/` + packages are pure stdlib (`encoding/json`); v0.5 ADDS `types.Msg*` + structs which MUST implement `sdk.Msg`, so the `types/` package gains a + cosmos-sdk import for the message types. **Pitfall:** this breaks the + v0.1-v0.4 property that `types/` is zero-dep. Mitigation: isolate the + `Msg*` types in a `types/msg_*.go` file set and accept the `types/` + package now imports cosmos-sdk (the dep is already in `go.mod` for the + runtime; the `types/` import is consistent, not a new dep tree). The + invariant tests (locked-const, lexicon) stay stdlib-only and green. +- **Version pin:** cosmos-sdk v0.50.x (LTS, go 1.22-compatible) is the + target; ibc-go v8.x (for cosmos-sdk v0.50) for the IBC packet handler + interfaces. ibc-go v10 (IBC v2 / Eureka) is attractive but requires + cosmos-sdk v0.50+ and is newer; the v0.5 pin should be cosmos-sdk v0.50.x + + ibc-go v8.x for stability (the IBC v2 patterns are documented in + RESEARCH but the v0.5 impl can use the v8 stable interfaces; v10 is a + later upgrade). **Confidence 0.78** — the planner confirms the version + pin; a GRILL review of the version is part of the controlled-exception + ratification. + +### v0.5 §3.2 G-003 production firewall (still intact) + +The G-003 by-ID-string rule (no production cross-`x//types` struct +imports) survives the runtime promotion, BUT the runtime adds a NEW +cross-module surface: keeper-to-keeper calls. The ibc-go convention for +this is the `expected_keepers.go` shim: a module's `types/expected_keepers.go` +defines Go INTERFACES for the keepers it depends on (e.g., +`x/exit/types/expected_keepers.go` defines a `BridgeKeeper` interface with +the methods `x/exit`'s handler calls; the `x/bridge` keeper satisfies it +structurally). The handler depends on the INTERFACE, not the concrete +keeper struct. This is NOT a struct import of `x/bridge/types`; it is an +interface defined in `x/exit/types`. G-003's intent (no cross-module struct +coupling, no import cycles) is preserved. + +Test-only cross-package imports (the G-003 test exemption, used by REQ-030) +remain exempt: a simtest may import both `x/exit/keeper` and `x/bridge/keeper` +to wire the expected-keeper shims in a test setup. + +### v0.5 §3.3 Lexicon firewall (still green, extended surface) + +The lexicon firewall (`lexicon_meta_test.go` scanning `x/**/*.go` + +`lexicon_meta_docs_test.go` scanning docs) automatically covers the new +`keeper/`, `msg_server.go`, `simtest/` files. The highest-risk v0.5 +additions: +- `x/hub` custody message names: AVOID "deposit" (banned) — use + `MsgCustodyReceiveAsset` / `MsgCustodyReleaseAsset` (A-542). +- `x/bond` matching: "match"/"fill"/"cancel" safe; "interest"/"yield" + banned. +- `x/council` Veto: "veto" is safe (not in the banned list); "VoteOption" + safe. +- The `Msg*` struct names are the lexicon surface (they appear in the Go + source the firewall scans). A per-module lexicon assertion test + (`TestLexiconNoBannedTermsInPackage`) is added to each new + `keeper/` package or extended in the existing `types/` test. + +### v0.5 §3.4 Simtest grade (not mainnet) + +D-054 ratifies: runtime = simtest-grade handlers, NOT mainnet. The simtest: +- Uses the SDK in-memory store (`dbm` in-memory backend), NOT a real + CometBFT node. +- Exercises each handler against an in-memory `sdk.Context` (constructed + via `sdk.NewContext(store, header, true, logger)` or the + `simtestutil` helpers). +- Asserts state transitions + event emission + idempotency (replay + rejection). +- Does NOT test: real IBC light clients, real relayers, real MPC, real + bearer hardware, real DEX venues, real Watcher attestations (all + stubbed). + +--- + +## v0.5 §4. Assumptions (logged with confidence scores) + +| ID | Assumption | Confidence | Rationale | +|----|-----------|------------|-----------| +| A-501 | Every v0.5 target module gains a `keeper/` subdir + `msg_server.go`; the `types/` package stays as the locked-contract layer (no struct field removal). | 0.92 | Cosmos SDK MsgServer convention; D-054 ratifies. | +| A-502 | The v0.3 in-memory `Keeper` stub (in `types/types.go`) is retired or wrapped by the store-backed keeper; the `types/` public API is not broken. | 0.80 | SDK migration; the stub may stay as a test helper. | +| A-503 | Simtest uses the SDK in-memory store; no live chain, no real IBC light clients (D-054). | 0.90 | D-054 explicit. | +| A-504 | `go.mod` gains cosmos-sdk v0.50.x + ibc-go v8.x (GRILL-approved D-055); `types/` packages gain the sdk.Msg import for `Msg*` types. | 0.78 | D-055 controlled exception; version pin is a planner/GRILL decision. | +| A-505 | Keeper-to-keeper cross-module calls use `expected_keepers.go` interface shims (ibc-go convention); G-003 by-ID-string rule preserved at the type level. | 0.85 | ibc-go standard pattern; breaks import cycles. | +| A-511 | `x/bridge` IBC handlers implement `OnRecvPacket`/`OnAcknowledgementPacket`/`OnTimeoutPacket` on the v0.3 `BridgeRoute` + ICS-20 v1 payload; Solana via wormhole-adapter branch. | 0.82 | ibc-go + D-059. | +| A-512 | IBC ack/timeout replay protection mirrors ibc-go (delete-on-ack, refund-on-timeout); simtest covers both. | 0.90 | ibc-go CVE-class pitfall; simtest must cover. | +| A-521 | OY-QR is one-shot; `MsgConsumeOYQR` flips `consumed` before the transfer effect; replay rejected idempotently. | 0.88 | v0.3 `consumed` flag; one-shot QR analog (Bolt Card). | +| A-522 | `BearerTransport` interface gains a store-backed impl (keeper as transport in simtest); no hardware/RF dep (D-054). | 0.85 | D-054. | +| A-531 | Anchor credential lifecycle = Pending → Onboarded → Suspended → Revoked (reuses v0.2 PartnerStatus 4-state shape). | 0.82 | v0.2 shape reuse. | +| A-532 | P3→P4 hub dependency broken by `expected_keepers.go` shim; hub runtime impl wired in P4. | 0.85 | ibc-go convention; D-056 ordering. | +| A-533 | Revocation authz delegates to `x/watcher` expected-keeper shim (6-of-9 quorum); no `x/watcher/types` struct import (G-003 intact). | 0.88 | REQ-004 + G-003. | +| A-541 | `CustodyKeyring` interface (`Sign`/`Derive`/`Status`) + `memKeyring` in-memory test impl; real MPC/HSM deferred (D-058). | 0.88 | D-058 explicit. | +| A-542 | Custody message names AVOID "deposit" (banned); use `MsgCustodyReceiveAsset`/`MsgCustodyReleaseAsset`. | 0.85 | Lexicon firewall; v0.5 discovery. | +| A-543 | Lending handler clamps coupon to [0, 800] bps at runtime (D-028/REQ-030 runtime echo); clamp event emitted for simtest. | 0.82 | D-028 + REQ-030. | +| A-544 | Compliance-before-custody ordering enforced (withdrawal checks compliance before debit). | 0.85 | State-machine ordering pitfall. | +| A-551 | Per-kind service message handlers (one `Msg*` per ServiceKind), NOT a generic dispatch. | 0.80 | Typed dispatch. | +| A-552 | `window-id` grant checked on EVERY service operation, not just registration (revoked Window invalidates ops). | 0.82 | Window lifecycle pitfall. | +| A-561 | CLOB matching with price-time priority (FCFS at same price, REQ-007); per-tx matching in simtest (dYdX-v4-shaped). | 0.82 | D-057 + REQ-007. | +| A-562 | Per-match coupon clamp to [0, 800] bps via v0.3 `Clamp`; a match above 800 is REJECTED (fails closed), not clamped-with-refund. | 0.70 | D-057 says "clamp"; runtime interpretation is reject. Planner to confirm. | +| A-563 | 8%/0% consts referenced directly (not copied); REQ-030 cross-const test stays green. | 0.90 | D-028 + REQ-030. | +| A-564 | No AMM in v0.5 (D-057); CLOB is the only matching engine. | 0.95 | D-057 explicit. | +| A-571 | `Proposal` + `ProposalKind` (4, incl. rejected MissionLockAmendment) + `ProposalStatus` (5) + `VoteOption` (4) ADDED to `x/council/types` (AUDIT §193 P1-1). | 0.85 | D-060 + AUDIT P1-1. | +| A-572 | `MissionLockAmendment-Rejected` proposals rejected at `ValidateBasic` (never reach handler); const firewall + ValidateBasic gate are dual firewall. | 0.80 | Mission-Lock non-amendable; planner to confirm reject-at-ValidateBasic. | +| A-573 | `SignalKind` stays at 4 (P1-2 defensible; v0.4 regression-guard test stays green); 5-source expansion deferred to v0.6+. | 0.95 | D-060 + v0.4 AUDIT rationale. | +| A-574 | Veto is Watcher-only, quorum-based (default `WatcherVetoQuorum = 6` per REQ-004 6-of-9); single Veto does NOT block (anti-greed). | 0.75 | Vision §19 anti-greed + REQ-004; quorum value is a planner decision. | + +--- + +## v0.5 Cross-Reference Summary + +| REQ | Component | Module(s) | Phase (D-056) | Promotion (skeleton → runtime) | +|-----|-----------|-----------|---------------|-------------------------------| +| REQ-033 | Exit + Bridge runtime | `x/exit`, `x/bridge` | P1 | DEX swap routing + L2↔L1 IBC packet handlers (5 L2 chains, D-059) | +| REQ-034 | Bearers transport runtime | `x/bearers` | P2 | OY-SAT + OY-QR message handlers; session lifecycle in simtest | +| REQ-035 | Anchors onboarding runtime | `x/partner` | P3 | Anchor credential issuance + revocation handlers | +| REQ-036 | Hub API B2B runtime | `x/hub` | P4 | Custody/Lending/Compliance handlers; `CustodyKeyring` interface + memKeyring (D-058) | +| REQ-037 | Services runtime | `x/services` | P5 | Care/SIM/Vault/Mail service lifecycle handlers | +| REQ-038 | Bond market depth runtime | `x/bond` | P6 | Growth Bond issuance + secondary-market CLOB matching (D-057); 8%/0% per-match clamp (D-028) | +| REQ-039 | Council governance runtime | `x/council` | P7 | Proposal/VoteOption enums (AUDIT §193 P1-1) + Voice lifecycle handlers; Mission Lock const firewall intact | + +**Modules promoted to runtime: 8 (exit, bridge, bearers, partner, hub, +services, bond, council). New enum types: 4 in council (ProposalKind, +ProposalStatus, VoteOption, plus Proposal struct — AUDIT P1-1). New +interfaces: CustodyKeyring (D-058). New dep: cosmos-sdk v0.50.x + +ibc-go v8.x (D-055, GRILL-approved G-006 exception). Simtest grade only +(D-054); no mainnet.** + +--- + +## v0.5 Planner-Actionable Items (low-confidence, escalate) + +The following require planner confirmation before the corresponding phase +lands (low-confidence assumptions, escalated through the normal decision +flow per the researcher role — NOT flagged `[ASSUMED]`): + +1. **A-504** (cosmos-sdk / ibc-go version pin): GRILL review of the exact + version (v0.50.x + ibc-go v8.x proposed; v10 IBC-v2 is an alternative). + Confidence 0.78. +2. **A-562** (bond match above 800 bps: reject vs clamp-with-refund): D-057 + says "clamp"; the runtime interpretation proposes REJECT (fails closed). + Confidence 0.70. +3. **A-572** (MissionLockAmendment proposal: reject at ValidateBasic vs + propose-then-fail): the const firewall is the gate; ValidateBasic + rejection is proposed. Confidence 0.80. +4. **A-574** (Watcher Veto quorum value): default 6 proposed (matches + REQ-004 6-of-9); the exact param value is a planner decision. + Confidence 0.75. \ No newline at end of file