ADR 0012: Federation State Origin Model
Status
Accepted (2026-03-31); amended (2026-04-26).
Amendment (2026-04-26): the Step 3 (CCL adoption contract) work this ADR scoped as "future, high complexity" was designed in ADR-0013, and Step 3d (the adoption proposal endpoint) has since landed. ADR-0013 retains an open-items section listing what remains unresolved (
FederationProvenancepersistence,coop_a_didplumbing, store-isolation tests). This ADR's Model C (Explicit Parallel) is unchanged; ADR-0013 is the current source of truth for the adoption path's status. Decision lifecycle isamended, notsuperseded: Model C still holds.
Context
ADR 0011 established the canonical truth invariant: no gateway-local state for supervisor-owned
domains. It identified that federation clearing position reads must go through the supervisor's
FederationService. It left open the question: how do the gateway API write paths, governance
execution write paths, and compute receipt paths relate — and what is the correct long-term model?
This ADR answers that question by tracing every federation write path from code, performing a per-concept convergence analysis, and defining the minimal correct architecture for the current phase with a precise roadmap for unification.
Phase 1: Federation State Origin Map
Gateway-Originated State
Owner: FederationManager (gateway-local)
Store: data_dir/federation_store (persistent sled, per ADR 0011 fix)
Provenance: None — no decision_receipt_id, no decision_hash, no settlement attribution
Guarantees: Durable across gateway restarts, isolated from supervisor state
| Endpoint | Object Created | Store Key |
|---|---|---|
POST /federation/init |
Own CooperativeInfo |
CooperativeRegistry |
POST /federation/coops |
Peer CooperativeInfo |
CooperativeRegistry |
POST /federation/connect |
Peer CooperativeInfo |
CooperativeRegistry |
POST /federation/coops/{id}/vouch |
Vouch |
CooperativeRegistry |
POST /federation/attestations |
FederatedTrustAttestation |
AttestationStore |
POST /federation/clearing |
BilateralClearingAgreement |
ClearingManager |
POST /federation/clearing/{id}/settle |
Settlement (gateway's ClearingManager) | ClearingManager |
POST /federation/clearing/settle-scheduled |
Multiple settlements | ClearingManager |
POST /federation/clearing/netting/{unit}/apply |
Position adjustments | ClearingManager |
Visibility through gateway reads:
GET /federation/coops*→ prefersFederationService(Step 1a); falls back toFederationManagerGET /federation/clearing→ prefersFederationService(Step 1b); falls back toFederationManagerGET /federation/clearing/{id}→ prefersFederationService(Step 1b); falls back toFederationManagerGET /federation/clearing/{id}/position→FederationServiceonly (ADR 0011) — shows supervisor state
Supervisor/Governance-Originated State
Owner: FederationServiceImpl (supervisor-owned)
Store: store_path/{federation,clearing} (persistent sled, supervisor-controlled)
Provenance: Full — carries decision_receipt_id, decision_hash, state_change_hash on every operation
Guarantees: Durable, audit-attributed, settlement-linked, governance-ratified
| Origin | Kernel Effect | Object Created | Store Path |
|---|---|---|---|
| CCL governance → kernel executor | FederationEffect::JoinFederation |
CooperativeInfo with provenance |
store_path/federation |
| CCL governance → kernel executor | FederationEffect::LeaveFederation |
Departure record | store_path/federation |
| CCL governance → kernel executor | FederationEffect::VouchForCoop |
Vouch with provenance |
store_path/federation |
| CCL governance → kernel executor | FederationEffect::EstablishClearing |
BilateralClearingAgreement with provenance |
store_path/clearing |
| CCL governance → kernel executor | FederationEffect::SettleClearing |
Settlement + ledger entry | store_path/clearing |
Execution chain:
CCL contract execution
→ KernelEffect::Federation(FederationEffect)
→ federation_effect_to_operation()
→ KernelFederationExecutor::execute_federation_operation()
→ FederationService::join_federation / vouch_for_cooperative / establish_clearing / settle_clearing
→ FederationServiceImpl (adapter)
→ CooperativeRegistry / ClearingManager at store_path/{federation,clearing}
Compute-Originated Clearing State
Owner: ReceiptClearingManager (supervisor-owned, fed by compute actor)
Store: store_path/clearing (same physical store as governance-originated clearing)
Provenance: Attestation hash from compute receipt
Guarantees: Durable, per-task attributed, flushed periodically
Compute task completion (cross-coop)
→ ComputeActor emits clearing receipt
→ receipt_clearing_handle queue
→ periodic flush task (spawn_clearing_receipt_flush_task)
→ ReceiptClearingManager::flush_to_clearing()
→ ClearingManager::record_transfer() at store_path/clearing
Note: Compute receipts and governance-established agreements share the same ClearingManager instance at store_path/clearing. A compute receipt for agreement X accumulates into the same position that governance's get_clearing_position("X") reads. This is the intended design.
Agreements API (Separate Plane)
/v1/agreements/... via AgreementManagerHandle manages inter-cooperative agreements with full
lifecycle (draft, propose, sign, suspend, resume, terminate). These are not bilateral clearing
agreements — they are structured documents (Trade, Credit, ResourceSharing, FederationMembership,
Custom). They do not overlap with /federation/clearing. Omitted from convergence analysis below.
Phase 2: Per-Concept Convergence Analysis
Cooperative Registry (coops, vouches)
| Attribute | Gateway Path | Governance Path |
|---|---|---|
| Type | CooperativeInfo, Vouch |
CooperativeInfo, Vouch (same types) |
| Store | data_dir/federation_store |
store_path/federation |
| Provenance | None | decision_receipt_id, decision_hash |
| Read API | GET /federation/coops |
Not exposed via gateway |
| Relationship | Transitional | Canonical |
Gap: Governance-registered cooperatives are invisible to GET /federation/coops. The read API
only queries the gateway's FederationManager. This is not a correctness bug — it is a read-plane
gap that will require FederationService read method expansion to close.
Verdict: Parallel for now. Gateway path is direct-management / exploratory / standalone tooling. Governance path is institutional / ratified. Neither replaces the other in the current phase.
Attestations
| Attribute | Gateway Path | Governance Path |
|---|---|---|
| Type | FederatedTrustAttestation |
No governance effect path |
| Store | data_dir/federation_store |
N/A |
| Provenance | None | N/A |
Verdict: Standalone — gateway is the only write path. No convergence needed.
Bilateral Clearing Agreements
| Attribute | Gateway Path | Governance Path |
|---|---|---|
| Type | BilateralClearingAgreement |
BilateralClearingAgreement (same type) |
| Store | data_dir/federation_store |
store_path/clearing |
| Provenance | None | decision_receipt_id, decision_hash |
| Position query | 404 in daemon mode (ADR 0011) | ✅ via FederationService::get_clearing_position |
| Read list | GET /federation/clearing (fallback only) |
✅ via FederationService::list_agreements (Step 1b) |
| Read by ID | GET /federation/clearing/{id} (fallback only) |
✅ via FederationService::get_agreement (Step 1b) |
| Relationship | Transitional | Canonical |
Gap (documented, acceptable): A clearing agreement created via POST /federation/clearing in
daemon mode will return 404 from GET /federation/clearing/{id}/position because position reads
go through the supervisor's service (ADR 0011) which only knows about governance-established
agreements. This is documented and expected: direct-API agreements are for standalone operation.
Verdict: Parallel for now. Same analysis as cooperative registry.
Clearing Positions
| Attribute | Value |
|---|---|
| Write sources | Governance (SettleClearing) + Compute (receipt flush) |
| Store | store_path/clearing — single ClearingManager instance |
| Read path | FederationService::get_clearing_position (supervisor) |
| Gateway fallback | FederationManager::get_position (standalone only) |
Verdict: Already correctly unified under the supervisor's store. Governance and compute
both write to the same ClearingManager. ADR 0011 fixed the read path. No further action needed.
Settlement
| Attribute | Gateway Path | Governance Path |
|---|---|---|
| Endpoint | POST /clearing/{id}/settle |
FederationEffect::SettleClearing |
| Target store | FederationManager's ClearingManager |
FederationServiceImpl's ClearingManager |
| Can settle | Only gateway-API-created agreements | Only governance-established agreements |
Gap: Each settlement path can only settle agreements it owns. Gateway settlement of a governance-established agreement will return "not found"; governance settlement of a gateway-API agreement is not possible (no governance path to the gateway's store).
Verdict: Parallel — acceptable given the transitional status of the two paths.
Netting
| Attribute | Gateway Path | Governance Path |
|---|---|---|
| Endpoint | POST /clearing/netting/{unit} and /apply |
No governance netting path |
| Scope | Gateway's ClearingManager only | N/A |
Verdict: Standalone — netting only operates on gateway-API-created positions. No convergence needed or possible in current phase.
Phase 3: Chosen Model
Decision: Model C (Explicit Parallel) with Targeted Promotion Path Design
The two paths serve different institutional roles:
| Dimension | Gateway API Path | Governance Path |
|---|---|---|
| Use case | Direct management, admin tooling, standalone ops, test setup | Democratic ratification, institutional decisions |
| Actor | API caller (admin) | Governance body (votes) |
| Provenance | None | Full audit trail (decision_receipt_id, decision_hash) |
| Guarantees | Durable state, isolated | Durable, attributed, verifiable |
| Appropriate for | Exploratory federation, direct bilateral agreements, standalone nodes | Production institutional federation, cross-coop credit, compliance-visible settlement |
Why not Option B (Submission/Proxy):
- Gateway writes like
POST /federation/clearingwould need to create a governance proposal, wait for a vote, and return the agreement ID from governance execution - This breaks the direct management use case entirely (no sync response possible)
- Governance quorum is inappropriate overhead for local administrative operations
Why not Option A (Promotion):
- Gateway-created objects can't be "proposed to governance" without a CCL contract that accepts
BilateralClearingAgreementas a proposal payload — this infrastructure doesn't exist - Even if it did, the promotion flow is async and requires governance participation from both parties
Why not Option D (Hybrid unification of clearing):
- The
FederationServicetrait currently has no read methods beyondget_clearing_position - Unifying read paths requires adding
list_agreements,list_coops,get_vouchesto the trait - This is non-trivial and out of scope for the current phase
Model C Rules (Explicit Parallel)
Gateway API path = direct management / standalone tooling. Objects created here are valid for standalone and direct-bilateral use. They are NOT institutional state.
Governance path = canonical institutional state. Production clearing agreements, cross-coop credit, and federation membership records that are compliance-visible MUST originate from governance execution.
No silent mixing. The API contract must be clear which path a caller is on. Currently:
POST /federation/clearing→ direct-management (no provenance)- CCL governance execution → institutional (full provenance) These are not interchangeable.
Read APIs reflect their write path.
GET /federation/coopsreturns gateway-local state in standalone mode, governance-registered coops in daemon mode (Step 1 implemented below).Position reads are already unified (ADR 0011). This is the original crossing point.
Phase 4: Precise Design Artifact — What Full Unification Requires
Phase 4a: Read Unification — Step 1 Implementation (2026-03-31)
Implemented: Cooperative Registry Read Surface
Added the following to FederationService (icn-kernel-api) and implemented in
FederationServiceImpl (icn-core):
fn list_cooperatives(&self) -> Result<Vec<CooperativeView>>;
fn get_cooperative(&self, coop_id: &str) -> Result<Option<CooperativeView>>;
fn get_vouches_for(&self, coop_id: &str) -> Result<Vec<String>>;
New view DTO in icn-kernel-api:
pub struct CooperativeView {
pub coop_id: String,
pub name: String,
pub public_did: String, // DID as string — no icn-identity dep in kernel-api
pub gateway_endpoints: Vec<String>,
pub capabilities: Vec<String>,
pub last_seen: u64,
pub origin: String, // "governance" | "direct-management" (added Step 2)
}
Gateway handlers updated to prefer service in daemon mode:
GET /federation/coops— prefersFederationService::list_cooperatives()GET /federation/coops/{coop_id}— prefersFederationService::get_cooperative()GET /federation/coops/{coop_id}/vouches— prefersFederationService::get_vouches_for()
In standalone mode (no service injected), all three fall back to FederationManager.
Response shape note: In daemon mode, these endpoints return CooperativeView (pruned DTO)
not CooperativeInfo (internal type). Fields omitted from the view: FederationPolicy,
CurrencyInfo, signature. These are federation-internal and should not cross the boundary.
Tests added (4 new in api/federation.rs):
test_list_coops_prefers_federation_service— proves service path taken over mgrtest_list_coops_falls_back_to_fed_mgr_when_no_service— proves fallback pathtest_get_coop_prefers_federation_service— proves get + 404 behaviortest_get_vouches_prefers_federation_service— proves vouch path
Phase 4b: Remaining Read Unification — What Full Unification Requires
The cooperative registry reads are now unified. The following remain gateway-only reads:
GET /federation/clearing → FederationManager only (no service equivalent)
GET /federation/clearing/{id} → FederationManager only
Prerequisite: ClearingAgreementView DTO
Blocking issue: BilateralClearingAgreement is in icn-federation (not icn-kernel-api).
The trait needs a ClearingAgreementView DTO similar to ClearingPositionView.
Required new service methods:
fn list_agreements(&self) -> Result<Vec<ClearingAgreementView>>;
fn get_agreement(&self, agreement_id: &str) -> Result<Option<ClearingAgreementView>>;
Prerequisite B: Origin Labeling
Before mixed-origin reads can be served from the gateway without confusion, objects need an origin tag:
pub enum FederationStateOrigin {
DirectManagement, // gateway API, no provenance
GovernanceRatified { // governance execution, full provenance
decision_receipt_id: String,
decision_hash: String,
},
ComputeReceipt { // compute task attribution
attestation_hash: String,
},
}
This allows the API to return both governance-ratified and direct-management coops/agreements in a single list with clear provenance markers.
Prerequisite C: Promotion Path (Optional, Long-Term)
If gateway-created objects should be promotable to governance-ratified state:
- A CCL contract type for "adopt_direct_agreement" would accept a
BilateralClearingAgreementpayload and, on approval, executeFederationEffect::EstablishClearingwith the same terms - The governance execution path would write to the supervisor's store with full provenance
- The direct-management record would be superseded by the governance-ratified record
This is a long-term path, not a current requirement.
Sequencing Plan
| Step | Prerequisite | Risk | Scope | Status |
|---|---|---|---|---|
1a. list/get_cooperative, get_vouches_for, CooperativeView DTO |
None | Low | icn-kernel-api + icn-core + icn-gateway |
✅ Done (2026-03-31) |
1b. list/get_agreement, ClearingAgreementView DTO |
None | Low | icn-kernel-api + icn-core + icn-gateway |
✅ Done (2026-04-01) |
| 2. Add origin labeling to response DTOs | Steps 1a+1b | Medium | Cross-crate | ✅ Done (2026-04-01) |
| 3a. Terms propagation fix + source reference fields | Steps 1-2 + store-isolation tests | Low | icn-kernel-api + icn-core |
✅ Done (2026-04-01) |
| 3b. Adoption provenance persistence (Sled) | Step 3a | Low | icn-core |
✅ Done (2026-04-01) |
3c. source_agreement_id in ClearingAgreementView |
Step 3b | Low | icn-kernel-api + icn-gateway |
✅ Done (2026-04-01) |
| 3d. Adoption proposal endpoint | Steps 3a-3c + governance plumbing | High | icn-gateway + governance |
⏳ Future (ADR 0013) |
Step 2 is the origin labeling pass — adds origin: "governance" | "direct-management" to view DTOs and gateway fallback paths. See Phase 4d below.
Step 3 (ADR 0013) is the adoption contract / lifecycle unification. Steps 3a-3c are independently implementable (terms fix + reference fields). Step 3d (adoption proposal endpoint) requires governance plumbing and is the final piece. See ADR 0013 for full design.
Phase 4c: Read Unification — Step 1b Implementation (2026-04-01)
Implemented: Clearing Agreement Read Surface
Added the following to FederationService (icn-kernel-api) and implemented in
FederationServiceImpl (icn-core):
fn list_agreements(&self) -> Result<Vec<ClearingAgreementView>>;
fn get_agreement(&self, agreement_id: &str) -> Result<Option<ClearingAgreementView>>;
New view DTO in icn-kernel-api:
pub struct ClearingAgreementView {
pub agreement_id: String,
pub coop_a: String,
pub coop_a_did: String, // Did as string — no icn-identity dep in kernel-api
pub coop_b: String,
pub coop_b_did: String,
pub settlement_interval: String, // "daily" | "weekly" | "monthly" | "manual"
pub max_imbalance: i64,
pub created_at: u64,
pub exchange_rates: std::collections::HashMap<String, f64>,
pub origin: String, // "governance" | "direct-management" (added Step 2)
}
Boundary notes:
Didmapped toString—icn-kernel-apihas noicn-identitydependencySettlementIntervalmapped to string — avoids cross-crate enum duplication, safe for future variantssignaturesfield omitted — raw cryptographic bytes have no API utility and must not cross boundaryexchange_rates: HashMap<String, f64>crosses freely (std primitives)Noneclearing manager:list_agreementsreturns empty vec,get_agreementreturnsOk(None)
Gateway handlers updated to prefer service in daemon mode:
GET /federation/clearing— prefersFederationService::list_agreements()GET /federation/clearing/{id}— prefersFederationService::get_agreement()
In standalone mode (no service injected), both fall back to FederationManager.
Tests added (3 new in api/federation.rs):
test_list_agreements_prefers_federation_service— proves service path taken over mgr, correct shapetest_get_agreement_prefers_federation_service— proves get + 404 behaviortest_list_agreements_falls_back_to_fed_mgr_when_no_service— proves fallback path fires
Phase 4d: Origin Labeling — Step 2 Implementation (2026-04-01)
Why origin labeling was needed after Step 1a/1b
After Steps 1a and 1b, the 5 federation GET endpoints all have two possible code paths:
- Service path — reads from the supervisor's store (governance-originated state)
- Fallback path — reads from
FederationManager(direct-management state)
Before this step:
- The
originwas entirely implicit — clients had no in-band signal indicating which path was taken - The fallback paths serialized raw
icn-federationdomain types (CooperativeInfo,BilateralClearingAgreement) with different JSON shapes than the service-path view DTOs — creating an undocumented shape inconsistency between daemon mode and standalone mode
Labeling model chosen: Option A (add origin field to view DTOs)
Considered:
- Option B (envelope
{ origin, data }): body-breaking change, requires unwrap by clients - Option C (response header): silently ignorable, not reflected in OpenAPI
- Option A (field on DTOs): consistent with existing DTO pattern, visible in body, type-safe
Chose Option A. The origin field is a stable string at the CooperativeView and
ClearingAgreementView boundaries. It is not a full provenance record — that requires
decision_receipt_id / decision_hash, which belong in a future promotion-path design.
DTOs changed
CooperativeView in icn-kernel-api:
- Added
pub origin: String—"governance"or"direct-management"
ClearingAgreementView in icn-kernel-api:
- Added
pub origin: String—"governance"or"direct-management"
GET /federation/coops/{id}/vouches response (raw JSON object):
- Added
"origin"key directly — not a typed DTO
Handler changes
Service path (daemon mode): origin = "governance" — set in FederationServiceImpl mappers
in icn-core
Fallback path (standalone mode): origin = "direct-management" — set in gateway handler
adapters. Important: the fallback paths now also adapt the raw icn-federation types
(CooperativeInfo, BilateralClearingAgreement) to the view DTOs. This:
- Unifies the response shape regardless of which path was taken
- Eliminates the hidden shape inconsistency between daemon and standalone mode
- Ensures
signatures,FederationPolicy,CurrencyInfofields never reach API consumers
ClearingPositionView was NOT labeled — this endpoint only has one meaningful path
(FederationService) and the fallback already builds the same DTO from gateway state.
The handler comments explain the distinction.
Tests added (5 new)
test_list_coops_origin_governance_when_service_presenttest_get_coop_origin_governance_when_service_presenttest_get_vouches_origin_governance_when_service_presenttest_list_agreements_origin_governance_when_service_presenttest_get_agreement_origin_governance_when_service_present
Each asserts body["origin"] == "governance" when the stub service is injected.
Fallback path (direct-management) origin is covered implicitly by the existing fallback tests
(test_list_coops_falls_back_to_fed_mgr_when_no_service, etc.) which now produce labeled
ClearingAgreementView / CooperativeView responses.
What the next slice should be
Steps 3a/3b/3c (terms propagation, adoption provenance persistence, read surface exposure) are complete as of 2026-04-01. See Phase 4e below for details.
Step 3d (CCL adoption proposal endpoint: POST /v1/federation/clearing/{id}/propose-adoption) is
the remaining item for the Step 3 adoption contract sequence. It requires wiring the gateway
endpoint that accepts a direct-management agreement ID and emits an EstablishClearing effect with
the stored terms and source_agreement_id set. The governance execution path (Step 3b) is already
wired to carry these fields through to BilateralClearingAgreement.
Before Step 3d: consider whether origin labeling should be extended to include
decision_receipt_id and decision_hash on "governance" records — currently these fields are
available in FederationProvenance (stored in FederationServiceImpl) but not exposed in the
view DTOs. Exposing them would allow clients to verify governance provenance directly from the API.
Phase 4e: Adoption Contract Foundation — Steps 3a/3b/3c (2026-04-01)
What was implemented
Steps 3a, 3b, and 3c close the terms-carry-through gap and establish adoption provenance persistence across the governance execution path.
Step 3a — Terms propagation fix
Extended the full effect chain to carry agreement terms from proposal through to the stored
BilateralClearingAgreement. Four types updated:
FederationEffect::EstablishClearing(inicn-kernel-api::effects): addedsettlement_interval: Option<String>,max_imbalance: Option<i64>,source_agreement_id: Option<String>— all with#[serde(default)]for backward compatFederationOperation(inicn-kernel-api::governance): same three fields added with#[serde(default, skip_serializing_if = "Option::is_none")]federation_effect_to_operation():EstablishClearingarm updated to carry all three fieldsFederationClearingRequest(inicn-kernel-api::services): same three fields added
establish_clearing() in icn-core::services::federation_service updated to apply the provided
settlement_interval and max_imbalance instead of relying on BilateralClearingAgreement::new()
defaults (which were hardcoded to Weekly / 10000).
Step 3b — Adoption provenance persistence
BilateralClearingAgreement (in icn-federation::clearing) extended with
source_agreement_id: Option<String> using #[serde(default, skip_serializing_if = "Option::is_none")]
for Sled backward compat. The field is populated in establish_clearing() from
request.source_agreement_id before ClearingManager::create_agreement() persists it.
governance_executor.rs updated to forward operation.source_agreement_id into the
FederationClearingRequest it constructs, completing the governance → persistence chain.
Step 3c — Read surface exposure
ClearingAgreementView (in icn-kernel-api::services) extended with
source_agreement_id: Option<String>. Both list_agreements() and get_agreement() in
federation_service.rs now populate this field from the stored BilateralClearingAgreement.
Gateway direct-management handlers in icn-gateway::api::federation set source_agreement_id: None
(direct-management agreements are not adopted from anywhere).
Deviation from ADR 0013 design
ADR 0013 specified exchange_rates: HashMap<String, f64> as a term to carry through the effect
chain. This field was deferred: FederationEffect derives PartialEq + Eq, and HashMap<String, f64> cannot satisfy Eq because f64 does not implement Eq. Only settlement_interval,
max_imbalance, and source_agreement_id were added.
Resolution path when needed: wrap exchange rates in a newtype that implements Eq by approximate
comparison, or relax the Eq derive on FederationEffect variants that don't need it.
Tests added (5 new in icn-core)
test_establish_clearing_terms_propagate— verifiessettlement_intervalandmax_imbalancefromFederationClearingRequestare applied to the storedBilateralClearingAgreementtest_establish_clearing_backward_compat_no_terms— verifies a request without terms fields creates an agreement with defaultWeekly/10000(backward compat)test_source_agreement_id_persisted_via_clearing_manager— verifiessource_agreement_idround- trips throughClearingManager::create_agreement()and is returned byget_agreement()test_establish_clearing_creates_fresh_positions— verifies governance-path agreement starts with zero credit balances regardless of source provenancetest_list_agreements_returns_source_agreement_id— verifieslist_agreements()exposessource_agreement_idin the returnedClearingAgreementView
Phase 5: Tests and Documentation
Invariant Tests
The following invariants are already tested or should be added:
Existing (from ADR 0011 work):
test_get_position_prefers_federation_service_over_local_manager— proves ADR 0011 read preference is enforced for position queriestest_persistent_storage_survives_manager_reconstruction— proves gateway-API state is durable
Required (now confirmed present in icn-gateway/src/federation_mgr.rs):
test_gateway_clearing_and_governance_clearing_do_not_share_positions: exists and passes. Proves gateway and supervisor Sled stores are isolated — creating an agreement via one path does not appear in the other's position reads.test_gateway_coop_list_reflects_only_gateway_registered_coops: exists and passes. ProvesFederationManager::list_cooperativesreflects only direct-management registrations, not governance-ratified ones.
Documentation Updates
This ADR serves as the design artifact. Gateway API documentation should note:
- "Objects created via direct management APIs are for standalone and direct-bilateral use. Production institutional federation should originate from CCL governance execution."
GET /federation/coopsand related list endpoints return only direct-management state, not governance-ratified state.GET /federation/clearing/{id}/positionreturns supervisor-owned state (governance + compute) and will 404 for agreements created via direct management API in daemon mode.
Consequences
What This ADR Locks In
- Model C is the current architecture: two parallel paths, intentionally separate.
- Gateway read APIs reflect their write path — this is correct behavior, documented as such.
- No state copying between paths — divergence is the intended design.
- Read unification requires FederationService expansion — do not attempt partial unification before Steps 1-3 above are complete.
- Position reads (ADR 0011) are the only crossing point — do not add more crossing points without following the full wiring chain from ADR 0011.
What This ADR Leaves Open
- Step 3d:
POST /v1/federation/clearing/{id}/propose-adoptionendpoint — emitsEstablishClearingeffect with stored terms +source_agreement_idfrom an existing direct-management agreement. The governance execution and Sled persistence are already wired (Steps 3a/3b/3c); only the gateway intake endpoint remains. - Provenance field exposure on governance-origin reads:
decision_receipt_id/decision_hashare stored inFederationProvenancebut not yet exposed in view DTOs. Clients cannot verify governance origin directly from the API without this. Low-complexity follow-up. exchange_rates: HashMap<String, f64>carry-through: deferred becauseFederationEffectderivesEqandf64does not implementEq. Exchange rate terms do not flow through the effect chain today. When needed: either wrap in a newtype that implementsEq, or relax theEqderive onFederationEffect.
Steps 1a, 1b (FederationService read expansion), Step 2 (origin labeling), and Steps 3a/3b/3c (terms propagation, adoption provenance persistence, read surface exposure) are complete as of 2026-04-01. See Phase 4a, 4c, 4d, 4e below for details.
References
- ADR 0011: Canonical Truth Ownership — Gateway vs Supervisor
crates/icn-kernel-api/src/services.rs— FederationService trait (current + needed methods)crates/icn-kernel-api/src/effects.rs— FederationEffect enum (5 governance effect variants)crates/icn-core/src/supervisor/governance_executor.rs— KernelFederationExecutor, effect→operation bridgecrates/icn-core/src/services/federation_service.rs— FederationServiceImpl adaptercrates/icn-core/src/supervisor/init_federation.rs— ReceiptClearingManager setupcrates/icn-gateway/src/api/federation.rs— All gateway write endpointscrates/icn-gateway/src/federation_mgr.rs— FederationManager (gateway's state layer)