Files
openyield/.ciagent/oy/PLANS.md
T
cloudinit-bot 949b1acf50 docs(P00): create phase plans — v0.6 Nomad Web UI
---ci---
project: oy
phase: 0
milestone: v0.6
status: plan
---/ci---
2026-08-18 12:02:28 +00:00

270 KiB
Raw Blame History

Plans: OpenYield (oy) — v0.2 (The Mesh)

Milestone Summary

  • Milestone: v0.2 — The Mesh
  • Type: Feature (≥1 feat phase)
  • Tag base: v0.1.x patch line (P0 ships as v0.1.0; execution phases v0.1.1..v0.1.4; final phase v0.1.5 IS the milestone release)
  • Phases: 5 — P1..P4 (execution) + P5 (final review/audit/ship). Phase 0 (PLAN) is in progress.
  • Depth: skeleton + tests layer (D-020) — Go types + keeper stubs + unit tests, zero external deps, matching v0.1's pre-MVP pattern.
  • Coverage target: ≥80% on each new package (D-033); lexicon assertion (REQ-012) in every new module's test file (D-032).
  • New modules: 9 (x/satellite, x/council, x/window, x/stand, x/guild, x/partner, x/pact, x/bond, x/forex). Extended: 1 (x/bearers). Total v0.2 packages: 10 new/extended + v0.1 baseline of 15.
  • Phase ordering (D-031): P1 Orgs+Window foundation → P2 Pacts+Partners → P3 Councils+Forex → P4 Bonds+Bearers+L2 → P5 review/ship. Respects ARCHITECTURE.md blocker chain.
  • Personas: backend-engineer (bespoke types), cosmos-engineer (Cosmos-convention types), data-engineer (genesis validation), security-engineer (all test files, invariants, lexicon), lead-developer (cross-cutting verification/ship).

Cross-Phase Dependency Map

P1 (Window, Stand, Guild) ──┬──► P2 (Pact, Partner)         [Window refs + Stand refs]
                            ├──► P3 (Council, Forex)        [Stand + Guild refs; Forex independent]
                            └──► P4 (Bond, Bearers, L2)     [Stand refs for Bond; Bearers/L2 independent]
P2 ──► P3  (Partner/Pier feeds Forex oracle interface)
P3 ──► P4  (Council governance over Bond market; not a hard type-dep, soft ordering)
P4 ──► P5  (all execution phases complete before review/ship)

Hard cross-phase blockers (by-ID-string refs, no import cycles):

  • P2 Pact StandRegistry references x/stand StandRef (P1-02-01 must land first).
  • P2 Partner Window-scoped actions reference x/window WindowRef (P1-01-01 must land first).
  • P3 Council Stand Council references x/stand + x/guild (P1-02-01, P1-03-01 must land first).
  • P3 Forex oracle interface is consumed by Piers (P2 Partner) — soft ordering, no type dep.
  • P4 Bond issuer references x/stand StandRef (P1-02-01 must land first).

Phase P1 — Orgs + Window Foundation

  • Slug: orgs-window-foundation
  • Branch: oy/phase/01-orgs-window-foundation
  • REQs covered: REQ-015 (Window — fullest per D-023), REQ-016 (9 Stands), REQ-017 (Guilds + Hand-Pass 0%)
  • Tag: v0.1.1
  • Goal: Ship the Window leaf primitive (full lifecycle), the 9-type Stand enum + keeper stub, and the Guild + Hand-Pass @ 0% skeleton — the foundation every downstream Mesh module references.

Wave 1 — Foundation module types (parallel; no internal deps)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P1-01-01 REQ-015 cosmos-engineer x/window/types/types.go Window struct (id, grantor-holder, grantee, scope, start, end, rate-limit, revoked, audit-log-refs); ScopeKind enum (ReadStash, ReadStanding, ProcessPassActForStand, …); Scope struct (kind, resource-id); RateLimit struct (max-actions, per-duration-seconds, actions-consumed); AuditEntry struct (timestamp, action, result, granter-ref); WindowStatus enum (Open, Active, Revoked, Expired); Revoke() stub; DefaultParams, GenesisState, DefaultGenesisState, ValidateGenesis (reject dup window-ids); consts ModuleName/StoreKey/RouterKey/QuerierRoute go build ./x/window/... succeeds; Window struct fields match RESEARCH §1.3; ValidateGenesis returns error on dup window-ids (A-212 upgrade)
P1-02-01 REQ-016 cosmos-engineer x/stand/types/types.go StandType enum (Household, Crew, Entity, Coop, Circle, Trust, Foundation, Confederation, Shadow) — exactly 9; AllStandTypes() []StandType; Stand struct (id, type, name, vault-id, admin-reach, created-at, member-count); Membership struct (stand-id, reach-id, joined-at, role); StandRole enum (Member, Admin, Observer); StandPolicy stub (threshold/weighted — mirror x/group DecisionPolicy, A-205); DefaultParams, GenesisState (stands + memberships), DefaultGenesisState, ValidateGenesis (reject dup stand-ids, dup member-reach within a stand) go build ./x/stand/... succeeds; AllStandTypes() returns exactly 9 entries with vision §11 names; ValidateGenesis rejects dup stand-id and dup member-reach
P1-03-01 REQ-017 cosmos-engineer x/guild/types/types.go Guild struct (id, name, founder-reach, created-at, stand-affiliation-id optional); HandPass struct (pass-id, guild-id, issuer-reach, recipient-reach, amount-grain, timestamp, fee-grain-always-0); HandPassFeeBps = 0 locked const (cross-doc to feecovenant WaiverHandPassGuild); IssueHandPass(...) stub; DefaultParams, GenesisState (guilds + hand-passes), DefaultGenesisState, ValidateGenesis (reject dup guild-ids, dup pass-ids) go build ./x/guild/... succeeds; HandPassFeeBps == 0; ValidateGenesis rejects dup guild-id

Wave 2 — Tests + genesis validation (parallel; blocked-by Wave 1)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P1-01-02 REQ-015 security-engineer x/window/types/types_test.go Table-driven tests: lifecycle Open→Active→Revoked→Expired; Revoke() transitions to Revoked; revoke-after-expire is no-op (idempotent); double-revoke is idempotent OR error (test both paths); RateLimit consume increments actions-consumed, blocks at max-actions; Scope kind enum coverage; DefaultGenesis empty; ValidateGenesis rejects dup window-ids; lexicon assertion (no banned terms in package); locked-const for WindowStatus count; by-ID-string import invariant (G-003): a test that scans all non-test .go files under x/ using go/parser or a package-import grep and asserts NO production file imports another x/<module>/types package by struct (enforces A-203 as a tested invariant, not just a convention) go test ./x/window/... passes; ≥80% coverage on x/window/types; lexicon assertion green; lifecycle idempotency tests present; G-003 import-invariant test passes (zero cross-module struct imports in production code) P1-01-01
P1-01-03 REQ-015 data-engineer x/window/types/genesis.go (or extend types.go) Append-only audit log genesis shape: AuditEntry slice in GenesisState, ValidateGenesis enforces append-only ordering (timestamps non-decreasing) + no dup entry-ids go test ./x/window/... passes; audit-log ordering test green P1-01-01
P1-02-02 REQ-016 security-engineer x/stand/types/types_test.go Locked-const test: AllStandTypes() returns exactly 9; each name matches vision §11 (Household, Crew, Entity, Co-op, Circle, Trust, Foundation, Confederation, Shadow) — no extras, no dups; StandRole enum coverage; ValidateGenesis rejects dup stand-id + dup member-reach; lexicon assertion go test ./x/stand/... passes; ≥80% coverage; 9-type locked-const test breaks if a type is added/removed/renamed P1-02-01
P1-02-03 REQ-016 data-engineer x/stand/types/genesis.go Membership-set genesis: stands + memberships in GenesisState, ValidateGenesis enforces member-reach uniqueness within a stand go test ./x/stand/... passes; membership uniqueness test green P1-02-01
P1-03-02 REQ-017 security-engineer x/guild/types/types_test.go Locked-const test: HandPassFeeBps == 0; HandPass fee field always 0; Guild with Stand affiliation OR standalone; issuer type-level (no enforcement in skeleton); ValidateGenesis rejects dup guild-id + dup pass-id; lexicon assertion go test ./x/guild/... passes; ≥80% coverage; HandPassFeeBps == 0 invariant P1-03-01

Wave 3 — Phase verification + lexicon meta-test scaffolding + ship

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P1-04-01 REQ-012, D-033 lead-developer (cross-cutting) Run go build ./... + go test ./... across whole repo; confirm P1 packages ≥80% coverage; confirm lexicon assertion present in all 3 new test files; tag v0.1.1 go test ./... green; go test -cover ./x/window/... ./x/stand/... ./x/guild/... ≥80% each; 3 lexicon assertions present; git tag v0.1.1 created P1-01-02, P1-01-03, P1-02-02, P1-02-03, P1-03-02, P1-04-02
P1-04-02 REQ-012 security-engineer lexicon_meta_test.go (repo root, package lexicon or main) — NEW (G-004) Project-wide lexicon meta-test scaffolding: scans all x/**/*.go (production + test) for the 9 banned terms (bank, deposit, interest, yield, currency, dollar, euro, account, savings, depositor). Passes at P1 time (v0.1 baseline is clean). The meta-test is the durable firewall created in v0.2 (G-002: v0.1 has NO lexicon test). P5-01-01 will extend this scaffolding to assert all 10 new/extended packages, not create it from scratch. Includes a self-test table (G-009): a hardcoded slice of synthetic strings, one per banned term (e.g. "bank account", "interest yield", "currency dollar euro", "deposit savings depositor"), asserted to each trigger detection — so the meta-test's own coverage is durably verified without manual spikes. go test ./lexicon_meta_test.go (or go test ./...) green; meta-test scans every .go under x/; self-test table passes for all 9 banned terms (each synthetic string is detected) P1-01-01

P1 Must-Haves

  • x/window, x/stand, x/guild each have types/types.go + types/types_test.go (v0.1 pattern, package types, zero external deps).
  • go build ./... and go test ./... green across the whole repo.
  • ≥80% coverage on x/window/types, x/stand/types, x/guild/types.
  • Window lifecycle tests: Open→Active→Revoked→Expired; revoke-after-expire no-op; double-revoke handled.
  • Stand locked-const: exactly 9 types with vision §11 names.
  • Guild HandPassFeeBps == 0 invariant test.
  • Lexicon assertion in all 3 new test files.
  • ValidateGenesis performs ID-uniqueness checks (A-212 upgrade from v0.1 no-op).
  • Git tag v0.1.1.

P1 Risks & Mitigations

  • Rate-limit algorithm unstated (A-206, conf 0.65) → use simple counter (actionsConsumed vs maxActions); flag for v0.3 revisit. Test asserts counter semantics, not algorithm.
  • Shadow Stand behavioral split (A-213, conf 0.60) → treat all 9 uniformly in skeleton; test asserts uniform enum, no special-casing. Flag for v0.3 design.
  • Window ↔ downstream import cycles → Window ships first (Wave 1) so P2/P3/P4 reference by WindowRef id-string, not struct import.
  • x/group DecisionPolicy shape drift (A-205) → StandPolicy mirrors threshold + weighted fields; cosmos-engineer reviews.

Phase P2 — Pacts + Partners

  • Slug: pacts-partners
  • Branch: oy/phase/02-pacts-partners
  • REQs covered: REQ-020 (6 Pacts), REQ-018 (4-tier Partner Spectrum)
  • Tag: v0.1.2
  • Goal: Ship the 6-Pact module (Pause/Ground/Stance/Cover/StandRegistry/HubAPI) with execute-entry stubs + invariant tests, and the 4-tier Partner registry with credential-ref types — both consuming Window + Stand refs from P1.

Wave 1 — Module types (parallel; blocked-by P1 foundation)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P2-01-01 REQ-020 backend-engineer x/pact/types/types.go PactType enum (Pause, Ground, Stance, Cover, StandRegistry, HubAPI) — exactly 6 (A-207); Pact struct (id, type, parties, terms-bytes, status, execute-msg-ref, window-id-ref, stand-id-ref); PactStatus enum (Proposed, Active, Fulfilled, Voided); per-type Execute* stub methods (ExecutePause, ExecuteGround, ExecuteStance, ExecuteCover, ExecuteStandRegistry, ExecuteHubAPI); Mission-Lock invariant: Pause/Ground/Stance core terms non-amendable (const flag); DefaultParams, GenesisState (pacts), DefaultGenesisState, ValidateGenesis (reject dup pact-ids) go build ./x/pact/... succeeds; AllPactTypes() returns exactly 6 with vision §16 names; ValidateGenesis rejects dup pact-id P1-01-01, P1-02-01
P2-02-01 REQ-018 backend-engineer x/partner/types/types.go PartnerTier enum (Op, MasterOp, Pier, Anchor) — exactly 4 (D-026); Partner struct (id, tier, name, reach-id, region, credential-ref, status); CredentialRef struct (provider-id, credential-type, ref-uri — opaque URI, Pier routing deferred per Q5); PartnerStatus enum (Pending, Active, Suspended, Revoked); registry keeper stub: AddPartner, GetPartner, ListPartners, ListByTier; DefaultParams, GenesisState (partners), DefaultGenesisState, ValidateGenesis (reject dup partner-ids) go build ./x/partner/... succeeds; AllPartnerTiers() returns exactly 4 with vision §13 names; ValidateGenesis rejects dup partner-id P1-01-01

Wave 2 — Tests (parallel; blocked-by Wave 1)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P2-01-02 REQ-020 security-engineer x/pact/types/types_test.go Locked-const test: AllPactTypes() returns exactly 6; names match vision §16; no extras/dups; Mission-Lock invariant test (Pause/Ground/Stance AmendableCoreTerms == false); PactStatus enum coverage; Execute* stubs callable; ValidateGenesis rejects dup pact-id; lexicon assertion (no "bank"/"account" etc.) go test ./x/pact/... passes; ≥80% coverage; 6-type locked-const test breaks on add/remove/rename; Mission Lock invariant green P2-01-01
P2-02-02 REQ-018 security-engineer x/partner/types/types_test.go Locked-const test: AllPartnerTiers() returns exactly 4 (Op, MasterOp, Pier, Anchor); tier names lexicon-clean (no "operator" spelled out if banned — verify "Op" is safe per vision §13); PartnerStatus enum coverage; registry stubs: AddPartner/GetPartner/ListByTier round-trip; ValidateGenesis rejects dup partner-id; lexicon assertion go test ./x/partner/... passes; ≥80% coverage; 4-tier locked-const test breaks on change P2-02-01

Wave 3 — Phase verification + ship

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P2-03-01 REQ-012, D-033 lead-developer (cross-cutting) go build ./... + go test ./... green; P2 packages ≥80% coverage; 2 lexicon assertions present; tag v0.1.2 go test ./... green; coverage ≥80% on x/pact/types, x/partner/types; git tag v0.1.2 P2-01-02, P2-02-02

P2 Must-Haves

  • x/pact, x/partner each have types/types.go + types/types_test.go.
  • go build ./... and go test ./... green.
  • ≥80% coverage on x/pact/types, x/partner/types.
  • Pact locked-const: exactly 6 types (vision §16 names).
  • Partner locked-const: exactly 4 tiers (Op, MasterOp, Pier, Anchor).
  • Mission-Lock invariant: Pause/Ground/Stance core terms non-amendable.
  • Lexicon assertion in both new test files.
  • ValidateGenesis ID-uniqueness checks.
  • Git tag v0.1.2.

P2 Risks & Mitigations

  • Cover Pool seniority deferred (Q7) → Cover Pact is a flat commitment type with no seniority fields; test asserts no seniority fields exist.
  • "Master Op" lexicon risk → security-engineer's lexicon assertion scans partner-type strings; "Op" verified safe per vision §13.
  • 6 micro-modules vs 1 module (A-207) → one x/pact module with PactType enum + per-type execute stubs; matches v0.1 one-module-per-component pattern.

Phase P3 — Councils + Forex

  • Slug: councils-forex
  • Branch: oy/phase/03-councils-forex
  • REQs covered: REQ-011 (3 Councils + Mission Lock), Forex Engine v1
  • Tag: v0.1.3
  • Goal: Ship the 3-Council module (Mesh/Guild/Stand) with x/gov-shaped tally types + Mission Lock as a const bool, and the Forex Engine v1 stub (pair type + oracle interface + stub keeper) with lexicon-clean pair labels.

Wave 1 — Module types (parallel; blocked-by P1 + P2)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P3-01-01 REQ-011 cosmos-engineer x/council/types/types.go CouncilType enum (Mesh, Guild, Stand) — exactly 3; Proposal struct (id, council, proposer-reach, submit-time, voting-period, status); ProposalStatus enum (Pending, Active, Succeeded, Failed, Executed) — mirror OZ/Governor + x/gov; VoteOption enum (Yes, No, Abstain — no "no-with-veto", anti-greed); TallyResult struct (yes, no, abstain, total-voice, quorum-met) — mirror x/gov TallyResult (A-204); VoiceSource enum (Stash, Standing, Vouch, Freeholder, Guild) — multi-source weighting inputs; MissionLockAmendable = false locked const bool (A-211); Stand Council references x/stand by stand-id-string; Guild Council references x/guild by guild-id-string (no struct imports); DefaultParams, GenesisState (proposals + tallies), DefaultGenesisState, ValidateGenesis (reject dup proposal-ids) go build ./x/council/... succeeds; AllCouncilTypes() returns exactly 3; MissionLockAmendable == false; TallyResult fields match x/gov shape P1-02-01, P1-03-01
P3-02-01 Forex v1 cosmos-engineer x/forex/types/types.go ForexPair struct (base-asset, quote-asset — NOT "currency"/"dollar"/"euro"; use "Bread/Asset" style labels, A-208); RateOracle Go interface (GetRate(pair) (rate, timestamp, err) — no impl); ForexRate struct (pair, rate, source, timestamp, confidence); SpreadCapBps const (locked; placeholder 0 with documented TBD, A-214 — test asserts ≥0); stub keeper GetRate returns sentinel "not-integrated"; DefaultParams, GenesisState (pairs + rates), DefaultGenesisState, ValidateGenesis (reject dup pair-ids) go build ./x/forex/... succeeds; RateOracle interface compiles; SpreadCapBps >= 0; no banned terms in pair-label fields — (Forex independent; soft order after P2 Partner for oracle-consumer narrative)

Wave 2 — Tests + genesis (parallel; blocked-by Wave 1)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P3-01-02 REQ-011 security-engineer x/council/types/types_test.go Locked-const test: AllCouncilTypes() returns exactly 3 (Mesh, Guild, Stand); Mission Lock invariant: MissionLockAmendable == false and a test asserting it can never be set true (the regression firewall); ProposalStatus enum coverage (5 states); VoteOption enum coverage (3 options, no veto); TallyResult struct shape matches x/gov; VoiceSource enum coverage (5 sources); ValidateGenesis rejects dup proposal-id; lexicon assertion (no "account" — use "Holder"/"Reach") go test ./x/council/... passes; ≥80% coverage; Mission Lock invariant test is the highest-severity regression firewall P3-01-01
P3-01-03 REQ-011 data-engineer x/council/types/genesis.go Voice tally genesis shape: proposals + tallies in GenesisState; ValidateGenesis enforces tally-proposal referential integrity (tally.proposal-id exists) go test ./x/council/... passes; referential integrity test green P3-01-01
P3-02-02 Forex v1 security-engineer x/forex/types/types_test.go ForexPair struct fields lexicon-clean (assert no "currency"/"dollar"/"euro"/"fx" in field names or sample labels); RateOracle interface signature compiles + a stub impl satisfies it; SpreadCapBps >= 0; stub keeper GetRate returns sentinel; ValidateGenesis rejects dup pair-id; lexicon assertion (highest lexicon-risk module per RESEARCH §1.10) go test ./x/forex/... passes; ≥80% coverage; lexicon assertion scans pair-label sample data P3-02-01

Wave 3 — Phase verification + ship

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P3-03-01 REQ-012, D-033 lead-developer (cross-cutting) go build ./... + go test ./... green; P3 packages ≥80% coverage; 2 lexicon assertions; Mission Lock invariant green; tag v0.1.3 go test ./... green; coverage ≥80% on x/council/types, x/forex/types; Mission Lock invariant passes; git tag v0.1.3 P3-01-02, P3-01-03, P3-02-02

P3 Must-Haves

  • x/council, x/forex each have types/types.go + types/types_test.go.
  • go build ./... and go test ./... green.
  • ≥80% coverage on x/council/types, x/forex/types.
  • Council locked-const: exactly 3 types (Mesh, Guild, Stand).
  • Mission Lock invariant: MissionLockAmendable == false, test asserts non-amendable (highest-severity).
  • TallyResult shape mirrors x/gov (A-204) for future wiring.
  • VoteOption has no "no-with-veto" (anti-greed).
  • Forex pair labels lexicon-clean (no "currency"/"dollar"/"euro"); RateOracle interface compiles.
  • Lexicon assertion in both new test files.
  • ValidateGenesis ID-uniqueness + referential integrity (Council).
  • Git tag v0.1.3.

P3 Risks & Mitigations

  • Multi-source Voice weighting math unstated (RESEARCH §1.2) → skeleton defines VoiceSource enum + TallyResult struct only; no aggregation algorithm. Flag weighting math as v0.3+ decision.
  • Forex lexicon hostility (highest risk) → security-engineer lexicon assertion scans pair-label sample data in tests, not just field names. "Forex" itself allowed (vision §13); "Bread/Asset" labels (A-208).
  • x/gov tally shape drift (A-204, conf 0.75) → cosmos-engineer mirrors TallyResult field names (yes/no/abstain/total); test asserts field names.
  • Council ↔ Stand/Guild import cycles → reference by id-string only; test asserts no struct imports of x/stand/x/guild.

Phase P4 — Bonds + Bearers + L2

  • Slug: bonds-bearers-l2
  • Branch: oy/phase/04-bonds-bearers-l2
  • REQs covered: REQ-021 (Mesh Bond Market 8% cap), REQ-009 (L2 IBC Satellite — Polygon rep + 4 stubs), Bearers OY-LR + Beacon v1 (D-029)
  • Tag: v0.1.4
  • Goal: Ship the Mesh Bond module with 8% cap / 0% floor clamp invariants, extend the x/bearers module with the BearerTransport interface + OY-LR + Beacon stubs, and ship the L2 satellite module with ICS-20-shaped packet types for Polygon (representative) + 4 enum-placeholder chains.

Wave 1 — Module types (parallel; Bond blocked-by P1 Stand, Bearers/L2 independent)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P4-01-01 REQ-021 backend-engineer x/bond/types/types.go CouponCapBps = 800 (8%) and CouponFloorBps = 0 (0%) locked const (D-028); Bond struct (id, issuer-stand-id, principal-grain, coupon-bps, term-days, issued-at, maturity, status); BondStatus enum (Issued, Active, Matured, Defaulted, Repaid); Issue(...) stub; Clamp(couponBps) returns min(cap, max(floor, coupon)) — same shape as Fee Covenant's Clamp; issuer-stand-id references x/stand by id-string; DefaultParams, GenesisState (bonds), DefaultGenesisState, ValidateGenesis (reject dup bond-ids) go build ./x/bond/... succeeds; CouponCapBps == 800; CouponFloorBps == 0; Clamp matches Fee Covenant shape; ValidateGenesis rejects dup bond-id P1-02-01
P4-02-01 D-029 cosmos-engineer x/bearers/types/types.go (extend existing) EXTEND existing x/bearers (do NOT create new module — A-209); add BearerTransport Go interface (Send, Receive, Status — no impl); OYLRLink struct (gateway-id, range-meters, frequency-mhz, surveillance-resistant=true); BeaconFrame struct (beacon-id, ephemeral-id, payload-bytes, ttl); preserve existing BearerType enum + AllBearers() (OY-LR already present); DefaultParams/GenesisState unchanged go build ./x/bearers/... succeeds; BearerTransport interface compiles; OYLRLink + BeaconFrame structs present; existing AllBearers() unchanged (6 bearers)
P4-03-01 REQ-009 cosmos-engineer x/satellite/types/types.go L2Chain enum (Polygon active; Base, Arbitrum, Optimism, Solana as StatusPending per D-021); TransferChannel struct (port-id, channel-id, counterparty, status); ChannelStatus enum (Init, TryOpen, Open, Closed) — mirror ICS-20 handshake; WrappedBreadDenom struct (denom, trace-path) encoding IBC trace; Packet stub struct (sequence, source-port, source-channel, dest-port, dest-channel, data, timeout-height, timeout-timestamp) — pinned to ICS-20 v1 shape (A-215); no ibc-go import (zero deps); DefaultParams, GenesisState (channels + denoms), DefaultGenesisState, ValidateGenesis (reject dup channel-ids) go build ./x/satellite/... succeeds; L2Chain enum has 5 entries (Polygon + 4 stubs); Packet struct fields match ICS-20 v1; zero external deps confirmed

Wave 2 — Tests + genesis (parallel; blocked-by Wave 1)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P4-01-02 REQ-021 security-engineer x/bond/types/types_test.go Clamp invariant tests (highest-severity for bond): below floor → floor; above cap → cap; in range → unchanged; CouponCapBps == 800 locked-const; CouponFloorBps == 0 locked-const; BondStatus enum coverage; Issue stub callable; ValidateGenesis rejects dup bond-id; lexicon assertion (no "interest"/"yield" — use "coupon" exclusively, A-210) go test ./x/bond/... passes; ≥80% coverage; clamp invariant test breaks if cap/floor change; lexicon green (no banned terms) P4-01-01
P4-01-03 REQ-021 data-engineer x/bond/types/genesis.go Bond issuance genesis: bonds in GenesisState; ValidateGenesis enforces coupon-bps within [floor, cap] for each genesis bond (clamp at genesis load) go test ./x/bond/... passes; genesis clamp test green P4-01-01
P4-02-02 D-029 security-engineer x/bearers/types/types_test.go (extend existing) BearerTransport interface signature test (a stub impl satisfies it); OYLRLink struct non-empty + surveillance-resistant == true; BeaconFrame struct non-empty + ttl > 0; OY-LR still in AllBearers() (regression: existing v0.1 test still passes); lexicon assertion (extend existing) go test ./x/bearers/... passes; ≥80% coverage; existing v0.1 bearers tests still green (no regression) P4-02-01
P4-03-02 REQ-009 security-engineer x/satellite/types/types_test.go L2Chain enum has exactly 5 entries (Polygon + Base/Arbitrum/Optimism/Solana); Polygon is the only active rep (D-021); ChannelStatus enum coverage (4 states); Packet struct fields match ICS-20 v1 (cross-check field names); WrappedBreadDenom trace-path encoding; ValidateGenesis rejects dup channel-id; lexicon assertion (no "bank"/"account" — use "Holder"/"Reach") go test ./x/satellite/... passes; ≥80% coverage; 5-chain enum locked-const; ICS-20 shape test P4-03-01

Wave 3 — Phase verification + ship

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P4-04-01 REQ-012, D-033 lead-developer (cross-cutting) go build ./... + go test ./... green; P4 packages ≥80% coverage; 3 lexicon assertions (bond, bearers-ext, satellite); bond clamp invariant green; tag v0.1.4 go test ./... green; coverage ≥80% on x/bond/types, x/bearers/types, x/satellite/types; existing v0.1 tests still green; git tag v0.1.4 P4-01-02, P4-01-03, P4-02-02, P4-03-02

P4 Must-Haves

  • x/bond (new), x/bearers (extended), x/satellite (new) each have types/types.go + types/types_test.go.
  • go build ./... and go test ./... green — including all v0.1 baseline tests (no regression).
  • ≥80% coverage on x/bond/types, x/bearers/types, x/satellite/types.
  • Bond clamp invariant: CouponCapBps == 800, CouponFloorBps == 0; clamp below→floor, above→cap, in-range→unchanged.
  • Bond lexicon: "coupon" exclusively, no "interest"/"yield" (A-210).
  • Bearers: BearerTransport interface compiles; OYLRLink + BeaconFrame stubs; existing AllBearers() (6) unchanged.
  • Satellite: L2Chain exactly 5 (Polygon active + 4 stubs); Packet pinned to ICS-20 v1 shape; zero external deps.
  • Lexicon assertion in all 3 test files.
  • ValidateGenesis ID-uniqueness (all 3) + genesis clamp (Bond).
  • Git tag v0.1.4.

P4 Risks & Mitigations

  • Bond lexicon risk (RESEARCH §1.8, A-210 conf 1.00) → "coupon" exclusively; security-engineer lexicon assertion is the gate; "interest"/"yield" must never appear.
  • ICS-20 packet shape drift (A-215, RESEARCH §1.1) → pin to ICS-20 v1 (stable, widely implemented); test asserts field names; refactor cost is types-only if v0.3 picks a different ibc-go version.
  • Bearers extension regression → existing v0.1 bearers tests must stay green; AllBearers() count unchanged (6); test asserts no regression.
  • Full 5-chain IBC rollout is Phase 3 (D-021) → only Polygon is active; 4 chains are StatusPending enum placeholders; test asserts exactly 1 active.
  • Solana lacks native IBC (RESEARCH §1.1) → stubbed as StatusPending; no Solana light-client logic in v0.2.

Phase P5 — Final Review + Audit + Ship

  • Slug: final-review-audit-ship
  • Branch: oy/phase/05-final-review-audit-ship
  • REQs covered: REQ-012 (lexicon, project-wide meta-test), all v0.2 REQs (audit confirmation)
  • Tag: v0.1.5 (= milestone v0.2 release — final phase patch IS the milestone release per D-008/D-020)
  • Goal: Run the full milestone audit — project-wide lexicon meta-test, coverage gate across all 10 new/extended packages, all locked-const invariants green, all REQs in v0.2 scope have skeleton+tests — then ship the v0.2 milestone release as tag v0.1.5.

Wave 1 — Audit + meta-tests (parallel)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P5-01-01 REQ-012 security-engineer lexicon_meta_test.go (extend the P1-04-02 scaffolding — G-004/G-009) Extend the project-wide lexicon meta-test (created in P1-04-02 per G-004) to cover all 10 new/extended v0.2 packages: scans all x/**/*.go for banned terms (bank, deposit, interest, yield, currency, dollar, euro, account, savings, depositor); fails on any hit. The self-test table (G-009) from P1-04-02 remains and asserts each of the 9 banned terms' synthetic string is detected. This is an EXTENSION, not creation — the meta-test has been green and guarding since P1. go test ./... green; meta-test scans every .go under x/ (all 25 packages); self-test table passes for all 9 banned terms (G-009); a deliberately-injected banned term in any new file fails the meta-test P4-04-01, P1-04-02
P5-01-02 D-033 security-engineer (cross-cutting) Coverage gate: go test -cover ./x/window/... ./x/stand/... ./x/guild/... ./x/pact/... ./x/partner/... ./x/council/... ./x/forex/... ./x/bond/... ./x/bearers/... ./x/satellite/... — all ≥80%; produce a coverage report Coverage report shows ≥80% on all 10 new/extended packages; no package below threshold P4-04-01
P5-01-03 REQ-009..021 lead-developer .ciagent/oy/PLANS.md (this file, checklist update) + .ciagent/oy/REQUIREMENTS.md + .ciagent/oy/ROADMAP.md REQ-coverage audit: confirm every v0.2 REQ (REQ-009, REQ-011, REQ-015, REQ-016, REQ-017, REQ-018, REQ-020, REQ-021, Bearers OY-LR/Beacon, Forex v1) has skeleton + tests; update REQUIREMENTS.md status column (Pending → Skeleton). ROADMAP tag-line reconciliation (G-010): add a one-line note to ROADMAP.md that v0.1 pre-MVP shipped on the v0.0.x patch line (ROADMAP lines 4-13) and v0.2 ships on the v0.1.x patch line (config tag_base), so the v0.1.5 milestone release is not confused with a hypothetical v0.1.0 "MVP" tag. REQUIREMENTS.md status column updated for all v0.2 REQs; each REQ maps to a shipped module file; ROADMAP.md tag-line reconciliation note present P4-04-01

Wave 2 — Ship

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P5-02-01 (milestone) lead-developer (cross-cutting) Final ship: go build ./... + go test ./... green; all v0.1 baseline tests still green (no regression across 15 + 10 = 25 packages); create milestone release tag v0.1.5 (= v0.2 milestone release per D-008/D-020); update ROADMAP.md Phase 2 checkbox go test ./... green; v0.1.5 tag created; ROADMAP.md Phase 2 marked complete; release notes reference v0.2 scope P5-01-01, P5-01-02, P5-01-03

P5 Must-Haves

  • Project-wide lexicon meta-test scans all x/**/*.go for all 9 banned terms; green.
  • Coverage ≥80% on all 10 new/extended packages (window, stand, guild, pact, partner, council, forex, bond, bearers, satellite).
  • All locked-const invariants green: Window status count, 9 Stands, 4 Partner tiers, 6 Pacts, 3 Councils, Mission Lock non-amendable, Bond 8% cap / 0% floor clamp, Guild 0% fee, Forex spread cap ≥0, 5 L2 chains.
  • All v0.1 baseline tests still green (no regression across 25 packages).
  • REQUIREMENTS.md status column updated (all v0.2 REQs → Skeleton).
  • go build ./... and go test ./... green.
  • Git tag v0.1.5 created (= v0.2 milestone release).
  • ROADMAP.md Phase 2 marked complete.

P5 Risks & Mitigations

  • Lexicon drift via copy-pasted comments (RESEARCH §2) → meta-test scans comments + strings + identifiers; not just exported names.
  • Coverage cliff at integration boundaries → skeleton-only modules have low cyclomatic complexity, so 80% is achievable with table-driven tests on every exported const/struct/method (v0.1 baseline pattern).
  • Milestone versioning confusion (v0.2 milestone = v0.1.5 tag) → lead-developer enforces D-008/D-020: final phase patch IS the milestone release; no separate minor tag.

Coverage Targets (D-033)

Package Phase Target Locked-const tests
x/window/types P1 ≥80% WindowStatus count, lifecycle idempotency
x/stand/types P1 ≥80% 9 Stand types (exactly)
x/guild/types P1 ≥80% HandPassFeeBps == 0
x/pact/types P2 ≥80% 6 Pact types (exactly), Mission Lock non-amendable
x/partner/types P2 ≥80% 4 Partner tiers (exactly)
x/council/types P3 ≥80% 3 Councils (exactly), MissionLockAmendable == false
x/forex/types P3 ≥80% SpreadCapBps >= 0, lexicon-clean labels
x/bond/types P4 ≥80% CouponCapBps == 800, CouponFloorBps == 0, clamp invariant
x/bearers/types P4 ≥80% BearerTransport interface, OY-LR in AllBearers() (regression)
x/satellite/types P4 ≥80% 5 L2 chains (Polygon active + 4 stubs), ICS-20 Packet shape

Lexicon assertion (REQ-012): present in every new module's types_test.go (10 files) + project-wide meta-test in P5.


Task Count Summary

Phase Waves Tasks New/Extended Packages
P1 3 8 window, stand, guild (3) + lexicon meta-test scaffolding (G-004)
P2 3 5 pact, partner (2)
P3 3 6 council, forex (2)
P4 3 8 bond, bearers (ext), satellite (3)
P5 2 4 (audit/ship, 0 new — extends P1 lexicon meta-test)
Total 31 10 new/extended

Grill Binding Decisions Applied (G-001..G-010)

The Phase 0 grill (see .ciagent/oy/GRILL.md) returned 10 binding decisions, all applied to this plan and to PROJECT.md/RESEARCH.md/PERSONAS.md before Phase 0 ship:

ID Decision Applied to
G-001 Correct v0.1 baseline test count: 53 tests / 11 files (not 48) PROJECT.md D-033, RESEARCH.md
G-002 Lexicon assertion tests are NEW in v0.2 (v0.1 has zero lexicon tests); firewall is new work, not inherited PROJECT.md D-032/D-033, RESEARCH.md, PLANS.md P1-04-02
G-003 By-ID-string inter-module refs (A-203) enforced as a TESTED invariant in P1-01-02 (go/parser scan, no cross-module struct imports in production code) PLANS.md P1-01-02, RESEARCH.md §3
G-004 Lexicon meta-test scaffolding moved from P5 to P1 Wave 3 (new task P1-04-02); P5-01-01 EXTENDS it rather than creating it PLANS.md P1 Wave 3, P5-01-01
G-005 D-027 clarified: one x/pact module with PactType enum + 6 per-type execute-entry structs (A-207), NOT six micro-modules PROJECT.md D-027
G-006 go.mod is read-only in v0.2 (zero deps, A-201); any change is an escalation PERSONAS.md (all personas)
G-007 x/pact/x/partner/x/bond owned by backend-engineer; x/window/x/stand/x/guild/x/council/x/satellite/x/forex/x/bearers owned by cosmos-engineer PERSONAS.md
G-008 Genesis schema (genesis.go) = data-engineer; genesis test assertions (*_test.go incl genesis_test.go) = security-engineer PERSONAS.md
G-009 P5-01-01 lexicon meta-test includes a self-test table (synthetic string per banned term) so detection coverage is durably verified PLANS.md P1-04-02, P5-01-01
G-010 P5-01-03 reconciles ROADMAP.md tag-line narrative (v0.0.x vs v0.1.x) to prevent v0.1.5/v0.1.0 confusion PLANS.md P5-01-03

Per-Phase REQ Coverage

Phase REQs Components
P1 REQ-015, REQ-016, REQ-017 Window (fullest), 9 Stands, Guilds + Hand-Pass 0%
P2 REQ-020, REQ-018 6 Pacts, 4-tier Partner Spectrum
P3 REQ-011, Forex v1 3 Councils + Mission Lock, Forex Engine v1 stub
P4 REQ-021, REQ-009, Bearers Mesh Bond 8% cap, L2 IBC (Polygon rep), OY-LR + Beacon
P5 REQ-012 + all v0.2 REQs (audit) Lexicon meta-test, coverage gate, milestone ship

Cross-Phase Blockers (hard)

  • P1-01-01 (Window types) → blocks P2-02-01 (Partner Window-scoped actions), P2-01-01 (Pact window-id-ref).
  • P1-02-01 (Stand types) → blocks P2-01-01 (Pact StandRegistry stand-id-ref), P3-01-01 (Council Stand Council), P4-01-01 (Bond issuer-stand-id).
  • P1-03-01 (Guild types) → blocks P3-01-01 (Council Guild Council).
  • P4-04-01 (P4 ship) → blocks P5-01-01, P5-01-02, P5-01-03 (P5 audit).
  • All P(N) phase-ship tasks block P(N+1) Wave 1 tasks (soft ordering for branch hygiene; types themselves only depend on the listed hard blockers).

Milestone v0.3 — Bearers & Documentation — Phase Plan

This section APPENDS the v0.3 milestone plan to the v0.1/v0.2 plan above. It does NOT rewrite or supersede the earlier content. v0.3 bundles two work- streams under one feature milestone (D-034): (A) Bearers skeleton+tests (D-020/D-035 pattern) and (B) a README.md + MkDocs Material docs site with the REQ-012 lexicon firewall extended to docs (D-043). Tags run on the v0.2.x patch line (config.json tag_base: v0.2.x): P0 → v0.2.0, P1..P5 → v0.2.1..v0.2.5, P6 → v0.2.6 (= the v0.3 milestone release per D-008 — final phase patch IS the milestone release; no separate minor tag).

Milestone Summary

  • Milestone: v0.3 — Bearers & Documentation
  • Type: Feature (Bearers phases P4/P5 are feat; docs phases P1-P3 are docs/test; P6 is final)
  • Tag base: v0.2.x patch line (P0 ships as v0.2.0; execution phases v0.2.1..v0.2.5; final phase v0.2.6 IS the milestone release)
  • Phases: 7 — P0 (this PLAN) + P1..P3 (docs) + P4..P5 (Bearers feat) + P6 (final review/audit/ship).
  • Depth: skeleton + tests layer (D-020/D-035) for Bearers; docs deliverable (D-042/D-045) for the docs site; zero external Go deps (G-006; mkdocs is a build-only Python dep, not a Go dep).
  • Coverage target: ≥80% on each new/extended x/* package (D-033); lexicon assertion (REQ-012) in every new/extended test file (D-032); docs firewall (lexicon_meta_docs_test.go) green for README.md + docs/**/*.md.
  • New x/ modules*: 4 (x/exit, x/bridge, x/hub, x/services). Extended: 3 (x/bearers, x/partner, x/bond). Docs surface: new (docs/, mkdocs.yml, README.md). Firewall: 1 new sibling test (lexicon_meta_docs_test.go).
  • Phase ordering (D-044): P1 docs foundation + firewall-first → P2 nomads docs → P3 freeholders docs → P4 Bearers I (exit/bridge/bearers/partner-Anchor) → P5 Bearers II (hub/services/bond) → P6 review/ship. Firewall lands in P1 BEFORE content (P2/P3) so docs are lexicon-clean by construction. P4 precedes P5 for the Anchor→hub dependency.
  • Personas: backend-engineer (all Bearers Go modules), lead-developer (cross-cutting/ship), frontend-engineer (docs toolchain + firewall wiring, P1-P3 only), docs-writer (docs content, P1-P3 only). The v0.2 custom personas (cosmos-engineer, security-engineer) are NOT reactivated (A-315).

Cross-Phase Dependency Map (v0.3)

P1 (firewall + docs foundation) ──┬──► P2 (nomads docs)   [firewall scans docs/nomads/ as added]
                                  └──► P3 (freeholders docs) [firewall scans docs/freeholders/ + docs/reference/]

P4 (Bearers I: bridge, exit, bearers ext, partner ext) ──► P5 (Bearers II: hub, services, bond ext)
        │  x/bridge (Wave 1) ──► x/exit (Wave 2)        [exit bridge-route-id refs bridge by ID]
        │  x/partner Anchor (Wave 4) ──► x/hub (P5 Wave 1) [hub operator-partner-id refs Anchor by ID]
        └──► P5 all waves

P5 (Bearers II) ──► P6 (review/audit/ship)
P1..P5 (all execution) ──► P6

Hard cross-phase blockers (by-ID-string refs, no import cycles — G-003):

  • P4 Wave 1 x/bridge types → blocks P4 Wave 2 x/exit tests (exit's bridge-route-id references a BridgeRoute by ID-string — A-308/G-003).
  • P4 Wave 4 x/partner Anchor extension → blocks P5 Wave 1 x/hub (hub's operator-partner-id references an Anchor partner by ID-string — A-304/G-003). This is the edge that forces P4 before P5 (D-044).
  • P1 Wave 1 docs firewall → blocks P2/P3 docs content (firewall-first: a banned term slipped into a P2/P3 page fails the build, not the P6 review).
  • P5 ship → blocks P6 audit/ship.

All other inter-module refs (x/services→x/window, x/bond→x/stand, x/bridge→x/satellite, x/bridge→x/watcher) are to v0.1/v0.2 baseline modules (no v0.3 phase-ordering concern).


Phase P1 — Docs Foundation + Firewall Extension

  • Slug: docs-foundation-firewall
  • Branch: oy/phase/01-docs-foundation-firewall
  • REQs covered: REQ-028 (lexicon firewall extension to docs), REQ-027 (README.md + docs site foundation: README + shared docs + index)
  • Tag: v0.2.1
  • Type: feat/test+docs
  • Personas: frontend-engineer (toolchain + firewall), docs-writer (README + shared content)
  • Goal: Land the docs lexicon firewall (lexicon_meta_docs_test.go) + the MkDocs Material scaffold (mkdocs.yml) + README.md + docs/index.md + docs/shared/ pages BEFORE any audience docs content (P2/P3), so docs are lexicon-clean by construction (D-044 firewall-first).

Wave 1 — Firewall + scaffold FIRST (parallel)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P1-01-01 REQ-028 frontend-engineer lexicon_meta_docs_test.go (repo root, package lexicon_meta_docs) NEW sibling meta-test mirroring lexicon_meta_test.go (D-043). Uses the SAME lexicon.FindBannedTerm (word-boundary, case-insensitive) — NO detection reimplementation. Walks the REPO ROOT (not x/): targets README.md (repo root) + every *.md under docs/ (recursive). Excludes .ciagent/ (firewall meta-files, not user-facing), .git/ (VCS), the meta-test file itself (self-exclusion via runtime.Caller(0)), and non-.md files under docs/. Includes the G-009 self-test table (one synthetic string per banned term, assembled from lexicon.BannedTerms() fragments so the test file's own source has no banned-term literal), TestLexiconMetaDocsBannedTermsCount (exactly 10), and TestLexiconMetaDocsNoFalsePositiveOnOpenYield (word-boundary does not match "openyield"/"european"). The firewall PASSES at P1 time with zero docs (or with only README + docs/index.md + docs/shared/ from Wave 2). go test ./lexicon_meta_docs/... green (invoked as go test -run TestLexiconMetaDocs ./... or via the repo-root file); self-test table passes for all 10 banned terms; TestLexiconMetaDocsNoFalsePositiveOnOpenYield green; a deliberately-injected banned term in a docs/*.md file fails the test
P1-01-02 REQ-027 frontend-engineer mkdocs.yml (repo root) MkDocs Material config (D-042): site_name: OpenYield; theme: name: material with navigation.sections/navigation.expand/toc.integrate features; markdown_extensions: [admonition, toc (permalink: true), pymdownx.superfences]; nav: skeleton with Home + Nomads + Freeholders + Shared + Reference sections (audience-organized per D-042). The nav references the P2/P3 pages by path (pages need not exist yet at P1 — mkdocs.yml is a config file, not validated by Go tests; the docs firewall does not validate nav, only .md content). Build-only Python dep; go.mod stays zero-dep (G-006). No publishing CI (D-046). mkdocs.yml is valid YAML (parses; documented mkdocs serve / mkdocs build invocation goes in README P1-02-01); go.mod unchanged (zero require lines); nav has the 4 audience sections + Home

Wave 2 — README + docs/index.md + docs/shared/ (parallel; blocked-by Wave 1 firewall)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P1-02-01 REQ-027 frontend-engineer README.md (repo root) Repo-root project overview: one-paragraph OpenYield description (lexicon-clean — "real production"/"real return" not "yield"; "Holder"/"Reach" not "account"; "Stash"/"Vault"/"Root-Pool" not "bank"/"deposit"/"savings"); build instructions (go build ./..., go test ./...); docs build instructions (mkdocs serve / mkdocs build per D-046); link to docs/ site; pointer to .ciagent/oy/PROJECT.md for governance. Lexicon-clean by construction (the P1-01-01 firewall scans README.md). README.md exists; go test ./lexicon_meta_docs/... green (README is scanned); go test ./... green (no Go regression — README is not a Go file) P1-01-01
P1-02-02 REQ-027 docs-writer docs/index.md Site home page: one-paragraph OpenYield overview (lexicon-clean), links to the 4 audience sections (nomads/freeholders/shared/reference), pointer to README for build instructions. docs/index.md exists; docs firewall green (index scanned) P1-01-01
P1-02-03 REQ-027 docs-writer docs/shared/six-principles.md Six Principles page (REQ-001): real value, sustainability, mission-lock, openness, ownership, self-service. Lexicon-clean (the firewall scans docs/shared/**/*.md). Page exists; firewall green P1-01-01
P1-02-04 REQ-027 docs-writer docs/shared/bread-scale.md Bread Scale page (REQ-013): Grain → Crumb → Bread → Loaf → Batch → Cake → Bakery → Granary → Mill → Harvest → Earth. Page exists; firewall green P1-01-01
P1-02-05 REQ-027 docs-writer docs/shared/storage-pools.md Storage Pools page (REQ-014): Stash (Holder), Vault (Stand), Root-Pool (treasury). Lexicon-clean ("Stash"/"Vault"/"Root-Pool" not "bank"/"deposit"/"savings"). Page exists; firewall green P1-01-01
P1-02-06 REQ-027 docs-writer docs/shared/watchers-mirror.md Watchers / Mirror page (REQ-004): 9 Watchers, 6-of-9 quorum, daily attestations, 100,000 Bread bond each. Page exists; firewall green P1-01-01
P1-02-07 REQ-027 docs-writer docs/shared/lexicon-glossary.md Lexicon Glossary page (REQ-012): the 10 banned terms named BY THEIR SAFE ALTERNATIVES (the page documents the safe phrasings — "real production"/"Holder"/"Stash"/"coupon" — NOT the banned literals; the firewall scans this page, so the banned terms must NOT appear as literals, only as the safe replacements described in prose). Cross-reference the firewall design (D-043). Page exists; firewall green (no banned-term literals — the glossary describes replacements, not the banned words themselves) P1-01-01
P1-02-08 REQ-027 docs-writer docs/shared/vision-overview.md Vision Overview page: the OpenYield covenant (real production, anti-greed, jurisdiction-light, public-good mesh), pointer to .ciagent/oy/PROJECT.md for the full vision source. Page exists; firewall green P1-01-01

Wave 3 — Phase verification + ship

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P1-03-01 REQ-012, REQ-027, REQ-028 lead-developer (cross-cutting) Run go build ./... + go test ./... across the whole repo (no Go regression — the docs firewall is a NEW Go test file but adds no x/* Go code); confirm go test ./lexicon_meta_docs/... green; confirm mkdocs.yml valid; confirm README.md + docs/index.md + 6 docs/shared/ pages exist and are lexicon-clean; tag v0.2.1. go test ./... green (incl. v0.1/v0.2 baseline + the new docs firewall); docs firewall green; mkdocs.yml valid; 8 docs files (README + index + 6 shared) exist + lexicon-clean; git tag v0.2.1 created P1-01-01, P1-01-02, P1-02-01..08

P1 Must-Haves

  • lexicon_meta_docs_test.go exists at repo root (package lexicon_meta_docs); mirrors lexicon_meta_test.go detection (same lexicon.FindBannedTerm + word-boundary regex + self-test table G-009 + self-exclusion); scans README.md + docs/**/*.md; excludes .ciagent/ + .git/ + itself.
  • go test ./lexicon_meta_docs/... green (firewall passes with README + docs/index.md + docs/shared/ present).
  • go test ./... green across the whole repo (no Go regression; the v0.2 lexicon_meta_test.go is UNCHANGED per D-043).
  • mkdocs.yml exists at repo root (Material theme, 4 audience sections in nav, admonition + toc + superfences extensions); go.mod unchanged (zero require lines).
  • README.md exists (lexicon-clean; build/test/docs-build instructions).
  • docs/index.md exists (site home).
  • 6 docs/shared/ pages exist (Six Principles, Bread Scale, Storage Pools, Watchers/Mirror, Lexicon Glossary, Vision Overview) — all lexicon-clean.
  • Docs firewall self-test table passes for all 10 banned terms (G-009 for docs).
  • TestLexiconMetaDocsNoFalsePositiveOnOpenYield green.
  • Git tag v0.2.1.

P1 Risks & Mitigations

  • Banned-term literals in the lexicon-glossary page (highest P1 risk) → docs-writer must describe SAFE ALTERNATIVES, not the banned words themselves; the firewall scans docs/shared/lexicon-glossary.md directly (unlike .go fragment assembly). Mitigation: P1-01-01 firewall is the gate; a literal banned term fails the P1 build.
  • mkdocs.yml nav references non-existent P2/P3 pages → mkdocs.yml is a config file, not Go-tested; nav can list future pages. The firewall scans .md content, not nav. Mitigation: P2/P3 create the referenced pages; missing pages are a mkdocs build warning, not a Go test failure.
  • README "yield" false positive → the firewall word-boundary regex allows "OpenYield" but bans standalone "yield"; README must say "real production"/"real return". Mitigation: TestLexiconMetaDocsNoFalsePositiveOnOpenYield is the regression firewall.

Phase P2 — Nomads Docs

  • Slug: nomads-docs
  • Branch: oy/phase/02-nomads-docs
  • REQs covered: REQ-027 (nomads audience docs, 7 pages per D-045)
  • Tag: v0.2.2
  • Type: docs
  • Personas: docs-writer (content); frontend-engineer (toolchain verify — firewall now scans the new docs/nomads/ files)
  • Goal: Ship the 7-page nomads audience docs (docs/nomads/) covering the Reach path, Stash, bearers, Maps/Pay, six Pacts, standing basics, and the Window primitive — all lexicon-clean (the P1 firewall now scans these files as they are added).

Wave 1 — Nomads content pages (parallel; all blocked-by P1 firewall landing)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P2-01-01 REQ-027 (REQ-005 Reach) docs-writer docs/nomads/reach.md What a Nomad is + the Reach path (REQ-005): how a person becomes a Holder via a Reach ID, no-KYC at protocol level, geographic-proximity FCFS (REQ-007). Lexicon-clean ("Holder"/"Reach" not "account"; "real production" not "yield"). Page exists; docs firewall green (scans docs/nomads/reach.md) P1-03-01
P2-01-02 REQ-027 (REQ-014 Stash) docs-writer docs/nomads/stash.md Stash usage (REQ-014): the Holder-level storage pool, how Bread is held in a Stash, the 90-day Freeholder-signal Stash requirement. Lexicon-clean ("Stash" not "bank"/"deposit"/"savings"). Page exists; firewall green P1-03-01
P2-01-03 REQ-027 (REQ-019 bearers) docs-writer docs/nomads/bearers.md Bearers for nomads (REQ-019): the six bearers (Internet, OY-LR, OY-BLE, OY-WiFi-Direct, OY-SAT, OY-QR) via the Unified Bearer Layer, first-to-deliver-wins, surveillance resistance. Lexicon-clean. Page exists; firewall green P1-03-01
P2-01-04 REQ-027 (Mesh Experience) docs-writer docs/nomads/maps-pay.md Maps / Pay Mesh Experience: how a Nomad uses Maps and Pay day-to-day (Maya's Day deferred per PROJECT.md out-of-scope Q1). Lexicon-clean. Page exists; firewall green P1-03-01
P2-01-05 REQ-027 (REQ-020 Pacts) docs-writer docs/nomads/pacts.md Six Pacts (REQ-020): Pause, Ground, Stance, Cover, Stand Registry, Hub API — what each means for a Nomad. Lexicon-clean. Page exists; firewall green P1-03-01
P2-01-06 REQ-027 (REQ-006 standing basics) docs-writer docs/nomads/standing-basics.md Standing basics for nomads (REQ-006): what Bayesian Standing is in plain language, how it accrues, why it matters (no formula math — defer detail to freeholders/bayesian-standing). Lexicon-clean. Page exists; firewall green P1-03-01
P2-01-07 REQ-027 (REQ-015 Window) docs-writer docs/nomads/window.md Window primitive (REQ-015): scope, duration, rate-limit, audit log, revoke — what a Window means for a Nomad delegating access to a partner/service. Lexicon-clean. Page exists; firewall green P1-03-01
P2-01-08 REQ-027 docs-writer docs/nomads/index.md Nomads section index: one-paragraph intro + links to the 7 nomads pages. Lexicon-clean. Page exists; firewall green P1-03-01

Wave 2 — Phase verification + ship

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P2-02-01 REQ-012, REQ-027 lead-developer (cross-cutting) Run go test ./... (docs firewall now scans the 8 new docs/nomads/*.md files and passes); confirm all 8 nomads pages lexicon-clean; tag v0.2.2. go test ./... green (docs firewall green with nomads content); 8 docs/nomads/*.md exist + lexicon-clean; git tag v0.2.2 P2-01-01..08

P2 Must-Haves

  • 8 docs/nomads/*.md pages exist (index + reach + stash + bearers + maps-pay + pacts + standing-basics + window) — all lexicon-clean.
  • go test ./... green (the P1 docs firewall now scans docs/nomads/**/*.md and passes).
  • go test ./lexicon_meta_docs/... green specifically.
  • No Go code changes (P2 is pure docs; go build ./... green by no-regression).
  • Git tag v0.2.2.

P2 Risks & Mitigations

  • "account"/"bank"/"deposit" drift in nomads prose (Stash/Reach pages are highest-risk) → docs-writer uses "Holder"/"Reach"/"Stash"; the firewall is the gate (fails the P2 build, not P6 review — D-044 firewall-first value).
  • Standing-basics page over-promises formula detail → defer math to freeholders/bayesian-standing (P3); nomads page stays conceptual.

Phase P3 — Freeholders Docs + Reference

  • Slug: freeholders-docs-reference
  • Branch: oy/phase/03-freeholders-docs-reference
  • REQs covered: REQ-027 (freeholders audience docs 7 pages + reference 2 pages; REQ-027 COMPLETE at end of P3)
  • Tag: v0.2.3
  • Type: docs
  • Personas: docs-writer (content); frontend-engineer (toolchain verify — firewall now scans docs/freeholders/ + docs/reference/)
  • Goal: Ship the 7-page freeholders audience docs (docs/freeholders/) covering the four signals, Bayesian Standing, Stands/Guilds, Councils/Voice, Bonds, Partner spectrum, Anchor preview — plus the 2-page reference section (docs/reference/). REQ-027 (docs deliverable) is COMPLETE at the end of P3. After P3, frontend-engineer and docs-writer are removed (phase-specific personas, P1-P3 only).

Wave 1 — Freeholders content pages (parallel)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P3-01-01 REQ-027 (REQ-005 four signals) docs-writer docs/freeholders/four-signals.md Four Freeholder signals (REQ-005): 90d Stash, 4.5★+ in 3 categories, Capital, Vouch. Lexicon-clean. Page exists; firewall green P2-02-01
P3-01-02 REQ-027 (REQ-006 Bayesian Standing) docs-writer docs/freeholders/bayesian-standing.md Bayesian Standing (REQ-006): the anti-gaming formula (Bayesian + time-decay + diversity + voucher-weighted slashes), why it resists gaming. Formula at conceptual depth (full sub-tables deferred per PROJECT.md Q2). Lexicon-clean. Page exists; firewall green P2-02-01
P3-01-03 REQ-027 (REQ-016/017 Stands/Guilds) docs-writer docs/freeholders/stands-guilds.md Stands & Guilds (REQ-016/017): the 9 Stand types (Household…Shadow), Guilds with Hand-Passes at 0% protocol fee. Lexicon-clean ("Stash"/"Vault" not "bank"; "Hand-Pass"/"0% protocol fee" not "interest"). Page exists; firewall green P2-02-01
P3-01-04 REQ-027 (REQ-011 Councils/Voice) docs-writer docs/freeholders/councils-voice.md Councils & Voice (REQ-011): the three Councils (Mesh, Guild, Stand), multi-source Voice, Mission Lock cannot be amended. Lexicon-clean. Page exists; firewall green P2-02-01
P3-01-05 REQ-027 (REQ-021 Bonds) docs-writer docs/freeholders/bonds.md Bonds (REQ-021): the Mesh Bond Market, 8% upper coupon cap / 0% floor, Growth Bonds (preview of v0.3 P5 work). Lexicon-clean ("coupon"/"growth" not "interest"/"yield"). Page exists; firewall green P2-02-01
P3-01-06 REQ-027 (REQ-018 Partner spectrum) docs-writer docs/freeholders/partner-spectrum.md Partner Spectrum (REQ-018): the four tiers (Op, Master Op, Pier, Anchor). Lexicon-clean ("Partner"/"Reach" not "account"). Page exists; firewall green P2-02-01
P3-01-07 REQ-027 (REQ-023 Anchor preview) docs-writer docs/freeholders/anchor-preview.md Anchor preview (REQ-023): the first institutional Partner tier, the Anchor credential (preview of v0.3 P4 work), custody/compliance relationship. Lexicon-clean ("custody"/"compliance"/"jurisdiction" safe; not "bank"/"account"). Page exists; firewall green P2-02-01
P3-01-08 REQ-027 docs-writer docs/freeholders/index.md Freeholders section index: one-paragraph intro + links to the 7 freeholders pages. Lexicon-clean. Page exists; firewall green P2-02-01

Wave 2 — Reference pages (parallel; blocked-by Wave 1)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P3-02-01 REQ-027 docs-writer docs/reference/architecture-index.md Architecture index: the 14-component index (from .ciagent/oy/ARCHITECTURE.md Component Index), the 6 cross-component interfaces, the critical blocker chain. Lexicon-clean (rewrite the architecture terms in user-facing prose — do NOT copy .ciagent/ content verbatim if it contains banned-term discussions; the firewall scans this page). Page exists; firewall green P3-01-01..08
P3-02-02 REQ-027 docs-writer docs/reference/component-map.md Component map: a table of v0.1/v0.2/v0.3 modules (x/<name>/) mapped to vision sections and REQs. Lexicon-clean. Page exists; firewall green P3-01-01..08

Wave 3 — Phase verification + ship (frontend-engineer + docs-writer removed after this phase)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P3-03-01 REQ-012, REQ-027 lead-developer (cross-cutting) Run go test ./... (docs firewall now scans docs/freeholders/**/*.md + docs/reference/**/*.md and passes); confirm all 10 new pages lexicon-clean; REQ-027 COMPLETE (total docs pages: 1 README + 1 index + 6 shared + 8 nomads + 8 freeholders + 2 reference = 26 pages, within D-045 20-25 budget +README/index); tag v0.2.3. Remove frontend-engineer + docs-writer personas (phase-specific, P1-P3 only). go test ./... green (docs firewall green with all docs content); REQ-027 marked complete in REQUIREMENTS.md; git tag v0.2.3; personas removed P3-01-01..08, P3-02-01..02

P3 Must-Haves

  • 8 docs/freeholders/*.md pages exist (index + four-signals + bayesian-standing + stands-guilds + councils-voice + bonds + partner-spectrum + anchor-preview) — all lexicon-clean.
  • 2 docs/reference/*.md pages exist (architecture-index + component-map) — all lexicon-clean.
  • go test ./... green (docs firewall scans the full docs/ tree + README and passes).
  • go test ./lexicon_meta_docs/... green.
  • No Go code changes (P3 is pure docs; go build ./... green by no-regression).
  • REQ-027 marked COMPLETE in REQUIREMENTS.md (docs deliverable done).
  • frontend-engineer + docs-writer personas removed (phase-specific, P1-P3 only).
  • Git tag v0.2.3.

P3 Risks & Mitigations

  • "interest"/"yield" in bonds page (highest P3 risk) → docs-writer uses "coupon"/"growth"/"real return"; firewall is the gate.
  • Architecture-index page copies .ciagent/ banned-term discussions verbatim.ciagent/ files discuss banned terms by name for governance but are excluded from the firewall; the reference page is NOT excluded, so it must use safe phrasings. Mitigation: docs-writer rewrites in user-facing prose; firewall scans docs/reference/architecture-index.md.

Phase P4 — Bearers Skeleton I (exit/bridge/bearers/partner-Anchor)

  • Slug: bearers-skeleton-i
  • Branch: oy/phase/04-bearers-skeleton-i
  • REQs covered: REQ-010 (Exit layer: x/bridge + x/exit), REQ-022 (Bearers OY-SAT/OY-QR), REQ-023 (Anchors — x/partner extension)
  • Tag: v0.2.4
  • Type: feat
  • Persona: backend-engineer (all Go modules; the v0.2 cosmos-engineer/security-engineer split is collapsed per A-315)
  • Goal: Ship the Bearers skeleton I: x/bridge (L2↔L1 bridge types) FIRST, then x/exit (exit routes + DEXSwap, referencing bridge by ID-string), then x/bearers extension (OY-SAT/OY-QR transport stubs), then x/partner extension (Anchor credential types — referenced by x/hub in P5).

Wave 1 — x/bridge types FIRST (intra-P4 ordering: bridge before exit)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P4-01-01 REQ-010 backend-engineer x/bridge/types/types.go + x/bridge/types/genesis.go New x/bridge/types/ module following the x/satellite pattern (D-036, A-302). BridgeStatus enum (Pending, Attested, Active, Closed) — exactly 4, locked-const BridgeStatusCount = 4 (A-312 names HubServiceCount; BridgeStatusCount is the bridge analog). AllBridgeStatuses() []BridgeStatus. BridgeRoute struct (route-id, source-chain (L2Chain by-ID-string ref to x/satellite — G-003, no struct import), dest-chain (L2Chain by-ID-string), bridge-type (opaque string e.g. "ibc" — NOT a locked enum per A-308), transfer-channel-id (by-ID-string ref to a v0.2 satellite TransferChannel), watcher-quorum-id (by-ID-string ref to x/watcher, set when status becomes Attested), status). Keeper stub: AddBridgeRoute / GetBridgeRoute / ListByStatus. Params, GenesisState (routes), DefaultGenesisState, ValidateGenesis (reject dup route-ids, A-212). go build ./x/bridge/... succeeds; BridgeStatusCount == 4; AllBridgeStatuses() returns 4 in vision order; ValidateGenesis rejects dup route-id; zero external deps (go.mod unchanged)

Wave 2 — x/exit types (blocked-by Wave 1 bridge)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P4-02-01 REQ-010 backend-engineer x/exit/types/types.go + x/exit/types/genesis.go New x/exit/types/ module following x/satellite (D-036). ExitStatus enum (Proposed, InProgress, Settled, Failed, Refunded) — exactly 5, locked-const ExitStatusCount = 5. AllExitStatuses() []ExitStatus. ExitRoute struct (route-id, holder-reach-id (by-ID-string ref to x/identity — use "Holder"/"Reach" not "account"), source-asset (opaque string), dest-asset (opaque string), amount-grain (int64 — NOT a x/bread import; "Grain" by name only), min-received-grain, bridge-route-id (OPTIONAL, by-ID-string ref to x/bridge BridgeRoute for cross-chain exits — G-003), venue-hops []string, deadline, status). DEXSwap struct (swap-id, route-id (by-ID-string ref to ExitRoute), venue (opaque string — NOT a locked enum per A-308), input-asset, input-amount-grain, output-asset, output-amount-grain, executed-at). Keeper stub: AddExitRoute / GetExitRoute / ListByHolder. Params, GenesisState (routes + swaps), DefaultGenesisState, ValidateGenesis (reject dup route-ids + dup swap-ids, A-212). go build ./x/exit/... succeeds; ExitStatusCount == 5; AllExitStatuses() returns 5 in vision order; ValidateGenesis rejects dup route-id + dup swap-id; the bridge-route-id field is a string (no x/bridge struct import — G-003 verified by the P1 G-003 import-invariant test, which now also scans the new x/exit + x/bridge files) P4-01-01

Wave 3 — x/exit tests + x/bridge tests (parallel; blocked-by Wave 1/2 types)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P4-01-02 REQ-010, REQ-012 backend-engineer x/bridge/types/types_test.go (+ genesis_test.go if split) Locked-const test: BridgeStatusCount == 4; AllBridgeStatuses() names match (Pending, Attested, Active, Closed); BridgeRoute struct round-trip (marshal/unmarshal); bridge-route-id references a satellite L2Chain by string (no x/satellite import — G-003 import-invariant test green); watcher-quorum-id is an opaque string (no x/watcher import); ValidateGenesis rejects dup route-id; lexicon assertion (no "bank"/"account"/"currency"/"dollar"/"euro" — use "Holder"/"Reach"/chain names); G-003 import-invariant: the P1-01-02 go/parser scan (extended to cover the new x/bridge + x/exit files) asserts NO production file imports another x/<module>/types by struct. go test ./x/bridge/... passes; ≥80% coverage on x/bridge/types; BridgeStatusCount=4 locked-const; lexicon green; G-003 import-invariant green P4-01-01
P4-02-02 REQ-010, REQ-012 backend-engineer x/exit/types/types_test.go (+ genesis_test.go if split) Locked-const test: ExitStatusCount == 5; AllExitStatuses() names match (Proposed, InProgress, Settled, Failed, Refunded); ExitRoute struct round-trip; DEXSwap struct round-trip; the bridge-route-id field references a BridgeRoute by string (no x/bridge struct import — G-003 verified); venue is an opaque string (A-308 — not a locked enum, so no enum-count test); ValidateGenesis rejects dup route-id + dup swap-id; lexicon assertion (no "account"/"currency"/"dollar"/"euro" — use "Holder"/"Reach"/opaque asset strings). go test ./x/exit/... passes; ≥80% coverage on x/exit/types; ExitStatusCount=5 locked-const; lexicon green; G-003 import-invariant green P4-02-01

Wave 4 — x/bearers extension + x/partner extension (parallel; independent of Waves 1-3)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P4-03-01 REQ-022 backend-engineer x/bearers/types/types.go (EXTEND existing) EXTEND x/bearers (do NOT create new module — A-209/D-037; the BearerType enum + BearerTransport interface are locked since v0.1/v0.2). Add OYSATLink struct (gateway-id, constellation (opaque string e.g. "iridium"), frequency-mhz, surveillance-resistant LOCKED true for OY-SAT — A-311). Add OYQRCode struct (qr-id, payload-bytes (the signed transfer), issuer-reach-id, expires-at, consumed (bool — OY-QR is one-shot, A-311)). Both are transport-shape stubs (matching D-029: the v0.2 OYLRLink/BeaconFrame are struct stubs, not BearerTransport impls; v0.3 keeps the same shape-only approach). The existing BearerType enum + AllBearers() (6 bearers, including BearerOYSAT + BearerOYQR since v0.1) is UNCHANGED — v0.3 adds transport structs only. GenesisState unchanged. go build ./x/bearers/... succeeds; OYSATLink + OYQRCode structs present; OYSATLink.SurveillanceResistant is LOCKED true; OYQRCode.Consumed field exists; existing AllBearers() unchanged (6 bearers — regression); zero external deps
P4-04-01 REQ-023 backend-engineer x/partner/types/types.go (EXTEND existing) EXTEND x/partner (do NOT create new module — A-305; the 4-tier PartnerTier enum is locked since v0.2). Add AnchorCredential struct (partner-id (by-ID-string ref to the Anchor Partner), jurisdiction (opaque string e.g. "EU-MiCA"), custody-provider-id (by-ID-string ref to x/hub custody service — EMPTY in v0.3 skeleton per A-304, hub not live until P5/v0.4), attestation-refs []string (opaque URIs to Watcher/auditor attestations), onboarded-at). Add Partner.AnchorCredential() *AnchorCredential accessor stub returning nil for non-Anchor tiers (A-305 prefers the accessor over a second top-level type). Add Keeper.AddAnchorCredential(partnerID, cred) convenience method (rejects non-Anchor partner-id); Keeper.ListAnchors() = ListByTier(TierAnchor) alias. PartnerTier enum (4 tiers) UNCHANGED — v0.3 adds Anchor-specific fields, not a new tier. go build ./x/partner/... succeeds; AnchorCredential struct present; Partner.AnchorCredential() returns nil for non-Anchor; AddAnchorCredential rejects non-Anchor partner-id; AllPartnerTiers() unchanged (4 tiers — regression); custody-provider-id field is a string (no x/hub import — G-003; x/hub does not exist yet, lands in P5)

Wave 5 — x/bearers + x/partner tests (parallel; blocked-by Wave 4)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P4-03-02 REQ-022, REQ-012 backend-engineer x/bearers/types/types_test.go (EXTEND existing) OYSATLink struct round-trip + SurveillanceResistant == true (LOCKED — A-311); OYQRCode struct round-trip + Consumed flips true (one-shot); BearerOYSAT + BearerOYQR still in AllBearers() (regression: existing v0.1/v0.2 bearers tests still green — 6 bearers unchanged); existing v0.2 OYLRLink/BeaconFrame tests still green (no regression); lexicon assertion (extend existing). go test ./x/bearers/... passes; ≥80% coverage on x/bearers/types; OY-SAT surveillance-resistant LOCKED true; OY-QR one-shot; existing v0.1/v0.2 bearers tests green (no regression) P4-03-01
P4-04-02 REQ-023, REQ-012 backend-engineer x/partner/types/types_test.go (EXTEND existing) AnchorCredential struct round-trip; Partner.AnchorCredential() returns nil for non-Anchor tiers; AddAnchorCredential rejects non-Anchor partner-id; ListAnchors() returns only Anchor-tier partners; AllPartnerTiers() unchanged (4 tiers — regression: v0.2 partner tests still green); custody-provider-id is an opaque string (no x/hub import — G-003 import-invariant green); lexicon assertion (extend existing — no "bank"/"account"; "custody"/"jurisdiction"/"compliance" safe). go test ./x/partner/... passes; ≥80% coverage on x/partner/types; Anchor accessor + AddAnchorCredential behavior; existing v0.2 partner tests green (no regression) P4-04-01

Wave 6 — Phase verification + ship

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P4-05-01 REQ-012, REQ-010, REQ-022, REQ-023 lead-developer (cross-cutting) Run go build ./... + go test ./... green (incl. v0.1/v0.2 baseline + docs firewall + new x/bridge, x/exit, x/bearers ext, x/partner ext); coverage ≥80% on the 4 P4 packages; 4 lexicon assertions present; G-003 import-invariant green (extended to scan the new x/bridge + x/exit + x/bearers + x/partner files); lexicon_meta_test.go (v0.2, x/*.go scan) green; lexicon_meta_docs_test.go (v0.3, docs scan) green; zero external deps (go.mod unchanged); tag v0.2.4. go test ./... green; coverage ≥80% on x/bridge/types, x/exit/types, x/bearers/types, x/partner/types; 4 lexicon assertions; G-003 import-invariant green; zero require lines in go.mod; git tag v0.2.4 P4-01-02, P4-02-02, P4-03-02, P4-04-02

P4 Must-Haves

  • x/bridge (new), x/exit (new), x/bearers (extended), x/partner (extended) each have types/types.go + types/types_test.go (v0.1/v0.2 pattern, package types, zero external deps).
  • go build ./... and go test ./... green — including all v0.1/v0.2 baseline tests + docs firewall (no regression).
  • ≥80% coverage on x/bridge/types, x/exit/types, x/bearers/types, x/partner/types.
  • Bridge locked-const: BridgeStatusCount == 4 (Pending, Attested, Active, Closed).
  • Exit locked-const: ExitStatusCount == 5 (Proposed, InProgress, Settled, Failed, Refunded).
  • Exit bridge-route-id is a by-ID-string ref to x/bridge (G-003 — no struct import; import-invariant test green).
  • Bearers: OYSATLink (surveillance-resistant LOCKED true) + OYQRCode (one-shot consumed); existing AllBearers() (6) unchanged.
  • Partner: AnchorCredential struct + Partner.AnchorCredential() accessor (nil for non-Anchor); PartnerTier (4) unchanged.
  • Partner Anchor custody-provider-id is an opaque string (no x/hub import — x/hub lands in P5).
  • Lexicon assertion in all 4 P4 test files.
  • lexicon_meta_test.go (v0.2, x/*.go) green; lexicon_meta_docs_test.go (v0.3, docs) green.
  • Zero external deps (go.mod unchanged — G-006).
  • Git tag v0.2.4.

P4 Risks & Mitigations

  • Intra-P4 ordering (bridge before exit) (A-308/G-003) → Wave 1 lands x/bridge types; Wave 2 lands x/exit (references bridge by ID); Wave 3 lands both tests. Reversing would force x/exit tests to reference a non-existent BridgeRoute type.
  • "venue" as a locked enum (A-308) → venue is an opaque string, NOT a locked enum; venues are operational (uniswap-v3/v4, oy-dex) and locking now would create a false firewall. Test asserts no enum-count for venue.
  • Bearers extension regression → existing v0.1/v0.2 bearers tests must stay green; AllBearers() count unchanged (6); test asserts no regression.
  • Partner Anchor → hub forward-referencecustody-provider-id is an EMPTY string in v0.3 (hub not live until P5); the field exists so the shape is stable. This is the P4→P5 edge (D-044).
  • "account"/"currency"/"dollar"/"euro" lexicon drift in exit → use "Holder"/"Reach"/opaque asset strings; lexicon assertion + meta-test are the gate.

Phase P5 — Bearers Skeleton II (hub/services/bond)

  • Slug: bearers-skeleton-ii
  • Branch: oy/phase/05-bearers-skeleton-ii
  • REQs covered: REQ-024 (Hub API — x/hub), REQ-025 (Services — x/services), REQ-026 (Bond market depth — x/bond extension)
  • Tag: v0.2.5
  • Type: feat
  • Persona: backend-engineer
  • Goal: Ship the Bearers skeleton II: x/hub (HubService enum + per-service stubs, referencing Anchor partners by ID), x/services (ServiceKind enum + per-service stubs, referencing Window by ID), and x/bond extension (GrowthBond + secondary-market order types; 8%/0% consts unchanged — D-028 regression firewall). P5 has NO intra-phase ordering (hub, services, bond are independent of each other).

Wave 1 — x/hub types + tests (blocked-by P4 partner Anchor for the operator-partner-id reference)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P5-01-01 REQ-024 backend-engineer x/hub/types/types.go + x/hub/types/genesis.go + x/hub/types/types_test.go (+ genesis_test.go if split) New x/hub/types/ module following x/forex (D-039, A-303). HubService enum (Custody, LendingPrimitive, Compliance) — exactly 3, locked-const HubServiceCount = 3 (A-312); AllHubServices() []HubService. HubServiceInfo struct (service-id, kind (HubService), operator-partner-id (by-ID-string ref to x/partner Anchor — G-003, no struct import; the P4 Anchor must exist), name, status). HubServiceStatus enum (Pending, Active, Suspended, Revoked) — exactly 4 (local redefinition of the v0.2 PartnerStatus 4-state shape, no import). Per-service struct stubs: CustodyService (service-id, custody-provider-id, assets-supported []string), LendingPrimitiveService (service-id, primitive-kind (opaque string), coupon-cap-bps uint32 — LOCAL const LendingCouponCapBps = 800 cross-documented to D-028/A-304, NOT an import of x/bond.Clamp), ComplianceService (service-id, jurisdiction, attestation-refs []string). Keeper stub: AddService / GetService / ListByKind. Params, GenesisState (services), DefaultGenesisState, ValidateGenesis (reject dup service-ids, A-212). Tests: HubServiceCount=3 locked-const; enum names; service round-trip; per-service struct fields; LendingCouponCapBps == 800 (cross-doc to D-028); operator-partner-id is a string (no x/partner struct import — G-003 import-invariant green); ValidateGenesis rejects dup service-id; lexicon assertion (HIGH-RISK: "interest"/"yield"/"deposit"/"savings" banned — use "lending primitive"/"coupon"/"custody"/"compliance"; "lending" is NOT banned per RESEARCH §1.5). go build ./x/hub/... succeeds; go test ./x/hub/... passes; ≥80% coverage; HubServiceCount == 3; LendingCouponCapBps == 800; lexicon green; G-003 import-invariant green P4-05-01

Wave 2 — x/services types + tests (parallel with Wave 1; independent)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P5-02-01 REQ-025 backend-engineer x/services/types/types.go + x/services/types/genesis.go + x/services/types/types_test.go (+ genesis_test.go if split) New x/services/types/ module following x/hub (D-040, A-307). ServiceKind enum (Care, SIM, Vault, Mail) — exactly 4, locked-const ServiceKindCount = 4 (A-307); AllServiceKinds() []ServiceKind. ServiceInfo struct (service-id, kind (ServiceKind), operator-reach-id (by-ID-string ref to x/identity Reach — G-003), name, status, window-id (by-ID-string ref to x/window — a service-grant opens a Window on the holder's behalf; the Window Lifecycle interface hook, typed in v0.3, invoked at runtime in v0.4)). ServiceStatus enum (Pending, Active, Suspended, Revoked) — exactly 4 (local redefinition). Per-service struct stubs: CareService (service-id, care-kind (opaque)), SIMService (service-id, carrier (opaque)), VaultService (service-id, storage-quota-grain), MailService (service-id, mailbox-id). Keeper stub: AddService / GetService / ListByKind. Params, GenesisState (services), DefaultGenesisState, ValidateGenesis (reject dup service-ids, A-212). Tests: ServiceKindCount=4 locked-const; enum names (Care, SIM, Vault, Mail); service round-trip; per-service struct fields; window-id is a string (no x/window struct import — G-003 import-invariant green); operator-reach-id is a string (no x/identity import); ValidateGenesis rejects dup service-id; lexicon assertion (no "account" — use service-id/operator-reach-id; "Mail"/"SIM"/"Care"/"Vault" safe). go build ./x/services/... succeeds; go test ./x/services/... passes; ≥80% coverage; ServiceKindCount == 4; lexicon green; G-003 import-invariant green P4-05-01 (P5 has no intra-phase dep; blocked-by P4 ship for branch hygiene + the G-003 import-invariant test scanning the new files)

Wave 3 — x/bond extension (GrowthBond + secondary market) + tests (parallel with Waves 1/2; independent)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P5-03-01 REQ-026 backend-engineer x/bond/types/types.go (EXTEND) + x/bond/types/genesis.go (EXTEND) + x/bond/types/types_test.go (EXTEND) EXTEND x/bond (do NOT create new module — D-041; the 8%/0% consts are locked since v0.2 and UNCHANGED in v0.3). Add GrowthBond struct embedding the v0.2 Bond + a GrowthRateBps field (per-period growth rate of the coupon). Add ClampGrowth(currentBps, growthBps uint32) uint32 helper returning min(CouponCapBps - currentBps, growthBps) so the post-growth coupon is ≤ CouponCapBps (A-306 — reuses the v0.2 CouponCapBps/CouponFloorBps consts, same package, no G-003 concern). Add SecondaryOrder struct (order-id, bond-id (by-ID-string ref to the Bond), side (OrderSide enum), price-bps (fraction of principal in bps), quantity-grain, holder-reach-id, status (OrderStatus enum), created-at). Add OrderSide enum (Buy, Sell) — exactly 2, locked-const OrderSideCount = 2 (A-313). Add OrderStatus enum (Open, Filled, Cancelled) — exactly 3, locked-const OrderStatusCount = 3 (A-313). AllOrderSides() / AllOrderStatuses(). Extend Keeper stub: AddOrder / GetOrder / ListByBond / CancelOrder (no matching — full secondary-market matching deferred to v0.4). Extend GenesisState with GrowthBonds []GrowthBond + Orders []SecondaryOrder; ValidateGenesis checks growth-bond-id + order-id uniqueness (A-212). Tests (extend existing): REGRESSION: CouponCapBps == 800 and CouponFloorBps == 0 STILL (D-028 firewall — v0.3 must not change the v0.2 consts); ClampGrowth invariant (post-growth coupon ≤ 800, never below 0; current + growth where current+growth > cap → growth clamped to cap-current); OrderSideCount == 2; OrderStatusCount == 3; order round-trip; GrowthBond round-trip; bond-id is a string (no struct self-import — same package); existing v0.2 bond tests still green (Clamp, BondStatus, etc. — no regression); lexicon assertion (HIGH-RISK: "interest"/"yield"/"deposit"/"savings" banned — use "coupon"/"growth"/"secondary"/"order"; "growth" safe, "yield growth" banned — use "coupon growth"/"real-return-linked coupon"). go build ./x/bond/... succeeds; go test ./x/bond/... passes; ≥80% coverage; CouponCapBps == 800 + CouponFloorBps == 0 regression green; ClampGrowth invariant green; OrderSideCount == 2 + OrderStatusCount == 3; existing v0.2 bond tests green (no regression); lexicon green P4-05-01

Wave 4 — Phase verification + ship

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P5-04-01 REQ-012, REQ-024, REQ-025, REQ-026 lead-developer (cross-cutting) Run go build ./... + go test ./... green (incl. v0.1/v0.2 baseline + docs firewall + P4 + P5); coverage ≥80% on x/hub/types, x/services/types, x/bond/types; 3 lexicon assertions (hub, services, bond-ext); G-003 import-invariant green (extended to scan x/hub + x/services + x/bond); lexicon_meta_test.go (v0.2) + lexicon_meta_docs_test.go (v0.3) green; D-028 regression: 8%/0% bond consts unchanged; zero external deps (go.mod unchanged); tag v0.2.5. go test ./... green; coverage ≥80% on x/hub/types, x/services/types, x/bond/types; 3 lexicon assertions; G-003 import-invariant green; CouponCapBps == 800 + CouponFloorBps == 0 regression green; zero require lines in go.mod; git tag v0.2.5 P5-01-01, P5-02-01, P5-03-01

P5 Must-Haves

  • x/hub (new), x/services (new), x/bond (extended) each have types/types.go + types/types_test.go.
  • go build ./... and go test ./... green — including v0.1/v0.2 baseline + docs firewall + P4 (no regression).
  • ≥80% coverage on x/hub/types, x/services/types, x/bond/types.
  • Hub locked-const: HubServiceCount == 3 (Custody, LendingPrimitive, Compliance); LendingCouponCapBps == 800 (cross-doc to D-028, local const — no x/bond import).
  • Hub operator-partner-id is a by-ID-string ref to x/partner Anchor (G-003 — no struct import; import-invariant green). This confirms P4→P5 ordering.
  • Services locked-const: ServiceKindCount == 4 (Care, SIM, Vault, Mail).
  • Services window-id is a by-ID-string ref to x/window (G-003); operator-reach-id is a by-ID-string ref to x/identity.
  • Bond: GrowthBond + ClampGrowth (post-growth coupon ≤ 800); OrderSideCount == 2 (Buy/Sell); OrderStatusCount == 3 (Open/Filled/Cancelled).
  • D-028 regression: CouponCapBps == 800 and CouponFloorBps == 0 unchanged (v0.3 must not change v0.2 bond consts).
  • Existing v0.2 bond tests still green (no regression).
  • Lexicon assertion in all 3 P5 test files.
  • lexicon_meta_test.go (v0.2, x/*.go) green; lexicon_meta_docs_test.go (v0.3, docs) green.
  • Zero external deps (go.mod unchanged — G-006).
  • Git tag v0.2.5.

P5 Risks & Mitigations

  • Hub lending-primitive lexicon risk (HIGHEST v0.3 lexicon risk after x/bond) → "interest"/"yield"/"deposit"/"savings" are natural fit-words for a lending primitive; use "lending primitive"/"coupon" (vision §13/§17). "lending" is NOT banned (RESEARCH §1.5); the lexicon assertion is the gate. The local LendingCouponCapBps = 800 const cross-documents D-028 (A-304) to avoid importing x/bond.Clamp (G-003).
  • GrowthBond "yield growth" phrasing → use "coupon growth"/"real-return-linked coupon"; "yield" is banned standalone (word-boundary); TestLexiconMetaNoFalsePositiveOnOpenYield allows "OpenYield" but bans "yield".
  • D-028 const regression → the v0.3 GrowthBond must NOT change CouponCapBps/CouponFloorBps; the regression test (8%/0% unchanged) is the D-028 firewall.
  • ServiceKind "Vault" naming collision with x/vault → the Vault enum value is a service kind (in x/services), not a module import; VaultService references x/vault by ID-string (G-003). No Go import cycle (concept-level collision only).
  • Hub → partner Anchor forward-reference → P4 must ship before P5 (D-044); the P5-04-01 verification confirms operator-partner-id is a string (no x/partner struct import).

Phase P6 — Final Review + Audit + Ship

  • Slug: final-review-audit-ship
  • Branch: oy/phase/06-final-review-audit-ship
  • REQs covered: REQ-012 (lexicon, project-wide meta-test + docs firewall), all v0.3 REQs (REQ-010, REQ-022..REQ-028 audit confirmation)
  • Tag: v0.2.6 (= milestone v0.3 release — final phase patch IS the milestone release per D-008/D-034)
  • Type: final
  • Personas: lead-developer (review/ship), backend-engineer (audit assist), ci-code-reviewer + ci-doc-verifier + ci-debugger (CI personas, activated for P6)
  • Goal: Run the full v0.3 milestone audit — project-wide lexicon meta-test + docs firewall, coverage gate across all 7 new/extended x/* packages, all locked-const invariants green (incl. D-028 regression), all docs pages lexicon-clean, all v0.3 REQs have skeleton+tests or docs — then ship the v0.3 milestone release as tag v0.2.6.

Wave 1 — ci-code-review (multi-persona review across P1-P5)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P6-01-01 REQ-012, all v0.3 lead-developer + ci-code-reviewer (cross-cutting) Code review across P1-P5: confirm go build ./... + go test ./... green; confirm lexicon_meta_test.go (v0.2, x/.go) green AND scans all 22 x/ packages (v0.1 15 + v0.2 10 + v0.3 7 = 32? — recount: v0.1=15, v0.2 added 10 new/extended to 25, v0.3 adds 4 new + 3 extended = 7 → 25 + 4 new = 29 distinct packages; the 3 extended are already counted); confirm lexicon_meta_docs_test.go (v0.3, docs) green AND scans README.md + all docs/**/*.md (26 pages); confirm G-003 import-invariant green across all v0.3 x/* files (no struct imports across x/<module>/types); confirm zero external deps (go.mod: module github.com/oy/openyield\ngo 1.22 with no require lines — G-006). go test ./... green; both firewalls green; G-003 import-invariant green; go.mod zero-dep confirmed; coverage report generated P5-04-01

Wave 2 — ciagent-audit (reconstruction + discipline)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P6-02-01 all v0.3 lead-developer + ci-debugger (cross-cutting) Reconstruction test: confirm every v0.3 REQ maps to a shipped artifact (REQ-010 → x/bridge + x/exit; REQ-022 → x/bearers OYSATLink/OYQRCode; REQ-023 → x/partner AnchorCredential; REQ-024 → x/hub; REQ-025 → x/services; REQ-026 → x/bond GrowthBond/SecondaryOrder; REQ-027 → README + docs/ 26 pages; REQ-028 → lexicon_meta_docs_test.go). File/branch/commit discipline: confirm each phase P1..P5 shipped on its own branch (oy/phase/0N-*) with its patch tag (v0.2.1..v0.2.5); confirm no phase branch merged out of order (D-044: P1 firewall before P2/P3 content; P4 before P5 for Anchor→hub). Locked-const audit: HubServiceCount=3, ServiceKindCount=4, BridgeStatusCount=4, ExitStatusCount=5, OrderSideCount=2, OrderStatusCount=3, LendingCouponCapBps=800, D-028 regression: CouponCapBps=800 + CouponFloorBps=0 unchanged. REQ→artifact map complete (each REQ has a file); branch/tag discipline confirmed (5 phase branches + 5 patch tags); all locked-consts green; D-028 regression green P6-01-01

Wave 3 — ciagent-ship (merge + tag + release + cleanup)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P6-03-01 (milestone) lead-developer + ci-doc-verifier .ciagent/oy/REQUIREMENTS.md + .ciagent/oy/ROADMAP.md Update REQUIREMENTS.md: mark REQ-010, REQ-022, REQ-023, REQ-024, REQ-025, REQ-026 → Skeleton; REQ-027 → Complete; REQ-028 → Complete. Update ROADMAP.md: mark v0.3 milestone COMPLETE (all 7 phases P0..P6 checked); add the tag-line note that v0.3 shipped on the v0.2.x patch line (P0 → v0.2.0, P1..P5 → v0.2.1..v0.2.5, P6 → v0.2.6 = milestone release, per D-008/D-034). REQUIREMENTS.md status column updated for all 8 v0.3 REQs; ROADMAP.md v0.3 marked complete + tag-line note present P6-02-01
P6-03-02 (milestone) lead-developer (cross-cutting) Final ship: merge phase/06 → milestone/v0.3 → main; create milestone release tag v0.2.6 (= v0.3 milestone release per D-008/D-034); delete the 5 phase branches (oy/phase/01-*..oy/phase/05-*) after merge (P6 branch retained until post-release cleanup); confirm go build ./... + go test ./... green at the v0.2.6 tag. v0.2.6 tag created on main; go test ./... green at the tag; ROADMAP.md v0.3 complete; phase branches deleted (except P6); release notes reference v0.3 scope (Bearers skeleton I+II + docs site + firewall extension) P6-03-01

P6 Must-Haves

  • Project-wide lexicon meta-test (lexicon_meta_test.go, x/.go) scans all 29 x/ packages (v0.1 15 + v0.2 10 + v0.3 4 new; 3 extended already counted); green.
  • Docs firewall (lexicon_meta_docs_test.go) scans README.md + all 26 docs/**/*.md pages; green.
  • Coverage ≥80% on all 7 new/extended v0.3 x/* packages (bridge, exit, bearers-ext, partner-ext, hub, services, bond-ext).
  • All locked-const invariants green: HubServiceCount=3, ServiceKindCount=4, BridgeStatusCount=4, ExitStatusCount=5, OrderSideCount=2, OrderStatusCount=3, LendingCouponCapBps=800, OYSATLink surveillance-resistant=true, OYQRCode one-shot consumed.
  • D-028 regression: CouponCapBps == 800, CouponFloorBps == 0 unchanged from v0.2.
  • All v0.1/v0.2 baseline tests still green (no regression across 29 x/* packages + docs).
  • G-003 import-invariant green (no struct imports across x/<module>/types in any v0.3 production file).
  • Zero external deps (go.mod unchanged — G-006).
  • REQUIREMENTS.md status column updated (REQ-010/022..026 → Skeleton; REQ-027/028 → Complete).
  • ROADMAP.md v0.3 marked COMPLETE + tag-line note (v0.2.x patch line).
  • go build ./... and go test ./... green at the v0.2.6 tag.
  • Git tag v0.2.6 created (= v0.3 milestone release).
  • Phase branches P1..P5 deleted post-merge (P6 retained until post-release cleanup).

P6 Risks & Mitigations

  • Lexicon drift via copy-pasted comments in v0.3 x/ modules* → the v0.2 meta-test scans comments + strings + identifiers; v0.3 modules are automatically covered (no new meta-test work, just the existing scan).
  • D-028 const regression at the last mile → P5-03-01 + P6-02-01 both assert 8%/0% unchanged; double firewall.
  • Milestone versioning confusion (v0.3 milestone = v0.2.6 tag) → lead-developer enforces D-008/D-034: final phase patch IS the milestone release; no separate minor tag. ROADMAP tag-line note (P6-03-01) prevents v0.2.6/v0.3.0 confusion.

Coverage Targets (D-033) — v0.3

Package Phase Target Locked-const tests
x/bridge/types P4 ≥80% BridgeStatusCount=4 (Pending, Attested, Active, Closed)
x/exit/types P4 ≥80% ExitStatusCount=5 (Proposed, InProgress, Settled, Failed, Refunded)
x/bearers/types (ext) P4 ≥80% OYSATLink surveillance-resistant=true (A-311); OYQRCode one-shot consumed; AllBearers()=6 unchanged (regression)
x/partner/types (ext) P4 ≥80% AllPartnerTiers()=4 unchanged (regression); AnchorCredential accessor nil-for-non-Anchor
x/hub/types P5 ≥80% HubServiceCount=3; LendingCouponCapBps=800 (cross-doc D-028/A-304)
x/services/types P5 ≥80% ServiceKindCount=4 (Care, SIM, Vault, Mail)
x/bond/types (ext) P5 ≥80% CouponCapBps=800 + CouponFloorBps=0 unchanged (D-028 regression); ClampGrowth post-growth ≤ 800; OrderSideCount=2; OrderStatusCount=3

Lexicon assertions (REQ-012): present in all 7 new/extended v0.3 test files (per-package) + project-wide lexicon_meta_test.go (v0.2, x/.go — automatically covers v0.3 x/ files) + lexicon_meta_docs_test.go (v0.3, README.md + docs/**/*.md).


Task Count Summary — v0.3

Phase Waves Tasks New/Extended Packages / Docs
P1 3 11 lexicon_meta_docs_test.go + mkdocs.yml + README.md + docs/index.md + 6 docs/shared/ pages
P2 2 9 8 docs/nomads/ pages
P3 3 12 8 docs/freeholders/ pages + 2 docs/reference/ pages
P4 6 9 x/bridge (new), x/exit (new), x/bearers (ext), x/partner (ext)
P5 4 5 x/hub (new), x/services (new), x/bond (ext)
P6 3 4 (audit/ship, 0 new — audit + REQUIREMENTS/ROADMAP update + tag)
Total 50 4 new x/ + 3 extended x/ + 26 docs pages + 1 firewall test**

Per-Phase REQ Coverage — v0.3

Phase REQs Components / Docs
P1 REQ-028, REQ-027 Docs firewall (lexicon_meta_docs_test.go) + mkdocs.yml + README + docs/index + docs/shared (6 pages)
P2 REQ-027 docs/nomads (8 pages: Reach, Stash, bearers, Maps/Pay, Pacts, standing-basics, Window, index)
P3 REQ-027 docs/freeholders (8 pages) + docs/reference (2 pages) — REQ-027 COMPLETE
P4 REQ-010, REQ-022, REQ-023 x/bridge + x/exit (Exit layer), x/bearers OY-SAT/OY-QR, x/partner Anchor
P5 REQ-024, REQ-025, REQ-026 x/hub, x/services, x/bond GrowthBond + secondary
P6 REQ-012 + all v0.3 REQs (audit) Both firewalls, coverage gate, D-028 regression, milestone ship

Cross-Phase Blockers (hard) — v0.3

  • P1-01-01 (docs firewall) → blocks P1-02-* (README + shared content scanned by the firewall) and P2/P3 (audience content scanned).
  • P4-01-01 (x/bridge types) → blocks P4-02-01 (x/exit — bridge-route-id refs BridgeRoute by ID-string, A-308/G-003).
  • P4-04-01 (x/partner Anchor extension) → blocks P5-01-01 (x/hub — operator-partner-id refs Anchor by ID-string, A-304/G-003). This is the P4→P5 edge (D-044).
  • P5-04-01 (P5 ship) → blocks P6-01-01 (P6 audit).
  • All P(N) phase-ship tasks block P(N+1) Wave 1 tasks (soft ordering for branch hygiene; types themselves only depend on the listed hard blockers).

v0.3 Decisions Applied (D-034..D-046 + A-301..A-315)

The v0.3 Phase 0 clarify/ideate/research stages produced 13 clarification decisions (D-034..D-046) and 15 research assumptions (A-301..A-315), all applied to this plan:

ID Decision / Assumption Applied to
D-034 v0.3 bundles Bearers skeleton + docs under one feature milestone; tags on v0.2.x Milestone Summary, all phases
D-035 Bearers skeleton continues D-020 pattern (no live chain) P4, P5
D-036 REQ-010 = x/exit + x/bridge (two packages) P4 Waves 1-3
D-037 REQ-022 = x/bearers OYSATLink + OYQRCode P4 Wave 4
D-038 REQ-023 = x/partner AnchorCredential extension (no new tier) P4 Wave 4
D-039 REQ-024 = x/hub new module (Pact #6 promoted) P5 Wave 1
D-040 REQ-025 = x/services new module (4 kinds) P5 Wave 2
D-041 REQ-026 = x/bond GrowthBond + secondary (8%/0% unchanged) P5 Wave 3
D-042 MkDocs Material; audience-organized docs P1-01-02, P2, P3
D-043 Docs firewall = sibling test lexicon_meta_docs_test.go (not modifying v0.2 meta-test) P1-01-01
D-044 Phase ordering: firewall-first (P1) before content (P2/P3); P4 before P5 (Anchor→hub) Cross-Phase Dependency Map, all phase goals
D-045 Docs depth: ~20-25 pages across 4 audiences P1/P2/P3 page counts
D-046 No docs publishing CI in v0.3 P1-01-02, P1-02-01
A-304 x/hub LendingCouponCapBps=800 local const (no x/bond import) P5-01-01
A-308 x/exit venue is opaque string (not locked enum) P4-02-01
A-311 OY-SAT surveillance-resistant LOCKED true; OY-QR one-shot P4-03-01
A-312 HubServiceCount=3 P5-01-01
A-313 OrderSideCount=2, OrderStatusCount=3 P5-03-01
A-315 v0.2 cosmos-engineer/security-engineer NOT reactivated Persona assignments (backend-engineer owns all P4/P5)

Milestone v0.4 — Refinement (NFR) — Phase Plan

Milestone Summary

  • Milestone: v0.4 — Refinement (NFR)
  • Type: NFR (zero feat: phases by construction; D-047). Final-phase audit enforces the NFR purity gate (git log --grep "^feat:" on the milestone range returns zero).
  • Tag base: v0.3.x patch line. P0 ships as v0.3.0; execution phases v0.3.1..v0.3.3; final phase P4 patch v0.3.4 IS the milestone release. No separate minor tag (D-008).
  • Phases: 4 execution + 1 final = 5. Phase 0 (this PLAN) is pre-execution.
  • Depth: refinement-only — refactor + test + docs + chore. No new production types, no new enum types, no behavioral changes. All work passes the D-001 refinement-only filter.
  • Coverage target: ≥80% on any modified package; do NOT reduce existing coverage (v0.3 floor is 93.3%).
  • New modules: ZERO. Touched files: lexicon/lexicon.go (add helper), lexicon_meta_test.go + lexicon_meta_docs/lexicon_meta_docs_test.go (refactor to consume helper), x/hub/types/cross_const_test.go (NEW test file), x/council/types/types_test.go (add intent test), .ciagent/oy/ARCHITECTURE.md (docs), .gitea/workflows/docs-build.yml (NEW CI).
  • Phase ordering (D-052): P1 lexicon+const hardening → P2 lifecycle divergence docs+guard → P3 docs build CI → P4 final review/audit/ship. P1 is firewall-first (highest-severity regression risk); each phase independently shippable (vertical slices).
  • Personas: backend-engineer (P1, P2 code), lead-developer (P2 docs, P3 CI, P4 review/ship). v0.3 frontend-engineer + docs-writer deactivated.

Cross-Phase Dependency Map

P1 (lexicon helper, cross-const test) ──► P4 (review)
P2 (lifecycle docs + regression guard)  ─► P4 (review)
P3 (docs build CI)                       ─► P4 (review)

No hard cross-phase blockers (all three execution phases are independent vertical slices). P4 depends on P1+P2+P3 completion only.

D-001 Refinement-Only Filter (governs every v0.4 task)

  • Accept: refactor, test, docs, chore, quality, architecture (drift fix only).
  • Reject: add_requirement + feat:-class signal (new enum type, new production type, new capability, new CLI, new distribution channel).
  • Pre-seeded NFR REQs (REQ-029..REQ-032) are exempt — already in REQUIREMENTS.md before PLAN.

Phase P1 — Lexicon + Const Hardening

  • Slug: lexicon-const-hardening
  • Branch: oy/phase/01-lexicon-const-hardening
  • REQs covered: REQ-029 (lexicon shared helper), REQ-030 (cross-const test)
  • Tag: v0.3.1
  • Goal: Close the G-014 lexicon-firewall drift risk (shared synthetic-string helper) and the A-304 hub↔bond const drift risk (cross-package equality test). Both are refactor+test; no behavioral change.

Wave 1 — Shared helper + cross-const test (parallel; no internal deps)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P1-01-01 REQ-029 backend-engineer lexicon/lexicon.go Add SyntheticBannedStrings() []string returning the 10 synthetic strings currently duplicated in both meta-tests. Each string embeds exactly one banned term (from BannedTerms()) in a plausible sentence context. Source uses BannedTerms() so the lexicon package's own source stays lexicon-clean (fragment-assembled). Add a doc comment cross-referencing G-014 and both consuming meta-tests. go build ./lexicon/... succeeds; SyntheticBannedStrings() returns exactly len(BannedTerms()) strings; each returned string triggers FindBannedTerm (self-verifiable in a new test); lexicon package source stays lexicon-clean (the existing lexicon_meta_test.go x/ scan does not scan lexicon/ itself, but the helper must not introduce a banned-term literal).
P1-01-02 REQ-029 backend-engineer lexicon_meta_test.go, lexicon_meta_docs/lexicon_meta_docs_test.go Refactor TestLexiconMetaSelfTestTable and TestLexiconMetaDocsSelfTestTable to consume lexicon.SyntheticBannedStrings() instead of building their own synthetic slice. Remove the duplicated 10-string table from both. Keep the per-string FindBannedTerm assertion loop (detection coverage unchanged). Keep the len(terms) == 10 assertion from BannedTerms(). Add a len(synthetic) == len(terms) assertion derived from the helper. go test ./lexicon_meta... ./lexicon_meta_docs/... passes; both tests still fail if detection breaks; the duplicated table is gone (grep for the old literal sentence fragments returns zero matches in meta-test files); coverage on both meta-tests does not decrease. P1-01-01
P1-02-01 REQ-030 backend-engineer x/hub/types/cross_const_test.go (NEW) New test file, package types (same package as x/hub/types). Test-only import github.com/oy/openyield/x/bond/types (G-003 test-exempt). Tests: (1) TestLendingCouponCapMatchesBondCap asserts hub.LendingCouponCapBps == bond.CouponCapBps; (2) TestLendingCouponFloorMatchesBondFloor asserts hub.LendingCouponFloorBps == bond.CouponFloorBps; (3) TestConstsAreMissionLocked800And0 asserts both caps == 800 and both floors == 0 (regression guard for the mission-locked values). Doc comment cross-references A-304 and the G-003 test-exemption. go test ./x/hub/types/... passes; all three tests green at current values (800/0); test fails closed if either const drifts; NO production file in x/hub/types/ imports x/bond/types (G-003 production firewall intact — verified by a grep of non-test .go files).

Wave 2 — Verification (blocked-by Wave 1)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P1-99-01 REQ-029, REQ-030 lead-developer — (verification only) Run go test ./... (all packages, both lexicon firewalls + cross-const test + all v0.3 baseline tests); run grep -rn "open a .* here|make a .* now|compounding .* rate" lexicon_meta_test.go lexicon_meta_docs/ and confirm zero matches (old duplicated table fully removed); confirm go.mod unchanged (zero deps, G-006). go test ./... exits 0; grep returns zero; go.mod diff is empty. P1-01-02, P1-02-01

P1 Must-Haves (verification gate before SHIP)

  • lexicon.SyntheticBannedStrings() exists and returns 10 strings.
  • Both meta-tests consume the helper (no duplicated table).
  • x/hub/types/cross_const_test.go exists and passes.
  • go test ./... green (all v0.3 baseline + v0.4 new tests).
  • go.mod unchanged.
  • No feat: commits in P1 (D-001 filter).

Phase P2 — Lifecycle Divergence Documentation + Regression Guard

  • Slug: lifecycle-divergence-docs
  • Branch: oy/phase/02-lifecycle-divergence-docs
  • REQs covered: REQ-031
  • Tag: v0.3.2
  • Goal: Document the AUDIT §193 council lifecycle type divergences (P1-1 Proposal/VoteOption absent; P1-2 SignalKind 4-vs-5) in ARCHITECTURE.md and add an intent-assertion test guarding the current SignalKindCount==4 shape. No enum additions, no locked-const changes (D-050).

Wave 1 — Docs + regression guard (parallel; no internal deps)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P2-01-01 REQ-031 lead-developer .ciagent/oy/ARCHITECTURE.md Add a "Council Voice/Council Interface — Lifecycle Type Divergence Decisions (v0.4)" subsection under the v0.4 Architecture section. Document: (a) P1-1 Proposal/ProposalStatus/VoteOption absent — deferred to v0.5+ governance runtime (feat:-class, rejected by D-001); (b) P1-2 SignalKind 4 sources (Stash/Standing/Vouch/Capital) vs spec VoiceSource 5 sources — the AUDIT rationale (Freeholder is eligibility, Guild is council tier, Capital is committed-capital per vision §9.1) and the decision to KEEP the 4-source shape; (c) the Bearers ValidateGenesis no-op is correct per spec (P2, no action). Cross-reference AUDIT §193 P1-1/P1-2/P2. The ARCHITECTURE.md section exists and names both P1 divergences + the P2 no-action; a reader can find why Proposal/VoteOption are absent and why SignalKind is 4-not-5 without re-reading AUDIT.
P2-02-01 REQ-031 backend-engineer x/council/types/types_test.go Add TestSignalKindShapeIntentional — an intent-assertion test that documents and guards the 4-source SignalKind shape. Asserts: SignalKindCount == 4; AllSignalKinds() returns exactly [Stash, Standing, Vouch, Capital]; a doc-comment block above the test explaining the AUDIT §193 P1-2 rationale (why Freeholder and Guild are NOT signal kinds, why Capital IS) so a future agent does not "fix" the divergence by silently changing the locked const. This is a regression GUARD, not a shape change — the existing TestSignalKindCountLockedConst already locks the count; this test adds the INTENT documentation. go test ./x/council/types/... passes; the new test fails if SignalKindCount changes from 4; the test's doc comment explains the rationale.

Wave 2 — Verification (blocked-by Wave 1)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P2-99-01 REQ-031 lead-developer — (verification only) Run go test ./...; confirm ARCHITECTURE.md subsection present; confirm no production .go file was modified in P2 (only a _test.go file + a .ciagent/ doc). go test ./... exits 0; git diff --name-only <p2-base> HEAD -- 'x/**/*.go' returns only x/council/types/types_test.go (no production files). P2-01-01, P2-02-01

P2 Must-Haves (verification gate before SHIP)

  • ARCHITECTURE.md has the Council lifecycle divergence subsection.
  • TestSignalKindShapeIntentional exists and passes.
  • SignalKindCount unchanged (still 4).
  • No production .go files modified in P2 (test + docs only).
  • No feat: commits in P2 (D-001 filter).

Phase P3 — Docs Build CI

  • Slug: docs-build-ci
  • Branch: oy/phase/03-docs-build-ci
  • REQs covered: REQ-032
  • Tag: v0.3.3
  • Goal: Ship a Gitea Actions workflow that runs the lexicon firewall (go test ./...) + builds the docs site (mkdocs build) on every push, uploading site/ as a CI artifact. Full Gitea Pages publishing deferred (no hosting target configured, D-051). The workflow file is chore (CI config), not feat:.

Wave 1 — CI workflow (single task; no internal deps)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P3-01-01 REQ-032 lead-developer .gitea/workflows/docs-build.yml (NEW) Gitea Actions workflow (YAML). Triggers: on push (all branches). Jobs: (1) go-test — setup Go 1.22, go test ./... (runs lexicon firewall + all x/* tests); (2) docs-build — setup Python, pip install mkdocs mkdocs-material, mkdocs build (produces site/), upload site/ as an artifact. G-016 binding: the docs-build job MUST depend on go-test (serial: needs: go-test), so a lexicon-firewall failure blocks the docs build (no false-green docs build from a repo with a lexicon violation). go.mod is NOT modified (Python deps are isolated to the docs-build job). Doc comment in the YAML references D-046 (forward-reference), D-051 (no Pages publish), and G-016 (firewall-gates-docs-build). YAML parses; go test ./... command matches the local green baseline; mkdocs build command matches the README's local build invocation; docs-build job has needs: go-test; no go.mod change.

Wave 2 — Verification (blocked-by Wave 1)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P3-99-01 REQ-032 lead-developer — (verification only) Validate the workflow YAML parses; confirm go test ./... still green locally (the workflow does not change Go source); confirm mkdocs build succeeds locally (mkdocs installed); confirm go.mod unchanged. YAML parse OK; go test ./... exits 0; mkdocs build produces site/; go.mod diff empty. P3-01-01

P3 Must-Haves (verification gate before SHIP)

  • .gitea/workflows/docs-build.yml exists and parses.
  • Workflow runs go test ./... and mkdocs build.
  • go.mod unchanged.
  • No feat: commits in P3 (D-001 filter; the workflow is chore).

Phase P4 — Final Review + Audit + Milestone Ship

  • Slug: final-review-ship
  • Branch: oy/phase/04-final-review-ship
  • REQs covered: all v0.4 REQs (REQ-029..REQ-032) — final coverage accounting
  • Tag: v0.3.4 (IS the v0.4 milestone release; D-008)
  • Goal: Multi-persona review across P1..P3, audit (reconstruction test + NFR purity gate), milestone ship (merge to main, tag v0.3.4, release, delete all milestone branches).

Wave 1 — Review + Audit (parallel; no internal deps)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P4-01-01 lead-developer (review) .ciagent/oy/REVIEW.md (NEW for v0.4) Multi-persona code review across P1..P3. Adversarial probes: (1) does the shared helper actually dedupe (grep for old table); (2) does the cross-const test fail closed on drift; (3) does the regression guard lock the 4-signal shape; (4) does the CI workflow YAML parse. Auto-apply P0 fixes; flag P1+ for post-hoc. REVIEW.md written; P0 issues (if any) fixed in P4; P1+ flagged. P1, P2, P3
P4-02-01 ci-security-auditor (audit) .ciagent/oy/AUDIT.md (v0.4 section) Audit: (1) reconstruction test (git log ↔ .ciagent/ files for v0.4); (2) file/branch/commit discipline; (3) NFR purity gategit log --grep "^feat:" $(git rev-list --tags=v0.3.0..v0.3.4) returns zero (or the equivalent milestone-range grep); (4) coverage did not decrease on any modified package. AUDIT.md v0.4 section written; NFR purity gate GREEN (zero feat: commits); reconstruction test passes. P1, P2, P3

Wave 2 — Ship (blocked-by Wave 1)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P4-03-01 REQ-029..REQ-032 lead-developer (ship) .ciagent/oy/REQUIREMENTS.md, .ciagent/oy/ROADMAP.md, .ciagent/CHECKPOINT.json Milestone ship via ciagent ship 4 (or ShipWorkflow fallback). Merge oy/phase/04oy/milestone/v0.4-refinementmain. Tag v0.3.4. Create release (best-effort via resolveSecret(GITEA_TOKEN)). Delete all milestone branches (local + remote). Update REQUIREMENTS.md (v0.4 REQs → Complete) + ROADMAP.md (v0.4 → COMPLETE). Commit docs(milestone): complete v0.4. Clear CHECKPOINT. Tag v0.3.4 created; milestone branch merged to main; release created (or local-only fallback); all oy/phase/* and oy/milestone/v0.4-refinement branches deleted; REQUIREMENTS.md + ROADMAP.md updated; checkpoint cleared. P4-01-01, P4-02-01

P4 Must-Haves (verification gate before milestone complete)

  • REVIEW.md v0.4 section written; P0 fixes applied.
  • AUDIT.md v0.4 section written; reconstruction test passes.
  • NFR purity gate GREEN: zero feat: commits in the v0.4 milestone range.
  • Tag v0.3.4 created (= milestone release).
  • Milestone branch merged to main.
  • All milestone branches deleted (local + remote).
  • REQUIREMENTS.md + ROADMAP.md mark v0.4 COMPLETE.
  • CHECKPOINT.json cleared (milestone complete).

Coverage Targets (D-033) — v0.4

v0.4 does not lower the v0.3 coverage floor (93.3% on the lowest package). The modified packages must not decrease:

  • lexicon (if coverage applies — it's a helper package): maintain or improve.
  • lexicon_meta / lexicon_meta_docs: maintain 100% (test-only packages).
  • x/hub/types: the new cross_const_test.go ADDS coverage; do not decrease.
  • x/council/types: the new intent test ADDS coverage; do not decrease.

No new packages are created, so no new 80% floor is set.

Task Count Summary — v0.4

Phase Tasks Personas Tag
P0 (pre-execution, this plan) lead-developer v0.3.0
P1 4 (3 code + 1 verify) backend-engineer, lead-developer v0.3.1
P2 3 (2 deliver + 1 verify) backend-engineer, lead-developer v0.3.2
P3 2 (1 deliver + 1 verify) lead-developer v0.3.3
P4 3 (review + audit + ship) lead-developer, ci-security-auditor v0.3.4
Total 12 tasks

Per-Phase REQ Coverage — v0.4

Phase REQs covered Status after phase
P1 REQ-029, REQ-030 Complete (shipped as v0.3.1)
P2 REQ-031 Complete (shipped as v0.3.2)
P3 REQ-032 Complete (shipped as v0.3.3)
P4 (all v0.4 REQs final accounting) Complete (shipped as v0.3.4)

Cross-Phase Blockers (hard) — v0.4

None. P1, P2, P3 are independent vertical slices. P4 depends on P1+P2+P3 completion only (soft ordering for shippable slices).

v0.4 Decisions Applied (D-047..D-053)

Decision Application
D-047 v0.4 NFR type, tags v0.3.x, zero feat: phases
D-048 REQ-029 lexicon shared helper
D-049 REQ-030 cross-const test (G-003 test-exempt import)
D-050 REQ-031 = document only, no feat: enum additions
D-051 REQ-032 docs CI = .gitea/workflows build+artifact, no Pages
D-052 Phase ordering P1→P2→P3→P4
D-053 No IDEATE in v0.4 (no --ideate flag)

MVP/UX Check (REQ-MVP-UX-001) — v0.4 NFR Milestone

Auto-generated at full autonomy per run.md §MVP/UX CHECK. The three required sections below name at least one user-facing surface, a happy path written BEFORE execute, and explicit UX acceptance criteria. v0.4 is an NFR milestone, so "user-facing surface" is developer-facing (the lexicon firewall, the cross-const test, the CI workflow) and the docs site build (end-user-facing via the published docs artifact).

User-Facing Surface

  1. Lexicon firewall (developer-facing): go test ./lexicon_meta... ./lexicon_meta_docs/... — the green test output is the surface a developer sees on every go test ./... run. After REQ-029, both meta-tests consume lexicon.SyntheticBannedStrings(); a future banned-term addition updates both firewalls from one place.
  2. Cross-const drift test (developer-facing): go test ./x/hub/types/...TestConstsAreMissionLocked800And0 is the surface a developer sees if the hub↔bond mission-locked consts ever drift.
  3. Docs build CI (end-user-facing via artifact): .gitea/workflows/docs-build.yml produces a site/ artifact on every push. A maintainer downloads the artifact to preview the docs site without running mkdocs build locally.
  4. ARCHITECTURE.md divergence section (developer-facing): a contributor reading .ciagent/oy/ARCHITECTURE.md finds the "Council Voice/Council Interface — Lifecycle Type Divergence Decisions (v0.4)" subsection explaining why SignalKind is 4-not-5 and why Proposal/VoteOption are absent.

Happy Path

Scenario: a contributor adds an 11th banned term to the lexicon firewall.

  1. The contributor edits lexicon/lexicon.go to add a new fragment pair to fragments (e.g., a new banned term).
  2. lexicon.BannedTerms() now returns 11 strings.
  3. lexicon.SyntheticBannedStrings() (REQ-029) is the single source — the contributor does NOT need to update two meta-test tables (the v0.3 drift risk).
  4. go test ./lexicon_meta... ./lexicon_meta_docs/... — both meta-tests consume the helper; the len(terms) == 10 assertion in BOTH meta-tests now fails (expecting 11), alerting the contributor to update the count assertion in both files.
  5. The contributor updates the len(terms) == 11 assertion in both meta-tests (the G-014 minimum — both already derive count from BannedTerms()).
  6. Both meta-tests pass; the new banned term is now enforced in BOTH the x/**/*.go firewall AND the docs/**/*.md firewall from one source change.
  7. The contributor pushes; .gitea/workflows/docs-build.yml (REQ-032) runs go-test (green) → docs-build (green, G-016 gating) → site/ artifact uploaded.

Scenario: a contributor accidentally changes x/bond.CouponCapBps from 800 to 900 without updating x/hub.LendingCouponCapBps.

  1. go test ./x/hub/types/...TestLendingCouponCapMatchesBondCap (REQ-030) fails: hub.LendingCouponCapBps (800) != bond.CouponCapBps (900).
  2. TestConstsAreMissionLocked800And0 (G-015) ALSO fails: bond.CouponCapBps (900) != 800.
  3. The contributor sees two failures pointing at the same root cause; the mission-locked 8% cap is defended by the cross-const test. The v0.3 A-304 drift risk is closed.

UX Acceptance Criteria

The v0.4 deliverable MUST meet these explicit criteria (verified in P4 audit):

  1. REQ-029: lexicon.SyntheticBannedStrings() exists; both lexicon_meta_test.go and lexicon_meta_docs/lexicon_meta_docs_test.go consume it; the duplicated 10-string table is gone (grep for the old literal sentence fragments returns zero matches in meta-test files).
  2. REQ-030: x/hub/types/cross_const_test.go exists; TestLendingCouponCapMatchesBondCap, TestLendingCouponFloorMatchesBondFloor, and TestConstsAreMissionLocked800And0 all pass (G-015); no production .go file in x/hub/types/ imports x/bond/types (G-003 production firewall intact).
  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.

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/<module>/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/<denom>; 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/<denom>. 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/<denom> 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/<module>/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/<module>/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/08milestone/v0.5-bearers-runtimemain; 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/<module>/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.

v0.6 Plan (Nomad Web UI)

This section APPENDS the v0.6 milestone plan to the v0.1..v0.5 plans above. It does NOT rewrite or supersede the earlier content. v0.6 is the project's first UI milestone: a working prototype Web UI where a person can sign up to be a Nomad (create a Reach + open a Stash) and exercise basic functionality around (Reach, Stash) plus Window authorization, Standing progress, and Bloom accrual. All data is generated test fixtures — no real blockchain (D-020 continues). Stack: Go html/template + HTMX (vendored, no node) + Go 1.22 net/http.ServeMux mock HTTP server in a new web/ dir that instantiates the real x/*/types structs from in-memory fixtures (D-067). No keeper, no Cosmos runtime, no app.go (none exists in the repo). Tags run on the v0.5.x patch line (config.json tag_base: v0.5.x): P0 → v0.5.0; execution phases P1..P5 → v0.5.1..v0.5.5; final phase P6 → v0.5.6 IS the v0.6 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, matching the v0.5 convention).

Milestone Summary

  • Milestone: v0.6 — Nomad Web UI
  • Type: Feature (P1..P5 are feat; REQ-045 is test co-shipped in P1; P6 is final)
  • Tag base: v0.5.x patch line (P0 → v0.5.0; execution P1..P5 → v0.5.1..v0.5.5; final P6 → v0.5.6 IS the v0.6 milestone release)
  • Phases: 7 — P1..P5 (execution) + P6 (final review/audit/ship). Phase 0 (this PLAN) is in progress.
  • Depth: UI prototype — Go html/template server-rendered HTML + HTMX progressive enhancement; in-memory mock store seeded from fixtures; instantiates real x/*/types structs (Reach, Stash, Window, FreeholderSignals, BloomRecord). No keeper, no Cosmos runtime, no app.go, no persistence (resets on restart).
  • Coverage target: ≥80% on each new web/ package (D-033 carries forward); the mock store + handlers have Go tests (go test ./web/...), not just manual browser checks. Lexicon firewall (REQ-012) extended to web/ via a new sibling meta-test (REQ-045).
  • New dirs: web/ (top-level; main.go, handlers/, store/, templates/, static/); lexicon_meta_web/ (sibling firewall test dir, mirrors lexicon_meta_docs/). Extended: none (x/ is NOT modified — web/ imports x/*/types as app-layer consumption per D-070, not a production cross-x/ import).
  • G-006 (zero Go deps): preserved. HTMX is a vendored static asset (web/static/htmx.min.js), NOT a go get. Go 1.22 net/http.ServeMux is stdlib. html/template is stdlib. go.mod is unchanged (no new require lines; the v0.5 cosmos-sdk/ibc-go deps stay but v0.6 adds nothing).
  • G-003 (production firewall): intact. web/ importing x/*/types is app-layer consumption (D-070), NOT a cross-x/ production import. No x/ module gains a production import of another x/ module's types via web/. The v0.2 G-003 import-invariant test scans x/**/*.go (unchanged scope); web/ is outside x/ so it is not scanned by that test — a NEW web/-scoped test confirms web/ only imports x/*/types (not x/*/keeper).
  • Phase ordering (D-072, finalized here): P1 web foundation + Reach signup + lexicon firewall (REQ-040 + REQ-045 — same web/ territory, vertical slice, firewall-first) → P2 Stash dashboard (REQ-041) → P3 Window authorization (REQ-042) → P4 Standing + Freeholder signals (REQ-043) → P5 Bloom accrual (REQ-044) → P6 final review/audit/ship. Each phase independently shippable (vertical slice); P1 lands the foundation + firewall first (lexicon-clean by construction — D-044 pattern).
  • Personas (from PERSONAS.md, reactivated for v0.6): frontend-engineer owns web/** (templates, static, handlers, main.go) — REACTIVATED for the first time (territory web/**); backend-engineer co-owns web/store/ (the mock store that imports x/*/types) + owns the lexicon firewall extension (REQ-045, lexicon_meta_web/ — mirrors lexicon_meta_docs/); lead-developer owns P0 + P6 + coordination. The v0.5 cosmos-engineer/security-engineer/mesh-engineer personas are NOT reactivated (no keeper/MsgServer/simtest work in v0.6 — UI mock only). ci-security-auditor activated in P6.

Cross-Phase Dependency Map (v0.6)

P1 (web foundation + Reach signup + lexicon firewall)
   │  web/main.go + web/store/ + web/handlers/reach.go + web/templates/reach.html
   │  lexicon_meta_web/lexicon_meta_web_test.go  [firewall-first; scans web/ as content lands]
   ▼
P2 (Stash dashboard)            [depends on Reach existing (signup creates Stash atomically, D-071)]
   ▼
P3 (Window authorization)      [depends on Stash existing (Window scope references a Stash)]
   ▼
P4 (Standing + Freeholder signals)  [depends on Reach existing (Standing is per-Reach)]
   ▼
P5 (Bloom accrual)              [depends on Stash existing (BloomRecord is per-Stash)]
   ▼
P6 (final review/audit/ship)

Hard cross-phase blockers:

  • P1 lexicon firewall (lexicon_meta_web/) → blocks P2..P5 content (firewall-first: a banned term slipped into a P2..P5 template/JS/Go file fails the build, not the P6 review — D-044/D-069 pattern).
  • P1 web foundation (web/main.go + web/store/ + base templates) → blocks P2..P5 (each screen extends the base layout + uses the mock store).
  • P1 Reach signup (REQ-040) → blocks P2 (Stash dashboard needs a Stash, created atomically at signup per D-071), P4 (Standing is per-Reach).
  • P2 Stash dashboard (REQ-041) → blocks P3 (Window scope references a Stash), P5 (BloomRecord is per-Stash).
  • All P(N) phase-ship tasks block P(N+1) Wave 1 tasks (soft ordering for branch hygiene).

All other refs are app-layer consumption of x/*/types (D-070): web/store/ imports x/identity/types, x/stash/types, x/window/types, x/standing/types, x/bread/types, x/bloom/types. No x/ module is modified.

D-070 / G-003 Boundary (app-layer consumption, NOT a cross-x/ import)

web/ is NOT an x/ module — it is the application layer (exactly as a future cmd/oyd would be). web/store/ importing x/identity/types.Reach is app-layer consumption of protocol types, the intended consumption direction. The G-003 firewall (no production struct imports across x/<module>/types) is intact: no x/ module gains a production import of another x/ module's types via web/. A new web/-scoped test (web/store/import_test.go) asserts web/ only imports x/*/types packages (NOT x/*/keeper — there is no keeper in v0.6; the mock store IS the data source).


Phase P1 — Web Foundation + Reach Signup + Lexicon Firewall (REQ-040, REQ-045) → v0.5.1

  • Slug: web-foundation-reach-firewall
  • Branch: phase/01-web-foundation-reach-firewall
  • REQs covered: REQ-040 (Nomad Reach signup Web UI), REQ-045 (lexicon firewall extension to web/)
  • Tag: v0.5.1
  • Type: feat+test
  • Goal: Ship the web foundation (Go 1.22 net/http.ServeMux mock server in web/, base templates, vendored HTMX, in-memory mock store importing x/*/types) + the "Create a Reach" signup form (POST) that atomically creates a Reach (IsNomad=true) + a Stash (D-071) + Reach list/detail views + the lexicon_meta_web/ firewall extension (REQ-045, firewall-first). After P1, the UI is runnable via go run ./web and a visitor can create a Reach.

Wave 1 — Lexicon firewall FIRST (parallel with Wave 2 foundation; no internal deps)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P1-01-01 REQ-045, D-069 backend-engineer lexicon_meta_web/lexicon_meta_web_test.go (NEW; package lexicon_meta_web) NEW sibling meta-test mirroring lexicon_meta_docs/lexicon_meta_docs_test.go (D-069). Uses the SAME lexicon.FindBannedTerm (word-boundary, case-insensitive) — NO detection reimplementation. Walks the repo root; targets web/templates/**/*.html + web/static/**/*.js + web/**/*.go (production + test). Excludes .ciagent/ (firewall meta-files), .git/ (VCS), the meta-test file itself (self-exclusion via runtime.Caller(0)), and non-.{html,js,go} files under web/. Includes the G-009 self-test table (consumes lexicon.SyntheticBannedStrings() — G-014 shared helper from REQ-029), TestLexiconMetaWebBannedTermsCount (exactly 10), TestLexiconMetaWebNoFalsePositiveOnOpenYield (word-boundary does not match "openyield"/"european"), and TestLexiconMetaWebWalkCoverage (G-013 — injects a synthetic banned-term file into web/templates/.lexicon_fixture/ and asserts the walk FINDS it). The firewall PASSES at P1 Wave 1 with zero web/ content (a walk that scans nothing reports green on zero hits — closed by the walk-coverage test). "Sign up" maps to "Create a Reach" — the word "account" is banned (REQ-012); the firewall enforces this on all UI strings. go test ./lexicon_meta_web/... green (invoked as go test -run TestLexiconMetaWeb ./...); self-test table passes for all 10 banned terms; TestLexiconMetaWebNoFalsePositiveOnOpenYield green; TestLexiconMetaWebWalkCoverage finds the fixture; a deliberately-injected banned term in a web/templates/*.html file fails the test

Wave 2 — Web foundation: main.go + mock store + base templates + HTMX (parallel with Wave 1; no internal deps)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P1-02-01 REQ-040, D-066, D-067, D-068 frontend-engineer web/main.go, web/server.go web/main.go: package main; func main() calling server.go's runServer(). web/server.go: Go 1.22 net/http.ServeMux (mux := http.NewServeMux(); Go 1.22 method-pattern routing mux.HandleFunc("GET /", ...)); serves web/static/ via http.FileServer (HTMX vendored); loads templates via html/template (template.ParseGlob("web/templates/*.html")); constructs the mock store (store.NewStore() from P1-03-01) and injects it into handlers. Listens on :8080 (env-overridable PORT). No external deps (stdlib only — G-006). No app.go, no Cosmos runtime. go build ./web succeeds; go run ./web starts a server on :8080 (manual check: curl -s http://localhost:8080/ returns the home page); go.mod unchanged (zero new require lines); lexicon firewall green on web/main.go + web/server.go
P1-02-02 REQ-040, D-066 frontend-engineer web/static/htmx.min.js (vendored), web/static/style.css Vendored HTMX 1.x minified JS (a single static asset — NOT a go get; G-006 preserved). style.css: minimal lexicon-clean CSS for the 5 screens (no banned terms in comments/class names). HTMX attributes used for progressive enhancement (form POST → swap). web/static/htmx.min.js exists (vendored, not a go.mod entry); go build ./web succeeds; go run ./web serves /static/htmx.min.js (manual check: curl -s http://localhost:8080/static/htmx.min.js returns the JS); lexicon firewall green on web/static/**
P1-02-03 REQ-040 frontend-engineer web/templates/base.html, web/templates/home.html base.html: the shared layout (head, nav, {{block "content" .}}{{end}}, HTMX script tag, footer). Nav links to all 5 screens (Reach signup, Stash dashboard, Window authorization, Standing progress, Bloom accrual) — P2..P5 screens link to placeholder routes that P2..P5 fill in. home.html: site home with a one-paragraph OpenYield overview (lexicon-clean — "real production"/"Holder"/"Reach"/"Stash"; NOT "yield"/"account"/"bank") + links to the 5 screens. Lexicon-clean by construction (the P1-01-01 firewall scans these as they land). web/templates/base.html + web/templates/home.html exist; go run ./web serves / (home renders); nav has 5 links; lexicon firewall green P1-01-01, P1-02-01

Wave 3 — Mock store (imports x/*/types) + import-invariant test (blocked-by Wave 2)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P1-03-01 REQ-040, D-067, D-070, D-071 backend-engineer web/store/store.go, web/store/fixtures.go web/store/store.go: in-memory mock store (type Store struct { mu sync.Mutex; reaches map[string]identitytypes.Reach; stashes map[string]stashtypes.Stash; ... }). NewStore() seeds from fixtures.go. Methods: CreateReach(holderID, publicKey string) (identitytypes.Reach, stashtypes.Stash, error)atomic Reach + Stash creation per D-071: creates a Reach with IsNomad=true + a Stash with HolderID matching the Reach's HolderID and BalanceGrain seeded to a test value (e.g., 500000 Grain = 50 Bread per D-071 example); ListReaches() []identitytypes.Reach; GetReach(reachID string) (identitytypes.Reach, bool); GetStash(holderID string) (stashtypes.Stash, bool). Imports x/identity/types, x/stash/types (app-layer consumption — D-070; NOT a cross-x/ import). fixtures.go: seed data (a few pre-existing Reach/Stash pairs for the list view). All strings lexicon-clean ("Holder"/"Reach"/"Stash"; NOT "account"/"bank"/"deposit"). go build ./web/store succeeds; go test ./web/store passes (P1-03-02); CreateReach returns a Reach with IsNomad=true AND a Stash with matching HolderID (atomic — D-071); web/store imports only x/identity/types + x/stash/types (no x/*/keeper — verified by P1-03-03); lexicon firewall green P1-02-01
P1-03-02 REQ-040, D-033 backend-engineer web/store/store_test.go Table-driven tests: CreateReach atomicity (Reach IsNomad=true + Stash HolderID matches + BalanceGrain seeded); ListReaches returns seeded + created; GetReach hit/miss; GetStash hit/miss; concurrent CreateReach (mutex safety — two goroutines, distinct holder IDs, no race). Coverage ≥80% on web/store. go test ./web/store passes; coverage ≥80% on web/store; atomic-create test asserts both Reach + Stash exist after one call; lexicon firewall green P1-03-01
P1-03-03 REQ-040, D-070, G-003 backend-engineer web/store/import_test.go G-003 boundary test for web/: scans all non-test .go files under web/ using go/parser (or go/build import list) and asserts web/ imports ONLY x/*/types packages (NOT x/*/keeper — there is no keeper in v0.6). This is the app-layer-consumption invariant (D-070): web/ may consume protocol types but must not reach into keeper/state machinery. The existing v0.2 G-003 import-invariant test (scanning x/**/*.go) is UNCHANGED — web/ is outside x/ so it is not in that test's scope; this NEW test covers the web/ scope. go test ./web/store passes; the import-invariant test asserts no x/*/keeper import in any web/ production file; x/*/types imports are allowed (D-070) P1-03-01

Wave 4 — Reach signup handler + templates + tests (blocked-by Wave 3 store)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P1-04-01 REQ-040, D-071 frontend-engineer web/handlers/reach.go, web/handlers/server.go (handler wiring) web/handlers/reach.go: ReachHandler struct holding *store.Store + *template.Template. Routes (Go 1.22 method patterns, wired in web/server.go): GET /reach → list view (ListReaches); GET /reach/{id} → detail view (GetReach + GetStash); GET /reach/new → "Create a Reach" form (lexicon-clean: "Create a Reach", NOT "Sign up for an account" — "account" is banned); POST /reach → form handler calling store.CreateReach (atomic Reach + Stash per D-071), redirect to the new Reach detail. HTMX: the form POST can be progressive-enhanced (hx-post="/reach" → swap). web/handlers/server.go: handler constructor + route registration helper. go build ./web succeeds; go run ./web serves GET /reach (list), GET /reach/new (form), POST /reach (creates + redirects); manual browser check: fill the form → a Reach + Stash appear; lexicon firewall green on web/handlers/reach.go P1-03-01, P1-02-03
P1-04-02 REQ-040 frontend-engineer web/templates/reach_list.html, web/templates/reach_detail.html, web/templates/reach_new.html reach_list.html: table of Reaches (ReachID, HolderID, IsNomad, IsFreeholder) + "Create a Reach" link. reach_detail.html: Reach fields + the associated Stash (BalanceGrain). reach_new.html: the "Create a Reach" form (HolderID + PublicKey inputs; submit POST /reach). All lexicon-clean ("Holder"/"Reach"/"Stash"; NOT "account"/"bank"/"deposit"). Extends base.html. go run ./web renders all 3 Reach templates; manual browser check: form submission creates a Reach visible in the list; lexicon firewall green on all 3 templates P1-04-01, P1-02-03
P1-04-03 REQ-040, D-033 frontend-engineer + backend-engineer web/handlers/reach_test.go Handler tests using httptest.NewRecorder + httptest.NewRequest (stdlib — no external test deps): GET /reach returns 200 + list HTML; GET /reach/{id} returns 200 + detail HTML for a seeded Reach; GET /reach/new returns 200 + form HTML; POST /reach with valid form creates a Reach + Stash atomically (assert both in the store) + redirects (302) to the detail; POST /reach with empty HolderID returns 400; the rendered HTML contains NO banned terms (assert via lexicon.FindBannedTerm on the response body — a per-handler lexicon check, complementing the file-scan firewall). Coverage ≥80% on web/handlers (P1 scope). go test ./web/handlers passes; coverage ≥80% on web/handlers (P1 subset); atomic-create asserted via the store after POST; rendered-HTML lexicon check green P1-04-01, P1-04-02

Wave 5 — Phase verification + ship

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P1-99-01 REQ-012, REQ-040, REQ-045 lead-developer (cross-cutting) go build ./... + go test ./... green (incl. all v0.1..v0.5 baseline + P1 web + the new lexicon_meta_web/ firewall); coverage ≥80% on web/store + web/handlers (P1 subset); lexicon_meta_web/ firewall green; lexicon_meta_test.go (v0.2, x/*.go) + lexicon_meta_docs_test.go (v0.3, docs) green (no regression); go.mod unchanged (G-006 — zero new require lines); go run ./web starts on :8080 (manual check); tag v0.5.1. go test ./... green; coverage ≥80% on web/store + web/handlers; all 3 lexicon firewalls green; go.mod unchanged; go run ./web serves the home + Reach screens; git tag v0.5.1 created P1-01-01, P1-04-03, P1-03-02, P1-03-03

P1 Must-Haves

  • lexicon_meta_web/lexicon_meta_web_test.go exists (package lexicon_meta_web); mirrors lexicon_meta_docs/ detection (same lexicon.FindBannedTerm + word-boundary + G-009 self-test via lexicon.SyntheticBannedStrings() + G-013 walk-coverage + self-exclusion); scans web/templates/**/*.html + web/static/**/*.js + web/**/*.go; excludes .ciagent/ + .git/ + itself.
  • go test ./lexicon_meta_web/... green (firewall passes with P1 web content).
  • go test ./... green across the whole repo (no regression; v0.2/v0.3 lexicon firewalls unchanged).
  • web/main.go + web/server.go exist; Go 1.22 net/http.ServeMux + html/template; go run ./web starts on :8080 with no external deps.
  • web/static/htmx.min.js vendored (NOT a go get); go.mod unchanged (G-006).
  • web/store/store.go imports x/identity/types + x/stash/types (D-070 app-layer consumption); CreateReach atomically creates a Reach (IsNomad=true) + a Stash (D-071); web/store/import_test.go asserts no x/*/keeper imports.
  • web/handlers/reach.go + 3 Reach templates exist; GET /reach (list), GET /reach/{id} (detail), GET /reach/new (form), POST /reach (atomic create + redirect).
  • "Create a Reach" labels used (NOT "Sign up for an account" — "account" is banned per REQ-012).
  • ≥80% coverage on web/store + web/handlers (P1 subset).
  • Rendered-HTML lexicon check green (per-handler test asserts no banned terms in response body).
  • Git tag v0.5.1.

P1 Risks & Mitigations

  • "account" lexicon drift in signup form (highest P1 risk) → "Create a Reach" labels; the lexicon_meta_web/ firewall scans web/templates/reach_new.html; the per-handler rendered-HTML lexicon check (P1-04-03) catches a banned term in dynamic content. Mitigation: firewall-first (P1-01-01 lands before content).
  • HTMX vendored asset size / version → HTMX 1.x minified is ~14KB; vendored as a static file, not a go.mod entry. Pin the version in a comment in web/static/htmx.min.js header.
  • Atomic Reach + Stash creation race (D-071)CreateReach holds the store mutex across both map writes; the concurrent-create test (P1-03-02) asserts no race.
  • G-003 boundary confusionweb/ importing x/*/types is app-layer consumption (D-070), NOT a cross-x/ import; the NEW web/store/import_test.go (P1-03-03) documents and enforces the boundary (only x/*/types, never x/*/keeper).

Phase P2 — Stash Dashboard (REQ-041) → v0.5.2

  • Slug: stash-dashboard
  • Branch: phase/02-stash-dashboard
  • REQs covered: REQ-041 (Stash dashboard — balance in Grain + Bread-scale conversion + 90-day maturity progress)
  • Tag: v0.5.2
  • Type: feat
  • Goal: Ship the Stash dashboard screen: balance in Grain + Bread-scale conversion (using x/bread/types.BreadScaleAll() + GrainsPerBread=10000 per D-073 — code constants are the source of truth, NOT docs) + 90-day maturity progress bar (x/stash/types.StashActivity.IsMature, MaturityThresholdDays=90). Depends on P1 (a Stash exists, created atomically at signup per D-071).

Wave 1 — Store extensions + handler + template (parallel where possible; blocked-by P1 ship)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P2-01-01 REQ-041, D-073 backend-engineer web/store/store.go (EXTEND), web/store/fixtures.go (EXTEND) Extend the mock store: add GetStashActivity(stashID string) (stashtypes.StashActivity, bool) + seed StashActivity fixtures (some mature, some not — ActiveDays varying around MaturityThresholdDays=90; MaxGapDays varying around MaxGapForMaturity=30). Import x/stash/types (already imported in P1). The Bread-scale conversion is computed in the handler/template from x/bread/types.BreadScaleAll() + GrainsPerBread=10000 (D-073 — code constants, NOT docs/shared/bread-scale.md which is outdated). go build ./web/store succeeds; GetStashActivity returns seeded activities; web/store still imports only x/*/types (P1-03-03 import-invariant green); lexicon firewall green P1-99-01
P2-02-01 REQ-041, D-073 frontend-engineer web/handlers/stash.go, web/handlers/server.go (EXTEND route wiring) StashHandler struct. Route: GET /stash/{holderID} → dashboard. Loads Stash (balance BalanceGrain in Grain) + StashActivity from the store; computes Bread-scale conversion by calling x/bread/types.BreadScaleAll() (returns the 11-denomination table) and x/bread/types.GrainsPerBread (10000) to convert the Grain balance into Bread (and display the full scale table for context). Computes 90-day maturity progress: ActiveDays / MaturityThresholdDays (as a percentage; capped at 100%) + IsMature() boolean (stashtypes.StashActivity.IsMature()ActiveDays >= 90 && MaxGapDays <= 30). Passes all to the template. go build ./web succeeds; go run ./web serves GET /stash/{holderID} (200 + dashboard HTML); Bread-scale conversion uses BreadScaleAll() + GrainsPerBread=10000 (D-073 — verified by a test asserting the conversion matches the code constants); lexicon firewall green P2-01-01, P1-02-03
P2-02-02 REQ-041 frontend-engineer web/templates/stash.html Stash dashboard template: balance in Grain + Bread-scale conversion table (all 11 denominations from BreadScaleAll() — Grain, Crumb, Bread, Loaf, Batch, Cake, Bakery, Granary, Mill, Harvest, Earth) + 90-day maturity progress bar (CSS width = ActiveDays/90 * 100%) + IsMature badge (green if mature, amber if not). Extends base.html. Lexicon-clean ("Stash"/"Grain"/"Bread"/"maturity"; NOT "bank"/"deposit"/"savings"/"interest"). go run ./web renders the Stash dashboard; manual check: balance shows in Grain + Bread scale; progress bar reflects ActiveDays; lexicon firewall green on web/templates/stash.html P2-02-01, P1-02-03

Wave 2 — Tests (blocked-by Wave 1)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P2-03-01 REQ-041, D-073, D-033 frontend-engineer + backend-engineer web/handlers/stash_test.go, web/store/store_test.go (EXTEND) Handler test (httptest): GET /stash/{holderID} returns 200 + dashboard HTML; the rendered HTML shows the Grain balance + the Bread-scale conversion table (all 11 denominations) + the maturity progress bar. Bread-scale conversion correctness test: asserts the handler's conversion matches x/bread/types.BreadScaleAll() + GrainsPerBread=10000 (D-073 — e.g., 500000 Grain = 50 Bread; 100 Grain = 1 Crumb); a test that would FAIL if the handler used the outdated docs/shared/bread-scale.md values (1,000× ratios) instead of the code constants. Maturity progress test: a mature fixture (ActiveDays=90, MaxGapDays=10IsMature()==true, progress 100%) vs an immature fixture (ActiveDays=45, MaxGapDays=10IsMature()==false, progress 50%). Store test: GetStashActivity hit/miss. Rendered-HTML lexicon check (no banned terms in response body). Coverage ≥80% on web/handlers (P1+P2 cumulative). go test ./web/... passes; Bread-scale conversion matches code constants (D-073); maturity progress matches IsMature(); coverage ≥80% on web/handlers (cumulative); rendered-HTML lexicon check green P2-02-02, P2-01-01

Wave 3 — Phase verification + ship

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P2-99-01 REQ-012, REQ-041 lead-developer (cross-cutting) go build ./... + go test ./... green; coverage ≥80% on web/store + web/handlers (cumulative); lexicon_meta_web/ firewall green (now scans web/templates/stash.html + web/handlers/stash.go); go.mod unchanged; go run ./web serves the Stash dashboard; tag v0.5.2. go test ./... green; coverage ≥80% (cumulative); lexicon_meta_web/ green; go.mod unchanged; Stash dashboard reachable from home; git tag v0.5.2 P2-03-01

P2 Must-Haves

  • web/handlers/stash.go + web/templates/stash.html exist; GET /stash/{holderID} renders the dashboard.
  • Balance shown in Grain + Bread-scale conversion using x/bread/types.BreadScaleAll() + GrainsPerBread=10000 (D-073 — code constants, NOT docs).
  • 90-day maturity progress bar + IsMature badge using x/stash/types.StashActivity.IsMature() + MaturityThresholdDays=90 + MaxGapForMaturity=30.
  • Bread-scale conversion correctness test (would fail if docs values were used instead of code constants — D-073 regression guard).
  • ≥80% coverage on web/store + web/handlers (cumulative).
  • lexicon_meta_web/ firewall green; rendered-HTML lexicon check green.
  • go.mod unchanged (G-006).
  • Git tag v0.5.2.

P2 Risks & Mitigations

  • Bread-scale source-of-truth drift (D-073) → the conversion correctness test asserts the handler uses BreadScaleAll() + GrainsPerBread=10000 (code constants); a test using the outdated docs/shared/bread-scale.md 1,000× ratios would fail. The doc-fix is a P1+ follow-up (NOT v0.6 scope — PROJECT.md out-of-scope).
  • Maturity progress > 100% → cap ActiveDays/90 at 100% in the template/handler; test the cap.

Phase P3 — Window Authorization (REQ-042) → v0.5.3

  • Slug: window-authorization
  • Branch: phase/03-window-authorization
  • REQs covered: REQ-042 (Window authorization — open Window (scope+duration+rate-limit), lifecycle Open→Active→Revoked/Expired via Window.Activate/Revoke/Expire, audit log)
  • Tag: v0.5.3
  • Type: feat
  • Goal: Ship the Window authorization screen: a form to open a Window (scope + duration + rate-limit) delegating to a service, a lifecycle view (Open→Active→Revoked/Expired using x/window/types.Window.Activate/Revoke/Expire), and an audit log view (AuditEntry). Depends on P2 (Window scope references a Stash).

Wave 1 — Store extensions + handler + template (blocked-by P2 ship)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P3-01-01 REQ-042 backend-engineer web/store/store.go (EXTEND), web/store/fixtures.go (EXTEND) Extend the mock store: add windows map[string]windowtypes.Window + auditLogs map[string][]windowtypes.AuditEntry. Methods: OpenWindow(grantorHolder, grantee string, scope windowtypes.Scope, start, end int64, rateLimit windowtypes.RateLimit) (windowtypes.Window, error) (creates a Window status=Open + an initial AuditEntry); ActivateWindow(windowID string) error (calls Window.Activate() — Open→Active); RevokeWindow(windowID string) error (calls Window.Revoke() — idempotent; Revoked/Expired are no-ops); ExpireWindow(windowID string) (calls Window.Expire()); ListWindows(grantorHolder string) []windowtypes.Window; GetWindow(windowID string) (windowtypes.Window, bool); GetAuditLog(windowID string) []windowtypes.AuditEntry; AppendAuditEntry(windowID string, entry windowtypes.AuditEntry). Import x/window/types (app-layer — D-070). Seed fixtures: a few Windows in various lifecycle states (Open, Active, Revoked, Expired) + audit logs. go build ./web/store succeeds; OpenWindow creates a Window status=Open; ActivateWindow transitions Open→Active; RevokeWindow transitions to Revoked (idempotent on already-Revoked/Expired); ExpireWindow transitions to Expired; web/store still imports only x/*/types (import-invariant green); lexicon firewall green P2-99-01
P3-02-01 REQ-042 frontend-engineer web/handlers/window.go, web/handlers/server.go (EXTEND) WindowHandler struct. Routes: GET /window → list of Windows for a holder; GET /window/{id} → detail (lifecycle state + scope + rate-limit + audit log); GET /window/new → "Open a Window" form (scope kind dropdown from ScopeKind enum: ReadStash/ReadStanding/ProcessPassActForStand; resource-id; grantee; duration start/end; rate-limit max-actions + per-duration); POST /windowOpenWindow + redirect to detail; POST /window/{id}/activateActivateWindow (HTMX swap); POST /window/{id}/revokeRevokeWindow; POST /window/{id}/expireExpireWindow. Each lifecycle action appends an AuditEntry. go build ./web succeeds; go run ./web serves the 4 Window routes; lifecycle transitions call Window.Activate/Revoke/Expire (verified by test); lexicon firewall green P3-01-01, P1-02-03
P3-02-02 REQ-042 frontend-engineer web/templates/window_list.html, web/templates/window_detail.html, web/templates/window_new.html window_list.html: table of Windows (WindowID, GrantorHolder, Grantee, Scope, Status) + "Open a Window" link. window_detail.html: Window fields + lifecycle state badge (Open=amber, Active=green, Revoked=red, Expired=grey) + Activate/Revoke/Expire buttons (HTMX hx-post) + audit log table (AuditEntry rows: timestamp, action, result, granter-ref). window_new.html: the "Open a Window" form. Extends base.html. Lexicon-clean ("Window"/"scope"/"Holder"/"Reach"; NOT "account"/"bank"). go run ./web renders all 3 Window templates; manual check: open a Window → activate → revoke → audit log shows the actions; lexicon firewall green on all 3 templates P3-02-01, P1-02-03

Wave 2 — Tests (blocked-by Wave 1)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P3-03-01 REQ-042, D-033 frontend-engineer + backend-engineer web/handlers/window_test.go, web/store/store_test.go (EXTEND) Handler tests (httptest): GET /window (list 200); GET /window/{id} (detail 200 + lifecycle badge + audit log); GET /window/new (form 200); POST /window (open → 302 to detail; new Window status=Open + initial AuditEntry); POST /window/{id}/activate (Open→Active; AuditEntry appended); POST /window/{id}/revoke (→Revoked; idempotent — second revoke is a no-op, no new AuditEntry); POST /window/{id}/expire (→Expired; revoke-after-expire is a no-op). Lifecycle correctness test: asserts the handler calls Window.Activate/Revoke/Expire (the real x/window/types methods — not a reimplementation); Revoke() on an Expired window is a no-op (matches the v0.2 type contract). Store tests: OpenWindow/ActivateWindow/RevokeWindow/ExpireWindow/ListWindows/GetAuditLog round-trips. Rendered-HTML lexicon check. Coverage ≥80% on web/handlers (P1+P2+P3 cumulative). go test ./web/... passes; lifecycle transitions match Window.Activate/Revoke/Expire; revoke-on-expired no-op; coverage ≥80% (cumulative); rendered-HTML lexicon check green P3-02-02, P3-01-01

Wave 3 — Phase verification + ship

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P3-99-01 REQ-012, REQ-042 lead-developer (cross-cutting) go build ./... + go test ./... green; coverage ≥80% (cumulative); lexicon_meta_web/ green; go.mod unchanged; go run ./web serves the Window screen; tag v0.5.3. go test ./... green; coverage ≥80% (cumulative); lexicon_meta_web/ green; go.mod unchanged; Window screen reachable from home; git tag v0.5.3 P3-03-01

P3 Must-Haves

  • web/handlers/window.go + 3 Window templates exist; GET /window (list), GET /window/{id} (detail + lifecycle + audit log), GET /window/new (form), POST /window (open), POST /window/{id}/activate|revoke|expire (lifecycle).
  • Lifecycle transitions call x/window/types.Window.Activate/Revoke/Expire (the real methods); Revoke() on Expired is a no-op (v0.2 type contract).
  • Audit log (AuditEntry) appended on each lifecycle action; displayed in the detail view.
  • Window form uses ScopeKind enum (ReadStash/ReadStanding/ProcessPassActForStand) + rate-limit fields.
  • ≥80% coverage on web/store + web/handlers (cumulative).
  • lexicon_meta_web/ firewall green; rendered-HTML lexicon check green.
  • go.mod unchanged (G-006).
  • Git tag v0.5.3.

P3 Risks & Mitigations

  • Lifecycle state-machine divergence from the type contract → the handler MUST call Window.Activate/Revoke/Expire (not reimplement transitions); the lifecycle correctness test asserts the real methods are invoked.
  • Revoke-on-Expired no-op → the v0.2 Revoke() returns nil on Expired (terminal state wins); the test covers this edge.

Phase P4 — Standing + Freeholder Signals (REQ-043) → v0.5.4

  • Slug: standing-freeholder-signals
  • Branch: phase/04-standing-freeholder-signals
  • REQs covered: REQ-043 (Standing + Freeholder signals progress — computed from mock Rating/Vouch/Slash using locked constants + GetStandingBucket/ComputeDiversityBonus/GetVoucherWeight; 4-signal progress via FreeholderSignals.IsFreeholderEligible)
  • Tag: v0.5.4
  • Type: feat
  • Goal: Ship the Standing + Freeholder signals progress screen: computed from mock Rating/Vouch/Slash records using the locked formula constants + GetStandingBucket/ComputeDiversityBonus/GetVoucherWeight; displays the 4-signal progress (FreeholderSignals.IsFreeholderEligible — StashMaturity, MultiDomainStanding, CommittedCapital, CommunityEndorsement). Depends on P1 (Standing is per-Reach).

Wave 1 — Store extensions + handler + template (blocked-by P3 ship)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P4-01-01 REQ-043 backend-engineer web/store/store.go (EXTEND), web/store/fixtures.go (EXTEND) Extend the mock store: add ratings map[string][]standingtypes.Rating (per-ratee), vouches map[string][]standingtypes.Vouch, slashes map[string][]standingtypes.Slash. Methods: ListRatings(rateeID string) []standingtypes.Rating; ListVouches(voucheeID string) []standingtypes.Vouch; ListSlashes(reachID string) []standingtypes.Slash; ComputeStandingScore(reachID string) (float64, standingtypes.StandingBucket) — computes a simplified standing score from the mock Ratings using the locked constants (PriorMean=4.0, PriorWeight=10, decay buckets, ComputeDiversityBonus, GetVoucherWeight) + GetStandingBucket(score, ratingCount, isSlashed); ComputeFreeholderSignals(reachID string) standingtypes.FreeholderSignals — computes the 4 signals: StashMaturity (from StashActivity.IsMature() — P2 store method), MultiDomainStanding (score >= FreeholderMinStandingScore=4.5 in >= FreeholderMinCategories=3), CommittedCapital (mock: Stash balance >= a threshold), CommunityEndorsement (>= 1 Vouch). Import x/standing/types + x/stash/types (app-layer — D-070). Seed fixtures: a Reach with enough Ratings/Vouches to be Freeholder-eligible + one that is not. go build ./web/store succeeds; ComputeStandingScore returns a score + bucket using the locked constants; ComputeFreeholderSignals returns 4 booleans; web/store imports only x/*/types (import-invariant green); lexicon firewall green P3-99-01
P4-02-01 REQ-043 frontend-engineer web/handlers/standing.go, web/handlers/server.go (EXTEND) StandingHandler struct. Route: GET /standing/{reachID} → Standing + Freeholder signals progress. Loads Ratings/Vouches/Slashes from the store; calls ComputeStandingScore + ComputeFreeholderSignals; passes the score, bucket, 4 signals (each as a progress indicator), and the underlying records to the template. go build ./web succeeds; go run ./web serves GET /standing/{reachID} (200 + progress HTML); the score is computed from the locked constants (verified by test); lexicon firewall green P4-01-01, P1-02-03
P4-02-02 REQ-043 frontend-engineer web/templates/standing.html Standing + Freeholder signals template: standing score (float, 1 decimal) + StandingBucket badge (New/Trusted/Preferred/Top/Slashed) + 4-signal progress (StashMaturity, MultiDomainStanding, CommittedCapital, CommunityEndorsement — each a check/cross + label) + Freeholder-eligible badge (green if IsFreeholderEligible(), grey if not) + underlying Ratings/Vouches/Slashes tables. Extends base.html. Lexicon-clean ("Standing"/"Freeholder"/"Vouch"/"Rating"; NOT "account"/"bank"). go run ./web renders the Standing screen; manual check: a seeded Freeholder-eligible Reach shows 4 checks + green badge; a non-eligible Reach shows crosses; lexicon firewall green on web/templates/standing.html P4-02-01, P1-02-03

Wave 2 — Tests (blocked-by Wave 1)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P4-03-01 REQ-043, D-033 frontend-engineer + backend-engineer web/handlers/standing_test.go, web/store/store_test.go (EXTEND) Handler test (httptest): GET /standing/{reachID} returns 200 + progress HTML; the rendered HTML shows the score + bucket + 4 signals + Freeholder-eligible badge. Standing score computed-from-locked-constants test: asserts ComputeStandingScore uses x/standing/types constants (PriorMean=4.0, PriorWeight=10, ComputeDiversityBonus, GetVoucherWeight, GetStandingBucket) — a test that would FAIL if the handler hardcoded a score instead of computing from the locked constants. Freeholder-eligible badge test: a seeded eligible Reach (IsFreeholderEligible()==true → green badge) vs a non-eligible Reach (a signal false → grey badge); asserts the badge reflects IsFreeholderEligible(). Store tests: ListRatings/ListVouches/ListSlashes/ComputeStandingScore/ComputeFreeholderSignals round-trips. Rendered-HTML lexicon check. Coverage ≥80% on web/handlers (cumulative P1..P4). go test ./web/... passes; standing score computed from locked constants; Freeholder-eligible badge reflects IsFreeholderEligible(); coverage ≥80% (cumulative); rendered-HTML lexicon check green P4-02-02, P4-01-01

Wave 3 — Phase verification + ship

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P4-99-01 REQ-012, REQ-043 lead-developer (cross-cutting) go build ./... + go test ./... green; coverage ≥80% (cumulative); lexicon_meta_web/ green; go.mod unchanged; go run ./web serves the Standing screen; tag v0.5.4. go test ./... green; coverage ≥80% (cumulative); lexicon_meta_web/ green; go.mod unchanged; Standing screen reachable from home; git tag v0.5.4 P4-03-01

P4 Must-Haves

  • web/handlers/standing.go + web/templates/standing.html exist; GET /standing/{reachID} renders the progress.
  • Standing score computed from x/standing/types locked constants (PriorMean, PriorWeight, decay buckets, ComputeDiversityBonus, GetVoucherWeight, GetStandingBucket) — NOT hardcoded.
  • 4-signal progress (StashMaturity, MultiDomainStanding, CommittedCapital, CommunityEndorsement) displayed; Freeholder-eligible badge reflects FreeholderSignals.IsFreeholderEligible().
  • Standing-score computed-from-locked-constants test (regression guard against hardcoding).
  • ≥80% coverage on web/store + web/handlers (cumulative).
  • lexicon_meta_web/ firewall green; rendered-HTML lexicon check green.
  • go.mod unchanged (G-006).
  • Git tag v0.5.4.

P4 Risks & Mitigations

  • Standing formula oversimplification → the mock ComputeStandingScore is a SIMPLIFIED computation from the locked constants (not the full Bayesian formula — sub-tables deferred per PROJECT.md Q2); the test asserts it uses the locked constants, not that it matches a full oracle. Document the simplification in a code comment.
  • Freeholder-eligible badge divergence → the badge MUST reflect IsFreeholderEligible() (the real method); the test asserts the rendered badge matches the method output.

Phase P5 — Bloom Accrual (REQ-044) → v0.5.5

  • Slug: bloom-accrual
  • Branch: phase/05-bloom-accrual
  • REQs covered: REQ-044 (Bloom accrual — per-Stash BloomRecord view: AccruedGrain, RateBasisPoints, LastAccrualBlock; 4.5% target rate)
  • Tag: v0.5.5
  • Type: feat
  • Goal: Ship the Bloom accrual screen: a per-Stash BloomRecord view (AccruedGrain, RateBasisPoints, LastAccrualBlock) computed from mock data; shows the 4.5% target rate (TargetBloomRateBasisPoints=450). Depends on P2 (BloomRecord is per-Stash).

Wave 1 — Store extensions + handler + template (blocked-by P4 ship)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P5-01-01 REQ-044 backend-engineer web/store/store.go (EXTEND), web/store/fixtures.go (EXTEND) Extend the mock store: add bloomRecords map[string]bloomtypes.BloomRecord (keyed by StashID). Methods: GetBloomRecord(stashID string) (bloomtypes.BloomRecord, bool); ListBloomRecords(holderID string) []bloomtypes.BloomRecord (via the holder's Stashes). Import x/bloom/types (app-layer — D-070). Seed fixtures: BloomRecord per Stash with AccruedGrain (mock value), RateBasisPoints (e.g., 450 = the target rate), LastAccrualBlock (mock block height). go build ./web/store succeeds; GetBloomRecord/ListBloomRecords return seeded records; web/store imports only x/*/types (import-invariant green); lexicon firewall green P4-99-01
P5-02-01 REQ-044 frontend-engineer web/handlers/bloom.go, web/handlers/server.go (EXTEND) BloomHandler struct. Route: GET /bloom/{stashID} → Bloom accrual view. Loads the BloomRecord from the store; reads x/bloom/types.TargetBloomRateBasisPoints=450 (4.5% target rate — D-073 code-constant source-of-truth) + MinBloomRateBasisPoints=400 + MaxBloomRateBasisPoints=500 + AccrualPeriodBlocks=144; passes the record + the target rate band to the template. go build ./web succeeds; go run ./web serves GET /bloom/{stashID} (200 + accrual HTML); the target rate is read from x/bloom/types.TargetBloomRateBasisPoints (verified by test); lexicon firewall green P5-01-01, P1-02-03
P5-02-02 REQ-044 frontend-engineer web/templates/bloom.html Bloom accrual template: BloomRecord fields (AccruedGrain, RateBasisPoints displayed as a percentage e.g. 4.5%, LastAccrualBlock) + the target rate band (4.0%5.0%, target 4.5%) + a note that "Bloom originates only from real production" (the MissionLockBloom const, lexicon-clean). Extends base.html. Lexicon-clean ("Bloom"/"Grain"/"real production"; NOT "yield"/"interest"/"deposit"). go run ./web renders the Bloom screen; manual check: accrued Grain + 4.5% target rate displayed; lexicon firewall green on web/templates/bloom.html P5-02-01, P1-02-03

Wave 2 — Tests (blocked-by Wave 1)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P5-03-01 REQ-044, D-033 frontend-engineer + backend-engineer web/handlers/bloom_test.go, web/store/store_test.go (EXTEND) Handler test (httptest): GET /bloom/{stashID} returns 200 + accrual HTML; the rendered HTML shows AccruedGrain + RateBasisPoints (as %) + LastAccrualBlock + the 4.5% target rate. Target-rate source-of-truth test: asserts the handler reads x/bloom/types.TargetBloomRateBasisPoints=450 (NOT a hardcoded 450 or a docs value — D-073 pattern); a test that would FAIL if the handler hardcoded the rate. Store tests: GetBloomRecord/ListBloomRecords round-trips. Rendered-HTML lexicon check (highest-risk screen for "yield"/"interest" drift — the test scans the response body). Coverage ≥80% on web/handlers (cumulative P1..P5). go test ./web/... passes; target rate read from code constant (D-073); coverage ≥80% (cumulative); rendered-HTML lexicon check green (no "yield"/"interest") P5-02-02, P5-01-01

Wave 3 — Phase verification + ship

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P5-99-01 REQ-012, REQ-044 lead-developer (cross-cutting) go build ./... + go test ./... green; coverage ≥80% (cumulative across all web/ packages); lexicon_meta_web/ green; go.mod unchanged; go run ./web serves the Bloom screen; tag v0.5.5. go test ./... green; coverage ≥80% (cumulative); lexicon_meta_web/ green; go.mod unchanged; Bloom screen reachable from home; git tag v0.5.5 P5-03-01

P5 Must-Haves

  • web/handlers/bloom.go + web/templates/bloom.html exist; GET /bloom/{stashID} renders the accrual view.
  • BloomRecord fields displayed: AccruedGrain, RateBasisPoints (as %), LastAccrualBlock.
  • 4.5% target rate read from x/bloom/types.TargetBloomRateBasisPoints=450 (D-073 code-constant source-of-truth; NOT hardcoded).
  • Target-rate source-of-truth test (regression guard against hardcoding).
  • ≥80% coverage on web/store + web/handlers (cumulative P1..P5).
  • lexicon_meta_web/ firewall green; rendered-HTML lexicon check green (no "yield"/"interest").
  • go.mod unchanged (G-006).
  • Git tag v0.5.5.

P5 Risks & Mitigations

  • "yield"/"interest" lexicon drift in Bloom prose (highest P5 risk — Bloom is conceptually close to "yield") → "Bloom"/"real production"/"accrual" labels; the lexicon_meta_web/ firewall scans web/templates/bloom.html; the rendered-HTML lexicon check (P5-03-01) scans the response body. "Bloom" is the vision lexicon (§6); "yield" is banned.
  • Target-rate hardcoding → the handler MUST read TargetBloomRateBasisPoints from x/bloom/types (D-073); the source-of-truth test asserts it.

Phase P6 — Final Review + Audit + Milestone Ship → v0.5.6

  • Slug: final-review-audit-ship
  • Branch: phase/06-final-review-audit-ship
  • REQs covered: all v0.6 REQs (REQ-040..REQ-045) — final coverage accounting; no new REQs (covers post-hoc fixes from REVIEW/AUDIT)
  • Tag: v0.5.6 (IS the v0.6 milestone release; D-008)
  • Type: final
  • Personas: lead-developer (review/ship) + ci-security-auditor (ACTIVATED for the v0.6 milestone audit + feature purity gate)
  • Goal: Multi-persona review across P1..P5, audit (reconstruction test + feature purity gate: no breaking schema changes; G-003 production firewall intact — web/ is app-layer, not an x/ module; G-006 go.mod unchanged — HTMX is a vendored static asset, not a Go dep; locked-const firewall intact — no x/ consts amended), milestone ship (merge to main, tag v0.5.6 = v0.6 milestone release, release, delete all milestone branches).

Wave 1 — Review + Audit (parallel)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P6-01-01 lead-developer (review) .ciagent/oy/REVIEW.md (NEW for v0.6) Multi-persona code review across P1..P5. Adversarial probes: (1) does go run ./web start with no external deps (G-006); (2) are all 5 screens reachable from the home page; (3) does the lexicon_meta_web/ firewall scan all web/templates/** + web/static/** + web/**/*.go; (4) does the Bread-scale conversion match x/bread/types code constants (D-073); (5) does the Standing score use the locked formula constants; (6) does the Freeholder-eligible badge reflect IsFreeholderEligible(); (7) do the Window lifecycle transitions call Window.Activate/Revoke/Expire; (8) are there any banned terms in any rendered page (rendered-HTML lexicon checks). Auto-apply P0 fixes; flag P1+ for post-hoc. REVIEW.md v0.6 section written; P0 issues (if any) fixed in P6; P1+ flagged. P1..P5
P6-02-01 ci-security-auditor (audit) .ciagent/oy/AUDIT.md (v0.6 section) Audit: (1) reconstruction test (git log ↔ .ciagent/ files for v0.6; each REQ-040..REQ-045 maps to a shipped UI screen / firewall); (2) file/branch/commit discipline (6 phase branches phase/01-*..phase/06-*; 6 patch tags v0.5.1..v0.5.6; D-072 ordering respected — firewall-first P1 before content P2..P5); (3) feature purity gate: no breaking schema changes (no x/ module modified — web/ is new app-layer, not an x/ amendment); locked-const firewall intact (all v0.1..v0.5 consts unchanged — web/ does not touch x/ consts); G-003 production firewall intact (web/ imports only x/*/types per D-070; the web/store/import_test.go import-invariant green; no x/ module gains a production import of another x/ module's types via web/); G-006 go.mod unchanged (HTMX is a vendored static asset, NOT a go get — zero new require lines in v0.6); (4) coverage ≥80% on all web/ packages (web/store, web/handlers); (5) all 3 lexicon firewalls green (lexicon_meta_test.go x/*.go, lexicon_meta_docs_test.go docs, lexicon_meta_web/ web); (6) manual browser check: go run ./web → all 5 screens reachable + the happy path works end-to-end. AUDIT.md v0.6 section written; feature purity gate GREEN (no breaking schema changes; locked-const firewall intact; G-003 intact — web/ is app-layer; G-006 go.mod unchanged); reconstruction test passes. P1..P5

Wave 2 — Ship (blocked-by Wave 1)

Task ID REQ Persona Files Deliverable Must-have verification Blocked-by
P6-03-01 REQ-040..REQ-045 lead-developer (ship) .ciagent/oy/REQUIREMENTS.md, .ciagent/oy/ROADMAP.md Update REQUIREMENTS.md: mark REQ-040..REQ-045 → Complete (UI shipped). Update ROADMAP.md: mark v0.6 milestone COMPLETE; add the tag-line note that v0.6 shipped on the v0.5.x patch line (P0 → v0.5.0, P1..P5 → v0.5.1..v0.5.5, P6 → v0.5.6 = milestone release, per D-008). REQUIREMENTS.md status column updated for all 6 v0.6 REQs → Complete; ROADMAP.md v0.6 marked complete + tag-line note present. P6-01-01, P6-02-01
P6-03-02 (milestone) lead-developer (cross-cutting) Final ship: merge phase/06milestone/v0.6-nomad-web-uimain; create milestone release tag v0.5.6 (= v0.6 milestone release per D-008); delete the 6 phase branches (phase/01-*..phase/06-*) after merge; confirm go build ./... + go test ./... green at the v0.5.6 tag; confirm go run ./web starts at the tag. v0.5.6 tag created on main; go test ./... green at the tag; go run ./web starts at the tag; ROADMAP.md v0.6 complete; phase branches deleted; release notes reference v0.6 scope (5-screen Nomad Web UI: Reach signup, Stash dashboard, Window authorization, Standing + Freeholder signals, Bloom accrual; Go html/template + HTMX vendored; mock server over real x/*/types; lexicon firewall extended to web/). P6-03-01

P6 Must-Haves

  • REVIEW.md v0.6 section written; P0 fixes applied.
  • AUDIT.md v0.6 section written; reconstruction test passes.
  • Feature purity gate GREEN: no breaking schema changes (no x/ module modified); locked-const firewall intact (all v0.1..v0.5 consts unchanged); G-003 production firewall intact (web/ imports only x/*/types per D-070; import-invariant green); G-006 go.mod unchanged (HTMX vendored, not a Go dep).
  • Coverage ≥80% on all web/ packages (web/store, web/handlers).
  • All 3 lexicon firewalls green (lexicon_meta_test.go x/*.go, lexicon_meta_docs_test.go docs, lexicon_meta_web/ web).
  • go run ./web starts at the v0.5.6 tag; all 5 screens reachable; happy path works end-to-end.
  • REQUIREMENTS.md + ROADMAP.md mark v0.6 COMPLETE.
  • Tag v0.5.6 created (= v0.6 milestone release).
  • Milestone branch merged to main.
  • All 6 phase branches deleted (local + remote).

P6 Risks & Mitigations

  • UI mock diverges from x/*/types contracts → the mock store instantiates the REAL x/*/types structs (D-067); the handler tests assert the conversions match the code constants (D-073 for Bread-scale, locked formula constants for Standing, TargetBloomRateBasisPoints for Bloom); divergence is caught by the tests, not just the audit.
  • G-006 dep exception confusion → v0.6 adds NO Go deps (HTMX is a vendored static asset); the v0.5 cosmos-sdk/ibc-go deps stay but v0.6 adds nothing; the audit verifies go.mod is unchanged across the v0.6 milestone range.
  • Milestone versioning confusion (v0.6 milestone = v0.5.6 tag) → lead-developer enforces D-008: final phase patch IS the milestone release; no separate minor tag. ROADMAP tag-line note (P6-03-01) prevents v0.5.6/v0.6.0 confusion.

Coverage Targets (D-033) — v0.6

Package Phase Target Key tests
web/store P1 (created), P2..P5 (extended) ≥80% Atomic Reach+Stash create (D-071); GetStashActivity/IsMature (P2); OpenWindow/ActivateWindow/RevokeWindow/ExpireWindow lifecycle (P3); ComputeStandingScore/ComputeFreeholderSignals from locked constants (P4); GetBloomRecord (P5); import-invariant (only x/*/types, no x/*/keeper — D-070)
web/handlers P1 (created), P2..P5 (extended) ≥80% Reach signup form + atomic create (P1); Stash dashboard + Bread-scale conversion correctness (P2, D-073); Window lifecycle via Window.Activate/Revoke/Expire (P3); Standing score from locked constants + Freeholder-eligible badge (P4); Bloom target-rate from code constant (P5); rendered-HTML lexicon checks (all phases)
lexicon_meta_web P1 100% (test-only) G-009 self-test via SyntheticBannedStrings(); G-013 walk-coverage; no-false-positive on "openyield"/"european"; scans web/templates/** + web/static/** + web/**/*.go

Lexicon assertions (REQ-012): the NEW lexicon_meta_web/ firewall (P1) scans all web/**/*.{html,js,go} for the 10 banned terms. Per-handler rendered-HTML lexicon checks (each phase's handler test) scan the HTTP response body for banned terms — catching dynamic content the file-scan firewall cannot see. The v0.2 lexicon_meta_test.go (x/*.go) + v0.3 lexicon_meta_docs_test.go (docs) are UNCHANGED (no regression).


Task Count Summary — v0.6

Phase Waves Tasks New/Extended
P1 5 9 lexicon_meta_web/ (new firewall); web/main.go, web/server.go, web/static/, web/templates/base.html+home.html, web/store/, web/handlers/reach.go + 3 Reach templates
P2 3 5 web/handlers/stash.go + web/templates/stash.html (Stash dashboard)
P3 3 5 web/handlers/window.go + 3 Window templates (Window authorization)
P4 3 5 web/handlers/standing.go + web/templates/standing.html (Standing + Freeholder signals)
P5 3 5 web/handlers/bloom.go + web/templates/bloom.html (Bloom accrual)
P6 2 4 (review + audit + ship; 0 new — audit + REQUIREMENTS/ROADMAP update + tag)
Total 33 web/ new dir (5 screens) + lexicon_meta_web/ new firewall; zero x/ modifications

Per-Phase REQ Coverage — v0.6

Phase REQs Components
P1 REQ-040, REQ-045 Web foundation + Reach signup (mock server, base templates, HTMX, store, "Create a Reach" form/list/detail) + lexicon firewall extension to web/
P2 REQ-041 Stash dashboard (balance in Grain + Bread-scale conversion via BreadScaleAll()/GrainsPerBread + 90-day maturity progress via IsMature())
P3 REQ-042 Window authorization (open/lifecycle/audit-log via Window.Activate/Revoke/Expire)
P4 REQ-043 Standing + Freeholder signals (computed from mock Ratings/Vouches/Slashes using locked constants + GetStandingBucket/ComputeDiversityBonus/GetVoucherWeight; 4-signal IsFreeholderEligible)
P5 REQ-044 Bloom accrual (per-Stash BloomRecord + 4.5% target rate from TargetBloomRateBasisPoints=450)
P6 all v0.6 REQs (audit) Feature purity gate; G-003 (app-layer) + G-006 (go.mod unchanged) verification; milestone ship

Cross-Phase Blockers (hard) — v0.6

  • P1-01-01 (lexicon firewall lexicon_meta_web/) → blocks P1-02-03 + P1-04-02 (templates scanned by the firewall) and P2..P5 (content scanned).
  • P1-02-01 (web foundation main.go/server.go) → blocks P1-03-01 (store used by handlers) and all subsequent handler/template tasks.
  • P1-03-01 (mock store) → blocks P1-04-01 (Reach handler uses the store) and P2..P5 (each screen extends the store).
  • P1-04-01 (Reach signup handler) → blocks P2 (Stash dashboard needs a Stash created at signup — D-071), P4 (Standing is per-Reach).
  • P2-99-01 (P2 ship) → blocks P3 (Window scope references a Stash), P5 (BloomRecord is per-Stash).
  • P3-99-01 (P3 ship) → blocks P4 (soft ordering for branch hygiene; Standing is per-Reach, not per-Window, but the cumulative test suite + branch hygiene enforce serial phases).
  • P4-99-01 (P4 ship) → blocks P5 (soft ordering).
  • P5-99-01 (P5 ship) → blocks P6-01-01 (P6 audit).
  • All P(N) phase-ship tasks block P(N+1) Wave 1 tasks (soft ordering for branch hygiene).

v0.6 Decisions Applied (D-066..D-073)

The v0.6 Phase 0 clarify/ideate/research stages produced 8 clarification decisions (D-066..D-073), all applied to this plan:

ID Decision Applied to
D-066 Frontend stack = Go html/template + HTMX (vendored, no node); G-006 preserved P1-02-01, P1-02-02 (web foundation + HTMX vendored); Milestone Summary (G-006)
D-067 Mock data layer = Go HTTP server in web/ instantiating real x/*/types structs; no keeper, no Cosmos runtime, no app.go P1-02-01, P1-03-01 (mock store imports x/*/types); all handler tasks
D-068 UI code location = new top-level web/ dir (not an x/ module) P1-02-01, P1-03-01 (web/ dir); Milestone Summary
D-069 Lexicon firewall extension to web/ = new sibling lexicon_meta_web/ (mirrors lexicon_meta_docs/); firewall-first (P1 before content) P1-01-01 (firewall); all P2..P5 content scanned
D-070 G-003 boundary: web/ importing x/*/types is app-layer consumption, NOT a cross-x/ production import P1-03-01, P1-03-03 (import-invariant test); all store tasks; P6 audit
D-071 "Sign up" = create a Reach + open a Stash atomically; UI labels it "Create a Reach" ("account" is banned) P1-03-01 (atomic CreateReach), P1-04-01/02 ("Create a Reach" labels)
D-072 Phase ordering: P1 foundation+Reach+firewall → P2 Stash → P3 Window → P4 Standing → P5 Bloom → P6 final Cross-Phase Dependency Map; all phase goals
D-073 Bread-scale source of truth = x/bread/types code constants (GrainsPerBread=10000, BreadScaleAll()), NOT docs P2-02-01, P2-03-01 (Bread-scale conversion correctness test); P5-02-01 (Bloom target rate from TargetBloomRateBasisPoints=450)

User-Facing Surface

Web UI at http://localhost:8080 — runnable via go run ./web (single binary, no external deps, no node toolchain, no build step). The UI is a Go html/template server-rendered HTML layer with HTMX progressive enhancement (vendored web/static/htmx.min.js). Five screens, all reachable from the home page nav:

  1. Reach signup (GET /reach/newPOST /reach): the "Create a Reach" form. A visitor signs up to be a Nomad by creating a Reach (atomically creates a Stash per D-071). Reach list (GET /reach) + detail (GET /reach/{id}).
  2. Stash dashboard (GET /stash/{holderID}): balance in Grain + Bread-scale conversion (all 11 denominations from BreadScaleAll()) + 90-day maturity progress bar + IsMature badge.
  3. Window authorization (GET /window/newPOST /window; GET /window/{id}): open a Window (scope + duration + rate-limit), lifecycle transitions (Activate/Revoke/Expire via HTMX buttons), audit log.
  4. Standing + Freeholder signals (GET /standing/{reachID}): standing score + bucket + 4-signal progress (StashMaturity, MultiDomainStanding, CommittedCapital, CommunityEndorsement) + Freeholder-eligible badge.
  5. Bloom accrual (GET /bloom/{stashID}): per-Stash BloomRecord (AccruedGrain, RateBasisPoints as %, LastAccrualBlock) + 4.5% target rate band.

README quickstart: go run ./web → open http://localhost:8080. No make, no npm, no docker — just Go 1.22 stdlib + one vendored JS file. The README (updated in P1 or P6) documents the go run invocation + the 5 screen routes.


Happy Path

Scenario: a visitor signs up as a Nomad and exercises the full UI end-to-end (mock data, no chain, no real values).

  1. The visitor opens http://localhost:8080/ (home page) in a browser. The home page shows a one-paragraph OpenYield overview + nav links to the 5 screens.
  2. The visitor clicks "Create a Reach" → GET /reach/new renders the signup form (HolderID + PublicKey inputs; labeled "Create a Reach" — NOT "Sign up for an account").
  3. The visitor fills the form and submits (POST /reach). The handler calls store.CreateReach which atomically creates a Reach (IsNomad=true) + a Stash (matching HolderID, BalanceGrain seeded to 500000 Grain = 50 Bread per D-071). The browser redirects to GET /reach/{id} (Reach detail showing the Reach + the associated Stash).
  4. The visitor clicks the Stash link → GET /stash/{holderID} renders the Stash dashboard: balance 500000 Grain + the Bread-scale conversion table (50 Bread, 5 Loaves, 0.5 Batch, … from BreadScaleAll()) + a 90-day maturity progress bar (e.g., 45/90 days = 50% — the seeded StashActivity is immature) + an amber "Not Mature" badge (IsMature()==false because ActiveDays < 90).
  5. The visitor clicks "Window authorization" → GET /window/new renders the "Open a Window" form (scope dropdown: ReadStash/ReadStanding/ProcessPassActForStand; grantee; duration; rate-limit). The visitor opens a Window delegating ReadStash to a service → POST /window creates the Window (status=Open) + an initial AuditEntry → redirects to GET /window/{id}.
  6. On the Window detail page, the visitor clicks "Activate" (POST /window/{id}/activate via HTMX) → the Window transitions Open→Active (Window.Activate()); an AuditEntry is appended; the badge turns green. The visitor clicks "Revoke" → the Window transitions to Revoked (Window.Revoke()); the badge turns red; the audit log shows both actions.
  7. The visitor clicks "Standing" → GET /standing/{reachID} renders the Standing + Freeholder signals progress: a standing score (computed from mock Ratings using the locked constants) + a StandingBucket badge + 4-signal progress (StashMaturity: cross — the Stash is immature; MultiDomainStanding: check; CommittedCapital: check; CommunityEndorsement: cross — no Vouch) + a grey "Not Freeholder-eligible" badge (IsFreeholderEligible()==false because 2 signals are false).
  8. The visitor clicks "Bloom accrual" → GET /bloom/{stashID} renders the Bloom view: AccruedGrain (mock value) + RateBasisPoints 4.5% (from TargetBloomRateBasisPoints=450) + LastAccrualBlock + the target rate band (4.0%5.0%) + the Mission Lock note ("Bloom originates only from real production").
  9. End-to-end in-browser with mock data; no chain, no real values, no persistence (resets on restart). The whole flow took ~6 clicks across 5 screens.

UX Acceptance Criteria

The v0.6 deliverable MUST meet these explicit criteria (verified in P6 audit):

  1. go run ./web starts without external deps: go.mod is unchanged across the v0.6 milestone range (G-006 — zero new require lines; HTMX is a vendored static asset at web/static/htmx.min.js, NOT a go get); go run ./web starts a server on :8080 using only Go 1.22 stdlib (net/http.ServeMux + html/template).
  2. All 5 screens reachable from the home page: the home page nav (web/templates/base.html) links to Reach signup, Stash dashboard, Window authorization, Standing progress, Bloom accrual; each route returns 200 (verified by handler tests + manual browser check at http://localhost:8080).
  3. Lexicon firewall green on all UI strings: the lexicon_meta_web/ firewall (P1-01-01) scans web/templates/**/*.html + web/static/**/*.js + web/**/*.go for the 10 banned terms and passes; per-handler rendered-HTML lexicon checks (each phase's handler test) scan the HTTP response body and pass; "Sign up" is labeled "Create a Reach" ("account" is banned per REQ-012).
  4. Bread-scale conversion matches x/bread/types constants: the Stash dashboard conversion uses x/bread/types.BreadScaleAll() + GrainsPerBread=10000 (D-073 — code constants, NOT docs/shared/bread-scale.md); the Bread-scale conversion correctness test (P2-03-01) asserts the conversion matches the code constants and would FAIL if the outdated docs values (1,000× ratios) were used.
  5. Standing score computed from locked formula constants: the Standing screen score uses x/standing/types constants (PriorMean=4.0, PriorWeight=10, decay buckets, ComputeDiversityBonus, GetVoucherWeight, GetStandingBucket) — NOT hardcoded; the standing-score computed-from-locked-constants test (P4-03-01) asserts this.
  6. Freeholder-eligible badge reflects IsFreeholderEligible(): the Standing screen badge is green when FreeholderSignals.IsFreeholderEligible()==true and grey when false; the Freeholder-eligible badge test (P4-03-01) asserts the rendered badge matches the method output.
  7. Window lifecycle transitions match Window.Activate/Revoke/Expire: the Window screen lifecycle buttons call the real x/window/types.Window.Activate/Revoke/Expire methods (NOT a reimplementation); Revoke() on an Expired window is a no-op (v0.2 type contract); the lifecycle correctness test (P3-03-01) asserts the real methods are invoked.
  8. No banned terms in any rendered page: the per-handler rendered-HTML lexicon checks (P1-04-03, P2-03-01, P3-03-01, P4-03-01, P5-03-01) scan each screen's HTTP response body via lexicon.FindBannedTerm and pass; the lexicon_meta_web/ file-scan firewall passes on all web/**/*.{html,js,go} files.