Proposal 076: Federation Identity and Network Selector¶
Based on:
doc/normative/40-constitution/CONSTITUTION.mddoc/normative/50-constitutional-ops/FEDERATION-MEMBERSHIP-AND-QUORUM.mddoc/project/40-proposals/032-key-delegation-passports.mddoc/project/40-proposals/054-user-maintained-federated-seed-directory.mddoc/project/40-proposals/069-corpus.mddoc/project/40-proposals/070-room-primitive.mddoc/project/40-proposals/074-multi-node-federation-harness-and-trace-explorer.mddoc/project/40-proposals/075-matrix-homeserver-runtime-profile.mddoc/project/60-solutions/021-agora-authority/021-agora-authority.mddoc/project/60-solutions/031-seed-directory/031-seed-directory.mddoc/project/60-solutions/036-room/036-room.md
Promoted to:
doc/project/60-solutions/041-federation-root/041-federation-root.md
Status¶
promoted
Date¶
2026-07-01
Executive Summary¶
Orbiplex already uses federation_id as a data field (Seed Directory trust
entries, Corpus taxonomy artifacts) and already has a Room exposure ladder that
assumes federations are countable, distinguishable things (federation-local
vs cross-federation/global). None of that is grounded in one concrete answer
to "what, operationally, is a federation."
This proposal promotes federation_id from descriptive metadata on individual
trust-registry entries to a node-wide network selector, in the sense that
mainnet/testnet are network selectors in existing distributed-ledger
systems: a value resolved once, before the node does anything else, that
determines which root trust and which default services apply.
The central rule is:
A federation is a separate operational identity: separate data-dir, separate
root trust, separate default services. Cooperation across federations is a
property of what participants explicitly do together (a room, a query, a
gossip promotion) — never an automatic consequence of running the same
software.
Unconfigured nodes default to orbiplex-main, the reference public network. An
operator who wants full structural isolation (for example, a company or a
closed group of friends whose top-level attestation keys must never mix with
the public network) configures a distinct federation_id, backed by its own
data-dir and its own root trust file. This proposal deliberately narrows
"federation" to that operational sense, and leaves broader cross-federation
cooperation to the separate, lighter alliance concept.
This root trust file is also the boundary between Orbiplex authority and
ordinary transport compatibility. Public WebPKI can protect an HTTPS/WSS
connection to a public endpoint, but it is not the source of federation
authority. A node accepts Seed Directory authority, top-level attestation roots,
service roots, and bootstrap peers because they are present in or derived from
the signed federation-root.v1 pack and local policy, not merely because an
endpoint presents a certificate chaining to a public browser CA. A private or
self-signed federation root is valid when it is explicit, scoped to the
federation, signed/accepted, and auditable.
Context and Problem Statement¶
federation_id already appears in the documentation and in code, in two
places, neither of which defines it as a node identity:
seed-directory-trust.v1(Proposal 054) carriesfederation_idas an optional field on individual trusted-directory entries — descriptive metadata about which federation a given directory claims to serve, not a property of the node consulting that registry.daemon/src/config.rsimplements exactly that:federation_idlives onDaemonSeedDirectoryTrustConfig, one field among several on one trust-registry row.- Corpus taxonomy artifacts (Proposal 069) carry a
federation/idfield (distinct from that same artifact's owntaxonomy/id, for example"taxonomy/id": "orbiplex.core"alongside"federation/id": "orbiplex") identifying which federation owns/signs a taxonomy, again as a property of one artifact, not of the node.
Meanwhile, Room (Proposal 070 / Solution 036) already has a policy axis that
presumes federations are first-class, countable things: room TTL policy
distinguishes federation-local rooms from cross-federation/global rooms.
That distinction is meaningless without a concrete answer to "which federation
is this room, or this node, actually in."
The practical trigger for this proposal was a Matrix homeserver profile discussion (Proposal 075): without a clear definition, "federation" risked quietly becoming "whatever Matrix's own homeserver-to-homeserver federation protocol provides" — importing a transport's trust model into a concept that must remain transport-independent (see Proposal 075's Authority Boundary rule, which this proposal extends one level up: a carrier must not become an authority, and neither should a specific federation's infrastructure become the definition of federation for everyone else).
A second, independent trigger is operational: an organization (a company, a closed group) wants to run Orbiplex entirely on its own root of trust, with a structural guarantee — not a configuration convention — that its data can never mix with the public default network.
Goals¶
- Define
federation_idas a node-wide, pre-runtime network selector, analogous to a chain id (mainnet/testnet/custom) in existing distributed systems. - Default to
orbiplex-mainwhen unconfigured, so a zero-config node safely joins the reference public network. - Bind federation identity, root trust keys, and Seed Directory bootstrap state
to one
data-dir-scoped root-config file, merged into runtime config at startup. - Guarantee structural (filesystem-level) non-leakage between differently
configured federations — no shared mutable state across two
data-dirs. - Preserve cross-federation cooperation as an explicit, higher-layer concept realized by Room/Whisper/Corpus, not eliminate it or fold it back into node identity.
- Reuse existing primitives — Agora Authority's organization root and custody
modes,
key-delegation.v1, Seed Directory's trust-tier model — rather than inventing a parallel trust mechanism.
Non-Goals¶
- Not a redefinition of Room's existing
federation-local/cross-federation/globalexposure vocabulary. Those terms are correct; this proposal gives them a concrete node-level referent. - Not a full specification of the lighter, cross-cutting cooperation concept
named
alliancein this proposal.Allianceis the vocabulary for explicit cross-federation cooperation; its minimal policy contract is frozen in Proposal 079 (alliance-policy.v1) and runtime enforcement is tracked there. - Not a reputation or ranking system for federations.
- Not a requirement that every deployment explicitly configure a federation —
the unconfigured
orbiplex-maindefault path must stay safe and zero-config. - Not a global registry or arbitration authority for
federation_idstrings; identifiers remain self-declared, consistent with Seed Directory's own anti-monopoly stance ("must not become one global source of truth"). - Not a live migration or merge tool for moving a running node between federations.
- Not the governance charter for the
orbiplex-mainroot itself. This proposal selects an org-kind threshold root as the required shape, but concrete custodian selection, rotation ceremony, appeal path, and production signer roster remain governance artifacts rather than schema mechanics.
Proposed Model¶
1. Federation as a Network Selector¶
federation_id becomes one node-wide configuration value, resolved before any
other subsystem starts:
- Default:
orbiplex-main— the reference public network, inferred when no federation is explicitly configured. - Alternative: any operator-chosen string (for example
acme-org), resolved only from an explicit root-config file (section 3) — never inferred, never guessed from environment. - A
federation_idis self-declared data, not a globally arbitrated registry entry. Two unrelated operators may pick the same string; implementations distinguish them by explicit local pinning of root-pack digest and root keys, not by first-seen string ownership.
2. data-dir as the Isolation Boundary¶
A running node instance is bound to exactly one federation for the full
lifetime of that data-dir.
- Changing federation means pointing at a different
--data-dir, not a runtime toggle or a config hot-reload. - No code path may share mutable state — identity keys, Room projections, Agora
authority cache, Seed Directory trust registry, capability passports — across
two
data-dirs. - This is not a new isolation primitive. One-daemon-one-
data-diralready implies this; this proposal makes federation scope explicitly co-located with it, so isolation is a structural property of "which directory did you point at," not an accident of careful configuration.
Operating in more than one federation is switching, never concurrent use.
An operator who genuinely needs both — for example, participating in the
public orbiplex-main network and running a closed corporate federation —
runs two entirely separate data-dirs, each with its own root config, its own
peer_discovery.seeds[] / network.seed_directory[] /
network.seed_directory_trust[] / identity.sovereign_participant_ids[], and
its own local storage. The
operator switches which one is active by choosing which data-dir to start the
daemon against. There is no mode where one running node instance is
simultaneously a member of two federations; that would reopen exactly the
leakage risk this proposal exists to close.
3. Root Config File (Federation Pack, Local Form)¶
A data-dir-scoped file, merged into runtime configuration at startup the same
way other config overlays already are (for example the model-runtime catalog
override pattern), carries the fields below. This is now a real, checkable
artifact rather than a pseudo-block:
doc/schemas/federation-root.v1.schema.json,
with a positive example at
doc/schemas/examples/orbiplex-main.federation-root.json
and negative fixtures under
doc/schemas/examples/invalid/
(org-without-custody.federation-root.json, unsigned.federation-root.json,
unknown-field.federation-root.json). Field names are snake_case throughout,
deliberately matching seed-directory-trust.v1 rather than the noun/attribute
convention of signed inter-node wire messages (node-advertisement.v1,
Corpus taxonomy records) — see the schema's own top-level description and
the resolved field-naming decision below. Shape, in outline:
federation-root.v1 {
schema # const "federation-root.v1"
federation_id
pack_version # monotonic; a loader rejects a lower pack_version
# than the last one accepted for this federation_id
issued_at?
attestation_roots[] # this federation's OWN canonical top-level anchor(s).
# kind: participant — { id (participant:did:key),
# purposes[]? (informational only, see below),
# valid_from?, valid_until? }, feeding
# identity.sovereign_participant_ids[].
# kind: org — adds required custody_mode
# (any-authorized | threshold) and custody_policy_ref.
# Loaded through identity.sovereign_subject_refs[]
# (P076-011), not silently projected into the
# participant-only compatibility list. NOT a copy of
# any relay's Agora authority_roots[].
custody_policies[] # optional, self-contained org custody rules referenced
# by attestation_roots[].custody_policy_ref. MVP
# runtime evaluates only purpose=federation-root,
# exactly one rule per policy,
# mode=any-authorized|threshold, and authorized
# participant/key signer sets.
bootstrap_seed_peers[] # this federation's static WSS seed peers (Proposal 014's
# "mandatory first bootstrap layer"), matching
# DaemonSeedPeerConfig field-for-field (one endpoint_url
# per entry, no priority/enabled — richer multi-endpoint
# peers would need that struct enriched first)
seed_directory_bootstrap[]
# this federation's own canonical default trusted
# directories; each entry fans out to BOTH
# network.seed_directory[] and
# network.seed_directory_trust[] at load time — the
# latter's federation_id is filled from this pack's
# own federation_id, not repeated per entry.
# Each entry MUST carry explicit enabled=true|false;
# there is no implicit admission default.
# Optional inline endorsement? is the sole
# bootstrap official-status proof; endorsement_refs[]
# remain metadata only.
policy_ref?
endorsement_refs[]?
signatures[] # required; "self-signed by the pack's own root" is
# valid, "unsigned" is not
}
purposes[] on an attestation_roots[] entry mirrors
AgoraAuthorityRootConfig.purposes. The generalized
identity.sovereign_subject_refs[] surface preserves this metadata; the older
identity.sovereign_participant_ids[] remains only a participant-id compatibility
projection and intentionally cannot carry per-root purpose metadata. Per-root
valid_from/valid_until live on each attestation_roots[] entry, not the whole
pack, mirroring AgoraAuthorityRootConfig exactly — different roots in one pack
may rotate on independent schedules. assurance and namespaces[] are
deliberately NOT part of attestation_roots[]: those are Agora Authority's own
local, per-relay, per-namespace policy vocabulary (Solution 021); a federation
root becomes Agora's default root for its reserved namespace (see below), and
Agora assigns its own assurance/namespace values when adopting it, rather than
this federation-wide pack carrying Agora-specific fields.
This is the concrete, local shape of the "federation pack" already named (but not specified) in Proposal 054. This proposal defines its file shape and load-time binding; it does not introduce a new kind of trust artifact.
Loading this file resolves, and where present overrides, exactly four existing
daemon configuration surfaces: peer_discovery.seeds[] (static WSS bootstrap
peers, Proposal 014), network.seed_directory[] and network.seed_directory_trust[]
(Seed Directory query targets and trust tiers, Proposal 054), and
identity.sovereign_participant_ids[] (the root/sovereign issuer list consumed
by capability-passport-chain verification, Seed Directory admission, and
Artifact Delivery bindings). Nothing else in daemon configuration is federation
material; a federation-root file that tries to carry any other field is
rejected, not silently ignored.
Runtime surface. identity.sovereign_subject_refs[] is the canonical runtime
projection for federation attestation roots. It accepts both participant and
org roots with optional custody metadata, while
identity.sovereign_participant_ids[] remains the narrower participant-only
compatibility projection. The loader validates this shape at startup; an
org-kind root without custody_mode/custody_policy_ref, or a participant root
with custody metadata, is rejected rather than silently coerced.
The projection is a federation authority set, not a claim that every node owns every participant root key. Local readiness and local signing intersect the participant-root projection with signing keys actually present in that node's data directory. At least one local intersection is required when participant roots are configured; absence fails closed. Remote root keys remain verification authority only and must never be copied to a node merely to satisfy readiness.
For production-trust packs, runtime verification now goes beyond schema shape:
the daemon canonicalizes the payload excluding signatures[], verifies Ed25519
signatures, requires participant roots to be self-signed by the corresponding
participant key, and evaluates org roots against self-contained
custody_policies[] rules for purpose = federation-root. The schema catches
local shape errors such as kind/id prefix mismatch and multiple custody rules
per policy; parity between attestation_roots[].custody_mode and the referenced
policy rule mode remains a runtime invariant because portable JSON Schema cannot
express that cross-array reference cleanly. Runtime threshold evaluation counts
unique authorized signing keys, not real-world people, orgs, or custody seats;
governance process must ensure that the signer roster maps to actually separate
custodians.
The bundled orbiplex-main pack is a dev-bootstrap hard-MVP fixture gated by
federation.allow_bundled_fixture_root. The daemon default is fail-closed
(false): a node without an explicit data-dir federation-root.v1.json refuses
to start. Enabling the bundled fixture is an explicit local operator choice for
development/demo bootstrap only, and the fixture bypass is pinned to the raw pack
digest so a same-shape but byte-different bundled payload cannot inherit the
bypass. Explicit data-dir packs always require real signatures.
attestation_roots[] is not the same list as Agora Authority's own
authority_roots[] (Solution 021, AgoraAuthorityRootConfig). Agora Authority's
roots are deliberately narrower and more local: each entry is scoped to specific
Agora topic-namespace prefixes (namespaces[] is required and non-empty), and
Solution 021 is explicit that this configuration is local relay policy, not a
public federation contract that other relays must honor. This proposal's
attestation_roots[] answers a different, more foundational question — "what is
this federation's own top-level anchor" — and is meant to be canonical for every
node that joins the federation (via distribution defaults or a corporate root
file), not a per-relay variant.
The relationship is layering, not replacement: a federation's root naturally
becomes the default entry Agora Authority (and Seed Directory's own sovereign-
issuer policy, per Proposal 054 section 4) recognizes for the federation's own
reserved namespace, using the same {id, kind, custody mode} shape. Agora
Authority remains free to configure additional, narrower, purpose-scoped
authority_roots[] entries locally — for specific sub-namespaces, moderation
records, or delegated purposes — layered on top of, never in place of, the
federation root.
Absent file: the node runs as orbiplex-main under distribution defaults
(section 4) — the distributed software package itself populates these four
surfaces for orbiplex-main, so a zero-config node is not merely "safe", it is
actually able to discover peers and directories out of the box. Present file:
it is authoritative for that data-dir and replaces, not merges with, the
distribution defaults for these four surfaces — this is precisely how a closed
deployment (for example a corporate installation) locally overrides the public
orbiplex-main values with its own. A parse, signature, or custody-threshold
failure fails the node closed rather than falling back to orbiplex-main
defaults — falling back silently would quietly weaken exactly the isolation
guarantee an operator configured this file to get.
4. Distribution Defaults (orbiplex-main)¶
Current state, verified in code. Today all four surfaces default to empty
or inert: peer_discovery.seeds (DaemonPeerDiscoveryConfig::default()),
network.seed_directory, and network.seed_directory_trust (both
DaemonNetworkConfig::default()) all default to Vec::new()
(daemon/src/config.rs), and
identity.sovereign_participant_ids ships exactly one hardcoded placeholder
(daemon/src/config.rs:341) whose matching private key does not appear
anywhere else in the codebase — it functions as schema filler, not a working
root of trust. A fresh, unconfigured node cannot discover any peer or directory
today. This proposal changes that default from "safe but inert" to "safe and
actually able to join orbiplex-main."
The reference public federation ships as a signed default pack bundled with the distributed software:
- Only the verification key(s) for this default pack are embedded/pinned in distributed code.
- Pack content (bootstrap directory list, thresholds, endorsements) can be
updated by shipping a newer signed pack without re-baking a new binary for
routine changes, using
key-delegation.v1so the cold root key is rarely touched. - Root custody for
orbiplex-mainSHOULD bethreshold, notany-authorized, per the high-stakes-mechanism guardrails inDEV-GUIDELINES.md(multisig/threshold review, appeal path, audit trace). The selected shape is an org-kind threshold root, with2-of-3as the required minimum unless the governance charter chooses a stricter threshold. The production ceremony shape is frozen at this level: threshold root pack digest, detached multi-signature collection, deterministic assembly, verification before publication, rotation overlap, and appeal path reference.DIA-ROOT-001is adopted as theorbiplex-mainroot charter at version0.1.0; concrete custodian names, concrete signing keys, and the signed production pack remain activation deliverables and block treatingorbiplex-mainas production-trustworthy until documented and published. - Federation-root custodian identities SHOULD be dedicated operational
identities used only for root-pack approval, rotation, and recovery
ceremonies. They SHOULD live in separate custodian
data-dirs rather than in the samedata-diras the same person's ordinary participant/node activity. The reason is structural privacy and audit separation:signatures[].key_publicand custody-policy authorized signer sets are public, stable correlation handles. Reusing a day-to-day participant identity would link root governance participation with unrelated Orbiplex activity by construction. Dedicated custodian identities remain accountable without making ordinary activity correlateable through the federation root.
5. Federation vs. Group¶
"Federation" in this proposal is deliberately narrow: an operational,
data-dir-scoped identity, one per running node instance.
Cross-federation cooperation — a Room whose membership spans participants
configured under different federation_ids, a Whisper public-gossip
promotion, a Corpus deliberation that solicits bids beyond one federation — is
a property of what participants deliberately do together, realized at the
Room/Whisper/Corpus layer. It is never a silent consequence of a node
belonging to two federations at once, because under this model it cannot: a
node has exactly one federation_id.
This grounds, without changing, Solution 036's existing Room exposure tiers:
federation-local— bounded to participants who share the acting node's configuredfederation_id.cross-federation/global— a room whose membership is not bounded that way.
The lighter, cross-cutting concept is named alliance. An alliance is an
explicit cooperation relationship across federation boundaries, distinct from
both "federation" (this proposal's data-dir-scoped network selector) and Seed
Directory's already-used "community" trust tier. Proposal 079 defines the
minimal alliance-policy.v1 contract: unilateral policy halves, stable member
subject pins rather than root-pack digest pins, scope intersection, explicit
denials, local-only publication, and the invariant that an alliance is only an
input to local admission, never a substitute for artifact or service-authority
verification.
6. Sovereign Operator Identity and Official-Service Endorsement¶
Infrastructure capability profiles (network-ledger, seed-directory,
offer-catalog) carried a sovereign operator notion that was previously
deployment-local and vague (Proposal 025 §2/§4/§6). This proposal grounds that
notion in the federation root, and moves the federation vouch off the
capability-passport.v1 (which stays a capability scope artifact) onto a
dedicated endorsement artifact:
Sovereign operator. A sovereign operator of federation
Fis an identity present in the activefederation-root.v1identity.sovereign_subject_refs[]forF(equivalently: one ofF'sattestation_roots[]). It is defined by the sovereign subject, not by a raw signing key.
The subject-not-key distinction is what keeps threshold custody intact:
- a participant sovereign subject can vouch with its single key;
- an org sovereign subject inherits its
custody_mode/custody_policy_ref, so vouching "as the org" MUST satisfy the samefederation-rootcustody threshold. A single custodian key is a fraction of org authority, never a sovereign operator on its own (consistent with Open Questions §Resolved 2: threshold is M-of-N unique keys, not M-of-N people).
Custodian keys may attest or authorize a separate operator identity through an
explicit binding or endorsement artifact, but they MUST NOT be converted,
promoted, or derived into a full operator identity. Operator identities remain
separately generated, separately revocable subjects. If tooling is added for
this boundary, prefer explicit flows such as custodian-identity-init,
custodian-key-import, custodian-key-rotate, and
custodian-attest-operator; do not hide the transition inside root ceremony
signing.
An official service is one a federation vouches for cryptographically, not only by address. Reachability and endorsement stay stratified (Proposal 014: address evidence is not capability authorization):
- the address stays a rotatable pointer (
seed_directory_bootstrap[]here, ornode-address-attestation.v1); - the endorsement is a single
federation-service-endorsement.v1artifact (below), signed by the federation's sovereign(s). It is deliberately not the service's operationalcapability-passport.v1: the passport carries capability scope, the endorsement carries the federation vouch. A single-issuercapability-passport.v1never confers official status on its own.
The endorsement carries a signatures[] array; the sovereign subject kind only
sets how many co-signers are required — participant = 1, org = its custody
threshold (see below).
Because signers are checked against the active pack, key rotation is free
revocation: when a new pack_version drops a subject, an endorsement whose
signer set no longer meets the requirement silently lapses. Verifiers therefore
MUST re-check signer membership on every use and MUST NOT cache an endorsement
decision across pack updates.
An official-service pointer in federation-root.v1 (a seed_directory_bootstrap[]
entry, or an offer-catalog recommended by a seed directory) that is labelled
or consumed as official MUST have a resolvable sovereign endorsement. If the
endorsement is missing or invalid, official loading/official use fails closed.
Community/address-only pointers MAY still load as advisory unendorsed pointers,
but they MUST NOT inherit official status by address, naming, or placement in a
bootstrap list. The endorsement contract and verifier rule are defined below.
For seed_directory_bootstrap[], "resolvable" means the entry carries an
optional inline endorsement artifact that verifies against the active
federation-root.v1 at load time. endorsement_refs[] MAY remain as
audit/discovery metadata, but it is not sufficient for bootstrap official
status because a reference cannot be resolved before the referenced directory is
trusted.
Sovereign subjects are governance-authored, not signature-derived¶
Membership of identity.sovereign_subject_refs[] is authored in the pack's
attestation_roots[] and only then signed; it is not derived from who signed.
The loader enforces the reverse constraint (every signature must tie back to an
attestation root or its custody policy — no stranger signatures), but signing a
pack never by itself confers standing sovereign-operator authority. A custodian
of an org root is a fraction of that org's authority, not an independent
sovereign subject, unless the pack also lists that identity as its own
participant-kind root. This keeps the sovereign-operator roster a deliberate
governance decision, immune to "signer creep".
One endorsement shape serves both subject kinds: the same
federation-service-endorsement.v1 artifact, with a signatures[] array of
length 1..M. Subject kind only sets the required signer count:
- participant subject → one signature (
M = 1) — not a different artifact, just the single-signer case; - org subject → threshold co-signatures (
signatures[]meeting the org'sfederation-rootcustody policy); no single custodian can vouch officially.
federation-service-endorsement.v1 (the single endorsement artifact)¶
A thin artifact — the signatures[] shape of federation-root.v1, a distinct
domain — by which a federation's sovereign(s) vouch for an official service. One
artifact serves both subject kinds: signatures[] of length 1 for a participant
subject, M-of-N for an org subject. It stays separate from the service's
operational capability-passport.v1: the passport carries scope, the
endorsement carries the federation's vouch.
Minimal contract sketch:
- binds
federation_id, servicenode_id,capability_id(seed-directory|offer-catalog| ...),issued_at,expires_at, optionalpolicy_ref, optionalrevocation_ref(where endorsement revocations are expected to appear), and anendorsement_id; - names the endorsing sovereign subject in
endorser_subject_ref((participant|org):did:key:...), which MUST be present in the verifier's activeidentity.sovereign_subject_refs[]. The artifact names the subject explicitly rather than inferring it from the signers, so the verifier knows which custody policy applies even with several org roots or overlapping keys; signatures[]— one{key_public, value}per co-signer, each overfederation-service-endorsement.v1\x00 || canonical JSON/JCS payload_without_signatures(domain separator distinct fromfederation-root.v1, so a root-pack signature can never be replayed as an endorsement, or vice versa);additionalProperties: false; unknown top-level fields rejected.
Verifier acceptance:
- each signature verifies over the canonical payload;
- resolve
endorser_subject_refin the activeidentity.sovereign_subject_refs[](else reject). If the subject iskind = participant, its own key MUST be the sole signer. If the subject iskind = org, the signatures MUST satisfy that org'sfederation-rootcustody policy — the custodian keys sign on behalf of the org and are not themselves sovereign subjects (this is exactly why the artifact names the subject rather than inferring authority from the signers, preserving "governance-authored, not signature-derived"). The localendorsement-multiplicitypolicy (below) may raise the bar. Rotation that removes the subject, or drops its satisfying signer set below the requirement, lapses the endorsement (re-check on every use, no cross-pack caching); capability_id/node_idmatch the service being consumed,expires_atis in the future, andendorsement_idis not revoked.
Endorsement acquisition is source-agnostic¶
The endorsement is a self-verifying artifact, so where a consumer got it carries no authority. A consumer verifying an official service MAY rely on whichever copy is available:
- a locally cached endorsement (obtained earlier, still unexpired),
- the Seed Directory read surface (
GET /cap*endorsementfield, Proposal 025 §3), - the serving node itself — the existing
capability-advertisement.v1capabilities_presented[].endorsements[]slot already carries "federation or issuer endorsement artifacts" over the post-handshake capability exchange (Proposal 014 §4), so the service can present its own proof without any directory round-trip.
Verification is identical regardless of source: resolve
endorser_subject_ref in the consumer's active root, check signer set,
expiry, and revocation. Provenance never substitutes for verification, and a
copy from a "more trusted" source grants nothing extra — freshness, revocation,
and the active root are the only discriminators.
Single-exchange verification (no connect-time round-trips). The normal
path is: the client connects, and the P014 session baseline already delivers
everything in one capability exchange — capabilities_presented[] carries the
passport and endorsements[] in the same message. From there,
official-status verification is pure local computation: the active
federation root (loaded from the data-dir), the local revocation view
(background-polled per Proposal 025 §5, bounded staleness by design), and the
clock. A consumer MUST NOT need a live Seed Directory query to decide official
status at connect time — the directory is discovery and revocation feed, not
per-connection authority. Service nodes SHOULD therefore include their current
endorsement in every capability advertisement for their official capabilities
(the P076-023 cache keeps it at hand).
Endorsement multiplicity policy (verifier-local)¶
A consuming node (and a Seed Directory at registration) MAY require more than
the subject-kind default through a local endorsement-multiplicity policy:
subject-default— participant subject = 1, org subject = its custody threshold (the §6 default);mirror-root-threshold— require the federation root's own custody threshold regardless of subject kind;explicit-N— require at leastNdistinct sovereign co-signers.
Policy raises, never lowers, the bar. An "official" service whose available endorsement does not meet the effective requirement downgrades to an unendorsed community pointer; it never fails open to "official".
Multiplicity counts signers within the named endorser_subject_ref, not
distinct sovereign subjects: a participant subject always yields exactly one
accepted signer, so explicit-N with N > 1 deliberately makes
participant-endorsed services unacceptable under that local policy. A future
policy that requires vouches from several distinct sovereigns would count
distinct valid endorsement artifacts (one per subject), not signatures inside
one artifact; that is out of scope for v1.
Relationship to Existing Proposals¶
- Proposal 054 / Solution 031 (Seed Directory). This proposal elevates
federation_idfrom a per-trust-entry tag to a node-wide selector, and gives concrete shape to the "federation pack" / "distribution defaults" language already present there. Seed Directory's own trust-tier model (personal/community/federation-endorsed) is unchanged; a federation's bootstrap directory list is what a fresh node under that federation starts from. - Solution 021 (Agora Authority). Reused for its custody-mode pattern
(
any-authorized/threshold,AgoraAuthorityRootConfig) andkey-delegation.v1; this proposal adds no new key-authority mechanism. Agora Authority's ownauthority_roots[]remain a distinct, narrower, per-relay, namespace-scoped configuration — explicitly local policy, not a federation-wide contract (Solution 021: "MUST NOT be used as a policy input by other relays"). This proposal'sattestation_roots[]is the federation's own canonical anchor, which naturally becomes the default root Agora Authority recognizes for the federation's reserved namespace; it does not replace Agora Authority's ability to configure additional, narrower roots locally. - Proposal 070 / Solution 036 (Room). Grounds the existing
federation-local/cross-federation/globalexposure tiers in a concrete node-level definition without changing them. - Proposal 074 (Multi-Node Federation Harness). Can pin distinct,
structurally isolated
federation-root.v1files per test node — a "testnet"-style harness federation, distinct fromorbiplex-main, instead of ad hoc per-test configuration. - Proposal 014 (Node Transport and Discovery MVP).
peer_discovery.seeds[]is exactly P014's static seed peer list, "the mandatory first bootstrap layer." This proposal's distribution defaults are what let that layer be non-empty fororbiplex-mainout of the box, instead of requiring every fresh node to be told about a first peer manually. - Proposal 075 (Matrix Homeserver Runtime Profile). P075's "Matrix as an Inter-Federation Carrier" profile is Matrix used as an optional carrier for moving federation-relevant data between two Orbiplex federations. Matrix's own homeserver-to-homeserver federation protocol is not what this proposal means by federation and must not become a stand-in definition for it — the same carrier-is-not-authority rule P075 already applies to Room/Agora/AD, applied one level up to the concept of federation itself.
- Proposal 069 (Corpus). Taxonomy artifacts already carry
federation/ididentifying the owning/signing federation for a namespace. This proposal's node-levelfederation_idis what a node uses to decide whether it treats an artifact's assertedfederation/idas its own or as an explicitly recognized foreign one. Field-naming convention differs today (federation/idin taxonomy fixtures vs.federation_idin Seed Directory config) is deliberate: config/runtime registry artifacts usefederation_id, while wire/domain artifacts may keepfederation/id. federation_unavailablecrisis detector (daemon configuration). Already monitors peer/Seed Directory reachability. Should be read as monitoring reachability of the node's configured federation's infrastructure specifically, which this proposal makes an explicit, named thing rather than an implicit global assumption.- Proposal 036 (Memarium). The Community memory space's
ReplicationScope::Federated(documented, not yet implemented) resolves against this proposal'sfederation_id: replication is bounded to peers sharing the local node's federation, a hard ceiling that a cross-federation alliance must not widen. Sharing Community-space knowledge across a federation boundary remains the existing explicit Community-to-Public promotion (Memarium section 3.3), riding Agora's federation-agnostic substrate — never an implicit consequence of shared Room/group membership.
Data Contracts¶
federation-root.v1(new) — thedata-dir-scoped root config file:federation_id,attestation_roots[](participant or org + custody mode), optional self-containedcustody_policies[],bootstrap_seed_peers[],seed_directory_bootstrap[],signatures[], optionalpolicy_ref/endorsement_refs[].seed_directory_bootstrap[]entries require explicitenabledand may carry inlineendorsement, the MVP official-status proof;endorsement_refs[]remain non-authoritative metadata. Schema:doc/schemas/federation-root.v1.schema.json; example:doc/schemas/examples/orbiplex-main.federation-root.json; negative fixtures underdoc/schemas/examples/invalid/.seed-directory-trust.v1(existing, Proposal 054) — unchanged; a federation's bootstrap list populates this registry on first run.- Reused unchanged:
capability-authorization-policy.v1,key-delegation.v1, and the Agora Authority organization-root configuration shape. federation-service-endorsement.v1(new, §6) — the sole official-service endorsement artifact: a thin multi-signature (1..M) vouch naming the endorsing sovereign subject (endorser_subject_ref) and the endorsednode_id/capability_id.capability-passport.v1(existing, Proposals 024/025) — unchanged, used only for capability scope/advertisement; it never confers official status (§6).
Failure Modes and Guardrails¶
| Failure | Risk | Mitigation |
|---|---|---|
| Root-config file missing/unreadable | Node silently runs under wrong trust | Fail closed by default: refuse to start unless the operator explicitly enables the bundled dev fixture. |
| Root-config file present but signature/custody threshold not met | Unauthorized root trust accepted | Fail closed at startup, not a degraded-mode warning. |
Two data-dirs point at overlapping storage (for example a shared object-store path) |
Cross-federation data leakage | Detect and refuse at startup; never silently merge. |
Bundled orbiplex-main fixture enabled in production |
Node relies on a bootstrap fixture rather than production root custody | Treat federation.allow_bundled_fixture_root = true as development/demo-only; production deployments provide an explicit signed data-dir pack. |
| Bundled fixture bytes differ from the pinned digest | A modified fixture inherits the invalid-signature bypass | Refuse the bypass and validate signatures normally, which currently rejects the fixture signature. |
Legacy data-dir state has the same pack_version but no pack_digest |
First post-upgrade load could pin the currently present pack bytes | Accept as a one-time migration caveat for pre-digest state only; after migration, same-version digest swaps are rejected. |
Local attacker can write federation-root.state.v1.json |
Anti-rollback state can be edited down | State guard is an operational guard against honest mistakes, not tamper-proof storage. Signature/custody verification remains the trust boundary; protect data-dir with host filesystem controls. |
Operator assumes changing federation_id in a running config reloads trust |
Stale keys/directories remain active | federation_id changes require a new data-dir; document and enforce that a live config reload MUST NOT silently accept a federation_id change. |
Root custodian reuses an ordinary participant identity or ordinary node data-dir |
Public root signatures become a correlation handle for unrelated Orbiplex activity | Use dedicated root-custodian participant identities and separate custodian data-dirs for federation-root ceremonies; do not use nyms, ordinary operator identities, or day-to-day participant keys as root custodians. |
An "official" seed-directory/offer-catalog is trusted from its address alone, with no resolvable sovereign endorsement |
Address authority is mistaken for federation authority; a swapped pointer inherits "official" trust | Fail closed for pointers labelled or consumed as official unless a valid federation-service-endorsement.v1 resolves (endorsing subject ∈ active sovereign_subject_refs[]; participant = its key, org = custody met). Community/address-only pointers may load as advisory, but never as official (§6). |
| A sovereign subject is rotated out but its previously issued service passports keep being honored | Stale endorsement outlives the federation's trust in that key | Verifiers resolve issuer against the active pack on every use and MUST NOT cache an endorsement decision across pack_version updates; rotation lapses endorsement for free (§6). |
Open Questions¶
None for the current proposal revision.
Resolved 2026-07-03:
- Seed Directory bootstrap TLS pin rollout and rotation.
seed_directory_bootstrap[].tls_certificate_sha256is a strict root-pack field. Becausefederation-root.v1usesadditionalProperties: false, adding, removing, or changing this field in a published pack is a root-pack migration and MUST use a higherpack_versionplus a coordinated loader and fixture rollout. MVP uses one active leaf-certificate pin per Seed Directory endpoint. Certificate rotation is therefore a new signed root pack followed by daemon restart; multi-pin grace windows are a future extension, not part of the current contract.
Resolved 2026-07-02:
- Bootstrap endorsement delivery and the "official" label for
seed_directory_bootstrap[]entries. Official bootstrap status is derived from an inline optionalendorsementartifact on the bootstrap entry. The entry is official only when thatfederation-service-endorsement.v1artifact is present and verifies against the activefederation-root.v1; otherwise it loads only as a community/advisory pointer.endorsement_refs[]may remain as audit/discovery metadata, but it is not sufficient for bootstrap official status because references cannot be resolved before the referenced directory is trusted. MVP does not add a separateofficialflag; official status is derived from the verified endorsement artifact to avoid redundant state and flag/artifact inconsistency.
Resolved 2026-07-01:
- The lighter, cross-cutting cooperation concept is named
alliance. orbiplex-mainuses an org-kind threshold root;2-of-3is the required minimum (amended 2026-07-03 from the earlier3-of-5initial target), and the governance charter may choose a stricter threshold. Concrete custodian names, rotation ceremony, appeal path, and production signer roster remain governance deliverables, not schema questions. Runtime custody modes are exactlyany-authorizedandthreshold;thresholdmeans M-of-N unique authorized signing keys, not M-of-N people/orgs.federation-root.v1usesfederation_id(snake_case), deliberately matching its closest sibling artifactseed-directory-trust.v1. Config/runtime registry artifacts usefederation_id; wire/domain artifacts such as Corpus taxonomy records may keepfederation/id.- ~~Should a node ever hold root-config files for more than one federation and
switch which is "active" without a full
data-dirchange, or should "one federation perdata-dir, no exceptions" remain absolute?~~ Resolved: "one federation perdata-dir, no exceptions" is absolute. An operator needing more than one federation (for example public + corporate) runs onedata-dirper federation and switches which is active by choosing which to start the daemon against; a running instance is never concurrently a member of two federations. See section 2. federation_idcollisions are handled by explicit local pinning of the root pack digest and root keys. The string is not a global ownership claim, and first-seen-wins is not a trust rule.- Runtime should add
identity.sovereign_subject_refs[]now, preservingidentity.sovereign_participant_ids[]as the narrower participant-only compatibility projection. This lets an org-kindorbiplex-mainthreshold root be represented from the start instead of shipping a participant-only root that must be semantically migrated later.
Resolved 2026-07-02:
federation-root.v1activation is restart-only. A bounded hot reload is not part of the current contract.seed_directory_bootstrap[].enabledis explicit in every root pack entry. There is no hidden default admission for federation-endorsed directories.orbiplex-mainfreezes the threshold-root model and ceremony shape (digest, detached signatures, deterministic assembly, verification, rotation overlap, and appeal reference), while concrete signer roster, concrete keys, final custody threshold at or above the2-of-3minimum, and final appeal body remain governance deliverables.- Official-service pointers fail closed without a resolvable
federation-service-endorsement.v1. Community/address-only pointers may load as advisory unendorsed pointers, but not as official.
Implementation Tracker¶
Status values: todo, in-progress, partial, done, deferred.
| ID | Item | Status | Notes |
|---|---|---|---|
| P076-001 | Define federation-root.v1 schema |
done | doc/schemas/federation-root.v1.schema.json is accepted; covers exactly peer_discovery.seeds[], network.seed_directory[], network.seed_directory_trust[], identity.sovereign_subject_refs[], the participant-only compatibility projection identity.sovereign_participant_ids[], plus federation_id, pack_version, attestation-root/custody refs, self-contained custody_policies[], explicit seed_directory_bootstrap[].enabled, optional inline seed_directory_bootstrap[].endorsement as the bootstrap official-status proof, and optional seed_directory_bootstrap[].tls_certificate_sha256 for transport pinning; additionalProperties: false at every level; kind constrains attestation-root id prefixes; custody policy v1 allows exactly one federation-root rule; tls_certificate_sha256 and key_public patterns matched to the real runtime formats (base64url digest, bare multibase fingerprint); wired into scripts/validate-json-schemas.sh (*.federation-root.json mapping, next to *.seed-directory-trust.json); make validate-schemas passes. Positive examples now cover participant-root, org-threshold, and inline bootstrap endorsement shapes; negative schema-shape fixtures cover missing custody, unsigned/unknown fields, bad inline endorsement shape, forbidden redundant official, missing enabled, and malformed Seed Directory TLS pins; signature-validity, referenced-policy mode parity, endorsement verification, custody-threshold failures, including threshold-not-met, and TLS pin transport enforcement are covered by node runtime tests because JSON Schema cannot express cryptographic truth, clean cross-array references, or live transport evidence. The org-threshold example is mirrored into node/protocol/contracts and exercised by schema-gate/tests/federation_root_contracts.rs. |
| P076-002 | Promote federation_id from per-entry tag to node-wide config |
done | Node runtime now has a node-wide federation.federation_id config surface and includes it in the daemon config snapshot. Config-selected non-default federation ids must match the loaded federation-root.v1, so federation_id is no longer only an attribute of individual Seed Directory trust entries. |
| P076-003 | Load federation-root.v1 from data-dir at daemon startup |
done | Node startup now schema-gates and loads <data-dir>/federation-root.v1.json when present. If no explicit file exists, startup fails closed unless the local operator explicitly enables the bundled orbiplex-main dev fixture. Accepted packs project seed peers, Seed Directory bootstrap/trust, and sovereign identity roots into daemon config. Explicit data-dir packs require real Ed25519 signatures over canonical payloads, participant roots must be self-signed, org roots are evaluated against self-contained custody_policies[], inline bootstrap endorsements are verified against the active root before a Seed Directory is projected as federation-endorsed, invalid inline endorsements fail closed for the whole root pack, and the loader fails closed on schema/semantic/signature/custody errors plus state downgrade/mismatch. Bootstrap entries without inline endorsement remain advisory/community, never official. |
| P076-004 | Ship a signed orbiplex-main default federation pack |
partial | Node ships a bundled orbiplex-main federation-root fixture that exercises the runtime path and populates the four surfaces, but it is not trusted by default. Treat it as a development/demo bootstrap fixture, not production root authority, until the adopted root charter is activated through concrete custodians, concrete keys, final signer roster, and a signed production pack. The charter is now an adopted initial implementing act: doc/normative/50-constitutional-ops/ORBIPLEX-MAIN-ROOT-CHARTER.md (DIA-ROOT-001, 0.1.0, with a PL twin); roster/keys remain deliberately unfilled until the activation ceremony. The threshold-root ceremony shape is frozen in this proposal, but the production roster and signed pack remain governance-authored activation work. |
| P076-005 | Add a startup guard against overlapping data-dir/storage paths |
done | Node writes a local federation-root.state.v1.json under the selected data-dir and rejects startup if the stored federation differs, if the root digest changes unexpectedly, or if the accepted pack_version would roll back. This preserves the "one federation per data-dir" invariant. The state file is an operational guard, not tamper-proof storage against a local actor who can write the data-dir. |
| P076-006 | Cross-reference Room's federation-local/cross-federation exposure docs with this proposal's node-level definition |
done | Solution 036 now explicitly grounds Room federation-local, cross-federation, and global exposure in the active federation_id selected from federation-root.v1, while preserving Room's carrier-neutral boundary. |
| P076-007 | Update Proposal 075's "Federated Homeserver Deployment" framing | done | Renamed to "Matrix as an Inter-Federation Carrier"; Executive Summary and Non-Goals now state explicitly that Matrix's homeserver-to-homeserver federation protocol is a separate mechanism from Orbiplex's own federation identity, and that adopting it is not a goal. |
| P076-010 | Ground Memarium's ReplicationScope::Federated in federation_id |
done | Proposal 036 section 3.1/3.2 now defines Federated as bounded to peers sharing the node's federation_id, and section 3.3 states that cross-federation Community sharing is the existing explicit Community-to-Public promotion, never implicit group/Room membership. |
| P076-011 | Add an org-kind sovereign root surface (identity.sovereign_subject_refs[]) |
done | Node config now has identity.sovereign_subject_refs[] with participant/org kind, optional custody metadata, purpose refs, and a participant-only compatibility projection into identity.sovereign_participant_ids[]. Org entries are representable and validated for custody metadata; org federation-root signature evaluation is implemented through self-contained custody_policies[] rather than being a config-shape blocker. |
| P076-012 | Verify federation-root signatures and custody policies at startup | done | The daemon canonicalizes the federation-root payload without signatures[], verifies Ed25519 signatures, requires participant roots to have a matching self-signature, evaluates org roots with any-authorized or threshold custody rules for purpose = federation-root, counts threshold over unique authorized keys, rejects empty/missing explicit packs by default, pins/migrates the data-dir state digest, and keeps the bundled orbiplex-main fixture behind explicit federation.allow_bundled_fixture_root = true plus a raw-digest-pinned bypass so production deployments require an explicit signed data-dir pack. |
| P076-013 | Document root custodian operational separation | done | Production federation-root custodians should use dedicated participant identities and separate custodian data-dirs for root-pack approval, rotation, and recovery ceremony work. Public signatures[].key_public values and custody-policy signer sets are stable correlation handles, so ordinary participant identities, ordinary node data-dirs, and nyms must not be reused as federation-root custodians when privacy separation matters. Node-side ceremony tooling documents this operational guardrail. |
| P076-014 | Provide hard-MVP federation-root ceremony tooling | done | Node-side tooling can derive public ceremony identities from node participant mnemonics, generate passphrase-encrypted PEM custodian keys by default, and sign root packs with encrypted or legacy plaintext PEM keys, mnemonics, plaintext participant key records, plaintext data-dirs, or encrypted participant signing keys opened through the narrow Rust unlock helper. The tool supports manifest digest verification, strict pre-assembly signature checks, per-root custody threshold verification, production orbiplex-main profile checks, and an end-to-end smoke test that covers encrypted PEM keygen/signing, encrypted data-dir signing, and production-profile positive/negative cases. Plaintext PEM output is an explicit disposable-fixture mode, not production ceremony storage. This remains MVP multisig custody, not FROST/DKG threshold signing. |
| P076-008 | Define the alliance cross-federation cooperation concept |
done | Concept plus minimal unilateral policy contract are frozen in Proposal 079 as alliance-policy.v1; runtime enforcement is deferred and tracked in P079 rather than hidden inside P076. |
| P076-009 | Update Proposal 074's harness to pin distinct federation-root files per test node | done | Node acceptance seeders now write explicit signed federation-root.v1 packs into local profile data dirs, refresh placeholder node ids to runtime node:did:key values after first boot where needed, and embed signed federation-service-endorsement.v1 artifacts for Seed Directory bootstrap entries that claim federation-endorsed. Pre-first-boot roots may intentionally omit Seed Directory bootstrap entries until real runtime DID node ids exist, instead of giving official status to placeholders. Story-005 seeds one deterministic harness-only participant key per profile before first boot and includes the matching participant roots and signatures in both initial and refreshed packs; each private key remains on its own profile. Story-010 refreshes its root with the imported Marcin/Daniel participant roots and matching harness-only signatures; each node's readiness selects only the root key present locally, so no participant private key is copied between nodes. Story-011 additionally creates provider participants during first boot, inserts B/C participant attestation roots with matching self-signatures into the refreshed runtime root, restarts, asserts /v1/seed-directory active trust, and then exercises capability passport issue/publish through the active root projection. Story-005, Story-009, Story-010, and Story-011 no longer rely on loose trust-list shortcuts for official local Seed Directory bootstrap. These local roots are acceptance fixtures, not production org/threshold ceremony roots; Story-011 may include additional local participant roots only to exercise root-backed runtime projection. Broader multi-federation matrix polish remains a future P074/testnet extension. |
| P076-015 | Ground "sovereign operator" in the federation root | done | The §6 definition now treats a sovereign operator of federation F as a subject in F's active identity.sovereign_subject_refs[]; participant subjects vouch with their own key and org subjects are held to their federation-root custody policy. Proposal 025 §2/§4/§6 now uses this split: capability-passport.v1 is scope-only, while official-service status is carried by federation-service-endorsement.v1. |
| P076-016 | Enforce federation-endorsement resolution in the loader and capability-binding |
done | capability-binding contains the pure verifier core for federation-service-endorsement.v1, and daemon config now derives a runtime-only FederationSovereignSubjectSnapshot from the verified active federation-root.v1 pack. Embedded Seed Directory receives a live authority snapshot source and uses it to verify endorsement attach/revocation before mutating official-status state; GET /cap* downgrades to community/unofficial when no active verified endorsement is present, and attach responses expose a typed OfficialStatusDecision. Daemon consumers perform per-use official-status re-verification against the reader's active root for seed sync cache admission, host seed.directory.query, Artifact Delivery capability lookup, Contact Catalog provider discovery, local own-node endorsement installation, and capability-advertisement.v1 endorsement projection. Missing proof or missing active root degrades to scope-only; invalid proof rejects the claimed official entry and emits official_status_rejected or self-fetch refusal tracing. Consumer checks union fresh Seed Directory endorsement-revocation feeds with the daemon revocation snapshot. Startup applies root-pack endorsement revocations before accepting inline official Seed Directory bootstrap proofs, and runtime reload refuses any changed root activation fingerprint so root changes require restart. Future official-service consumers must reuse this same per-use verifier rather than treating local cache state as authority. |
| P076-017 | Define federation-service-endorsement.v1 (single endorsement artifact, 1..M signatures) |
done | doc/schemas/federation-service-endorsement.v1.schema.json defines the single endorsement artifact with additionalProperties: false, explicit endorser_subject_ref, federation_id, node_id, capability_id, validity window, optional policy/revocation refs, and signatures[]. Positive and negative fixtures are synced into node/protocol/contracts; schema-gate exposes dedicated ingress/export validators and rejects import-boundary use. The node verifier core uses the same domain separator and canonical JSON/JCS signing payload. §6. |
| P076-018 | New offline ceremony process for joint-issuance endorsements | done | Node-side ceremony tooling now has a sibling federation-service-endorsement.v1 command set: endorsement-digest, endorsement-sign, participant mnemonic/key/data-dir signing variants, endorsement-assemble, and endorsement-verify. The endorsement payload uses its own federation-service-endorsement.v1\0 domain separator, detached signatures are assembled deterministically, and --strict resolves the endorser_subject_ref signer group against the active federation-root.v1 before writing a final artifact. Strict assembly/verify rejects unauthorized excess signatures rather than merely checking that the threshold was met. The smoke test covers threshold org endorsement signing with mnemonic, plaintext data-dir, plaintext key-record, encrypted data-dir key sources, and unknown-signer rejection. This is MVP multisig custody, not FROST/DKG. |
| P076-019 | Post-MVP remote co-signing protocol for endorsements | deferred | Transport-agnostic protocol letting sovereign signers add detached signatures over a shared endorsement digest without an in-person ceremony. Guardrails: every signer verifies the exact digest/payload before signing (no "trust and sign"); deterministic assembly; the channel carries detached signatures, not authority. Transport is undecided — messaging plus an attachment primitive is one candidate, not a commitment; any attachment primitive would need its own bounded spec first. |
| P076-020 | Ceremony option to author attestation_roots[]/sovereign_subject_refs[] from supplied keys |
done | Node ceremony tooling now provides root-draft-from-roster, which writes an unsigned federation-root.v1 draft from an explicit governance roster before manifest digesting or signing. It supports participant-root drafts and org-threshold drafts, requires --confirm-roster, rejects duplicate signer keys, and reuses the production orbiplex-main profile checks when requested. The roster is supplied as explicit data; it is never derived from detached signatures discovered on disk. |
| P076-027 | Optional TLS pin for seed_directory_bootstrap[] entries |
done | Implemented 2026-07-03: federation-root.v1 carries optional seed_directory_bootstrap[].tls_certificate_sha256 with the same sha256:<base64url-43> shape as bootstrap_seed_peers[].tls_certificate_sha256. Node mirrors the schema and fixtures, projects the pin into network.seed_directory[] and network.seed_directory_trust[], rejects pinned non-HTTPS entries at load/config validation, rejects duplicate manual Seed Directory endpoint entries whose pins differ within network.seed_directory[], within network.seed_directory_trust[], or across the bootstrap/trust surfaces, and verifies the peer leaf certificate DER SHA-256 on source-aware Seed Directory fetch, sync, candidate lookup, query, publish, and revoke paths. Directory responses remain signed artifacts and official status still requires verified federation-service-endorsement.v1; the pin is only transport privacy/integrity material. Current Node verification is response-time request-before-check and is acceptable only for public/advisory discovery calls; future authenticated or secret-bearing calls require handshake-time certificate verification before any request bytes are sent. Compat: older loaders reject packs carrying this optional field because the schema is additionalProperties: false, so root pack rollout must coordinate schema, loader, fixture updates, and a pack_version bump. MVP uses one active pin per endpoint; certificate rotation is a new signed pack plus daemon restart. |
| P076-021 | Make federation-service-endorsement.v1 the sole proof of "official" status |
done | Node now implements the bootstrap and runtime slices: federation-root.v1 accepts optional inline seed_directory_bootstrap[].endorsement, the daemon loader verifies that federation-service-endorsement.v1 against the active root with expected node_id and seed-directory capability before projecting federation-endorsed, and a bootstrap entry without inline endorsement is downgraded to community/advisory even if the pack asked for federation-endorsed. Invalid inline endorsements fail closed for the whole root pack. No separate official flag exists, and endorsement_refs[] remain non-authoritative metadata. Root-pack endorsement revocations are applied before bootstrap projection. Seed Directory attach/read consumes endorsements as the official-status proof and returns typed official_status decisions. Daemon consumers re-verify claimed official status before cache admission or presentation, and own-node endorsements fetched from Seed Directory are persisted only through the same scoped verifier. Runtime tests cover missing inline endorsement, endorsement refs alone, foreign node, wrong capability, unknown endorser, invalid signature, expired/revoked endorsements, missing-proof downgrade, malformed-claim rejection at cache boundaries, and local advertisement projection. The operator API can issue non-own participant-sovereign endorsements; org-governed issuance uses P076-018 ceremony tooling. Future official-service consumers inherit the same rule: no verified endorsement, no official status. |
| P076-022 | Operator UI for issuing official-service endorsements for non-own services | done | Daemon exposes POST /v1/operator/federation-service-endorsements/issue as the thin operator API for non-own official-service endorsement issuance. A sovereign participant operator can provide a target node_id, official capability_id, optional validity/policy/revocation refs, and idempotency key; retries with the same key reuse the already installed endorsement, while reuse for different coordinates fails closed; the node builds and signs federation-service-endorsement.v1 through the host signer, schema-gates the artifact, installs it through the same scoped verifier/storage path used by config-install, and returns a percent-encoded Seed Directory attach hint for PUT /cap/{node-id}/{capability-id}/endorsement. If the active sovereign subject is org-governed, the API fails closed with ceremony-required and points to P076-018 rather than pretending one custodian can issue. |
| P076-023 | Endorsed-node periodic endorsement fetch and local cache | done | Seed Directory fetch/cache carries endorsement material through the existing capability discovery cache, and daemon consumers locally re-verify every claimed official endorsement before preserving it in cache-derived projections. The periodic seed-sync worker now builds its fetch set from hard-MVP capability ids plus currently local capability passports, fetches configured Seed Directories, applies official-status verification, and installs endorsements targeting the local node_id through the existing scoped federation-service-endorsement verifier/storage path. Installed, unexpired, non-revoked endorsements are re-verified against the active root before being projected into capability-advertisement.v1 capabilities_presented[].endorsements[]. Missing proof downgrades to scope-only, revoked/invalid proof is rejected, refusal/installation traces are emitted, and a directory outage does not delete a still-valid local cached endorsement. |
| P076-024 | Keep federation-root activation restart-only | done | Resolved daemon config now carries the active root activation fingerprint (federation_id, pack_version, digest). Runtime reload validates candidate config but refuses to continue when that fingerprint differs from the currently running daemon, logging a specific first-activation/removal/change restart-required event and keeping the existing runtime. Operators may still validate candidate packs through config checks, but applying a different federation-root.v1 requires restart against the selected data-dir. |
| P076-025 | Require explicit seed_directory_bootstrap[].enabled |
done | federation-root.v1 schema now requires enabled on every seed_directory_bootstrap[] entry, the root-loader struct has no default for that field, and negative fixtures/tests reject omitted enabled. Generic layered daemon config may still keep its own compatibility defaults outside the root-pack import boundary. |
| P076-026 | Freeze the production orbiplex-main ceremony shape |
done | Implemented 2026-07-03 in the node ceremony tooling as an explicit --production-orbiplex-main profile for manifest, assemble, and verify commands. The profile requires federation_id = orbiplex-main, exactly one org-kind threshold attestation root, optional attestation-root purposes[] to include federation-root when present, a self-contained federation-root custody policy, at least a 2-of-3 unique signer-key threshold with duplicate signer refs rejected, top-level policy_ref using the policy:dia-root-001@... charter-version convention, manifest digest/threshold parity, strict deterministic assembly, threshold satisfaction before writing, and hard failure on unauthorized or invalid signatures. federation-root.v1 has no dedicated rotation-overlap field yet, so the checker records that as not-representable-in-federation-root.v1 rather than inventing an unofficial field. Concrete roster/keys remain governance-authored. |
Implementation Recommendations¶
Task-level guidance for the tracker entries above, aligned with
node/DEV-GUIDELINES.md. The goal is a smooth, layered implementation with no
entanglement surprises. Registration-surface details live in Proposal 025
(P025-002/003/004), and peer-presentation delivery over WSS/Artifact
Delivery/INAC lives in Proposal 025 §Implementation Recommendations
(P025-010); this section covers the node-side chain.
Layering map for the endorsement chain¶
protocol/contracts schema + fixtures (federation-service-endorsement.v1)
→ schema-gate ingress/export boundary validation (shape only)
→ capability-binding pure verifier: no IO, no clock ownership, no config
→ daemon host adapters: authority snapshot from the loaded
federation root, decision surface, facts, cache
→ seed-directory registration / attach / read projection (P025 §2–§3)
→ node-ui / operator thin surface over host APIs; never touches keys
Keep the verifier pure: capability-binding::federation_endorsement takes
an explicit now_rfc3339, an explicit authority snapshot, and an explicit
revocation set. It must never read config, storage, or the system clock —
callers own time and state. This is what makes threshold semantics testable
without a daemon.
P076-015 — grounding the definition (done; invariant to preserve)¶
- The definition landed in the schema descriptions and
x-dia-basis. If ever revisiting those descriptions, keep the pointers: the §6 note incapability-passport.v1.schema.jsonand the §6 pointer next toseed_directory_bootstrap/endorsement_refsinfederation-root.v1.schema.json. - Do not add new envelope fields to
capability-passport.v1. It stays a scope artifact; the federation binding is a verifier rule, not schema shape.
P076-016 — runtime verification in the daemon¶
- Landed: the authority snapshot is derived once per loaded pack
(
daemonconfig carries a runtime-onlyfederation_service_authority_snapshotbuilt from the verified pack, alongside thesovereign_subject_refsprojection), and the embedded Seed Directory receives it for attach/revocation verification. Custody truth has one source, two consumers (validate_org_federation_rootand the snapshot mapping) — keep it that way. - Landed: daemon consumers re-check claimed official status against the
reader's active root before accepting cache-derived projections in seed sync,
seed.directory.query, Artifact Delivery capability lookup, and Contact Catalog provider discovery. Missing proof/root degrades to scope-only; invalid proof is rejected. - Landed: startup/bootstrap revocation application for inline official proofs:
root-pack
federation_service_endorsement_revocations[]are applied before aseed_directory_bootstrap[].endorsementcan confer official bootstrap status. - Remaining: rotation-triggered re-checks and non-Seed-Directory official consumers.
- Surface the outcome as a typed decision, not a bool or string:
enum OfficialStatusDecision {
FederationEndorsed { endorsement_id: String, endorser_subject_ref: String },
ScopeOnly { reason: OfficialStatusRefusal }, // community pointer
}
Downgrade is data, not an error branch — consumers route on it.
- Re-verify on every use. If profiling ever justifies a cache, key it by
(endorsement_id, pack_digest) so a pack update invalidates implicitly.
- Do not: cache across pack_version; treat custodian keys as subjects;
expose the verifier to module callers directly (host-owned surface, same
posture as the Agent lifecycle capabilities); let a failed endorsement check
abort an otherwise valid scope connection unless local policy demands it.
P076-017 — the artifact contract (verifier core landed)¶
- Fixture wiring is covered: the positive and
invalid/missing-endorserfixtures are exercised byschema-gate/tests/networking_contracts.rs. Remaining: add aninvalid/fixture per rejection class the runtime distinguishes (tampered payload, duplicate signer, sub-threshold). - Signing input stays
federation-service-endorsement.v1\x00 || canonical_json(JcsV1, payload_without_signatures)— config-registry family, likefederation-root.v1. Do not switch to the CBOR wire-envelope family or reuse the root domain separator. - Do not put endpoints, addresses, or TLS material into the endorsement — reachability stays in advertisements (§6 stratification).
- The verifier uses a 30s clock-skew tolerance for
issued_at(ISSUED_AT_CLOCK_SKEW_TOLERANCE, mirroring the P014 handshake window);expires_atstays exact. Keep that asymmetry — skew absorbs issuance races, never extends validity.
P076-018 — offline ceremony for endorsements¶
- Landed as a sibling command set in
tools/federation-root-ceremony, reusing the digest → collect detached signatures → assemble → verify pattern with the endorsement's own domain separator. Signing supports the same custodian key sources as root-pack signing, including encrypted node participant keys through the prebuilt unlock helper. Strict assemble/verify resolvesendorser_subject_refagainst a provided root pack before accepting the final artifact and rejects unauthorized excess signatures instead of accepting them as harmless threshold surplus. - Do not: reuse the
federation-root-ceremony-manifest.v1schema string; sign beforeendorser_subject_refand all payload fields are final (any later mutation invalidates signatures — TOCTOU discipline as in the whisper preflight); auto-derive the endorser subject from whichever key signs; derive or promote an operator identity from a custodian key.
P076-020 — authoring roots from supplied keys (operator-owned)¶
- Implemented as
root-draft-from-roster: the operator supplies participant roots or an org-threshold signer roster as data, passes--confirm-roster, and receives an unsignedfederation-root.v1draft withsignatures: []. The draft is the signing input and must be finalized through manifest, detached signatures, and strict assembly. - The production profile path reuses the same
--production-orbiplex-mainshape checks as manifest/assemble/verify. Duplicate signer keys are rejected before a draft is accepted. - Do not derive the roster from signature files discovered on disk — that reintroduces signature-derived authority.
P076-021 — cutover to endorsement-only official status¶
- Land read/verify paths (P076-016/017, P025 read surface) before flipping any consumer to require endorsements; existing scope-only registrations remain valid community entries — the cutover changes what "official" means, not what is stored.
- Gate the flip behind a config default so a deployment can stage it; the negative test "lone single-issuer passport ≠ official" is the cutover's definition of done.
- Bootstrap endorsement delivery:
seed_directory_bootstrap[]carriesendorsement_refs[]— references, not artifacts — and a reference cannot be resolved before the directory it points at is trusted (circularity). The MVP shape is an optional inlineendorsementfield next topassportin the bootstrap entry. Official bootstrap status is derived only from that verified artifact; entries without it remain advisory community pointers, and no separateofficialflag is introduced. Invalid inline artifacts fail closed for the whole root pack rather than being downgraded per-entry. Node implements this schema/runtime slice plus consumer-side per-use re-verification for current Seed Directory consumers; startup/bootstrap revocation application for inline official proofs. Remaining P076-021 work is broader official-service consumers.
P076-022 — operator API for endorsing non-own services¶
- Landed as a thin host API. A UI may wrap it, but the authority boundary is the daemon endpoint and host signer, not browser-held key material. The request DTO is intentionally small:
{ "node_id": "node:did:key:z...", // target service node
"capability_id": "seed-directory", // from a closed menu of official
// profiles, never free text
"expires_at": "...", // bounded default, e.g. 90 days
"policy_ref": "..." (optional) }
- Participant subject: signs through the signer service; the endpoint also
immediately installs the resulting artifact through the scoped P025 verifier.
Org subject: returns
ceremony-requiredand hands off to P076-018 instead of signing inline — one custodian cannot issue an org-governed endorsement. - The response includes a Seed Directory attach hint for
PUT /cap/{node-id}/{capability-id}/endorsement; operators or future UI code still need to respect the retryablescope-entry-missingbackoff (5s → 30s → 120s → 360s) when attaching to a remote directory. Terminal403is surfaced to the operator, never retried. - Operator-visible facts are currently the local installation record plus the
returned issue/install response. Richer
endorsement/drafted,signed,submitted,attach-retried, andattach-gave-upUI events can be added around remote attach automation without changing the endorsement artifact.
P076-023 — endorsed-node fetch and cache¶
- Landed: the discovery cache carries
official+endorsementend to end (SeedDirectoryCapabilityEntry), and daemon consumers re-verify the stored artifact before preserving it as official or projecting it intocapabilities_presented[].endorsements[]. - Landed: the bounded periodic seed-sync worker queries configured Seed
Directories for capability ids needed by hard-MVP consumers plus local
capability passports. Verified endorsements targeting the node's own
node_idare installed into the existing local endorsement store with source detail and trace events; rejected proofs are not cached. - Verify before storing, against the node's active root. Suggested cache row remains implemented through the existing installation envelope rather than a second table:
installation record -> endorsement artifact, installed_at, source, source/detail
The presentation path re-verifies installed artifacts against the current
active root and revocation set, so a root update after restart cannot keep an
obsolete endorsement alive by cache state alone.
- Presenting from cache is fine — consumers re-verify against their root
anyway (P025 §4 step 6), so the cache is availability, not authority. The
wire slot already exists: present the cached artifact through
capability-advertisement.v1 capabilities_presented[].endorsements[]
(§6, "source-agnostic"). Do not smuggle it through
capability-passport-present.v1's additionalProperties: true — that slot
is a single-passport contract; widening it silently is exactly the
silent-laxity anti-pattern.
- Do not: extend usable lifetime beyond expires_at; tighten the poll
interval on failure (back off instead); cache anything that failed
verification; treat a directory outage as loss of official status (the
cached, unexpired endorsement keeps serving).
P076-024 — restart-only federation-root activation¶
- Landed at the config-reload boundary: any reload path that would change the
effective
federation-root.v1(different digest for the samefederation_id, a newpack_version, first activation, or removal) refuses with a specific restart-required event:daemon_reload_federation_root_first_activation_restart_required,daemon_reload_federation_root_removal_restart_required, ordaemon_reload_federation_root_changed_restart_required. A validate-only operator command ("would this pack load?") is fine and useful; applying it live is not. - Do not special-case "small" pack changes (e.g. only
seed_directory_bootstrap[]edits) as hot-reloadable — the pack is one signed unit; partial application splits value from time.
P076-025 — explicit seed_directory_bootstrap[].enabled¶
- Two coordinated changes: add
enabledto the entry'srequiredlist infederation-root.v1.schema.json(today it is optional), and make the loader reject omitted values instead of defaulting. This is a breaking pack change: bumppack_versionguidance and update the bundled fixture plus every example in the same change. - Do not paper over it with
#[serde(default)]on the Rust side — a silent default is exactly the hidden admission the resolution forbids.
P076-026 — freeze the orbiplex-main ceremony shape¶
- Implemented as an explicit
--production-orbiplex-mainceremony profile for manifest, assemble, and verify commands. Production assembly requires--strict, checks the root custody threshold before writing, and rejects unauthorized or invalid detached signatures even when the threshold would otherwise be satisfied. - The checker enforces the production shape:
federation_id = orbiplex-main, exactly one org-kind threshold root, a self-containedfederation-rootcustody policy, at least a 2-of-3 unique signer-key threshold, top-levelpolicy_refusing thepolicy:dia-root-001@...charter-version convention, and manifest digest/threshold parity. Duplicate signer refs acrossauthorized/key_publics[]andauthorized/participant_ids[]are rejected fail-closed instead of merely deduplicated. federation-root.v1has no dedicated rotation-overlap field yet. The checker records this asnot-representable-in-federation-root.v1; if the schema gets an explicit rotation-overlap field later, the production profile must enforce it.- Keep roster/keys out of the checks — they are governance-authored inputs, not tool-verifiable constants.
P076-027 — Seed Directory bootstrap TLS pin¶
- Treat
seed_directory_bootstrap[].tls_certificate_sha256as transport integrity/privacy material only. It never makes a Seed Directory official; official status still comes from a verifiedfederation-service-endorsement.v1. - The pin format is
sha256:<base64url-43>over the raw leaf certificate DER, matching the narrower Seed Directory projection of Solution 024'ssha256-leaf-derevidence model. - Reject pinned non-HTTPS endpoints and reject conflicting duplicate pins for
the same endpoint within a surface or across
seed_directory_bootstrap[]andseed_directory_trust[]. - Node MVP verifies the pin from response TLS metadata after the HTTP request
has already reached the peer. That is acceptable only for the current
public/advisory Seed Directory discovery calls. Do not reuse this
request-before-check pattern for future authenticated or secret-bearing
calls; those require handshake-time certificate verification, for example a
custom rustls
ServerCertVerifier, before any request bytes are sent. - Because
federation-root.v1rejects unknown fields, adding this optional field to a deployed federation root is still a breaking pack-shape migration: bumppack_version, update loaders, update fixtures, then restart nodes against the new signed pack. - MVP supports a single active pin per endpoint. Certificate rotation is a new signed root pack plus restart; do not implement ad-hoc multi-pin overlap in individual consumers.
P076-019 (deferred) — remote co-signing¶
Keep transport-agnostic. Whatever carries detached signatures, each signer verifies the exact digest before signing, assembly stays deterministic, and the channel carries signatures — never authority.
Next Actions¶
- Complete the activation package under the adopted
orbiplex-mainroot charter (doc/normative/50-constitutional-ops/ORBIPLEX-MAIN-ROOT-CHARTER.md,DIA-ROOT-001, 0.1.0): name custodians, generate keys, fill the roster, confirm the ≥2-of-3 threshold, and record the appeal process before treating the default federation as production-trustworthy. - Replace the bundled hard-MVP fixture with a real
orbiplex-mainsigned pack once the activation ceremony is complete, then set production packaging to disablefederation.allow_bundled_fixture_root. - Continue the remaining P076 post-MVP protocol track: P076-019 (remote co-signing) as deferred post-MVP work. Alliance runtime enforcement now belongs to Proposal 079.