Chio/Docs
LOGIN · JOIN

Market Boundary

Move tenant storage, admit capabilities, contract outbound calls, and ship releases while each market owner keeps its authority exact.

Concept chio@5f63e6e

On this page

An operator can move a tenant's storage between databases, promote a new release, and retract a finding a buyer already read, while every capability proof still admits once and every contracted outbound HTTP call reaches only the destination its contract declares.

Five owners carry that guarantee: the transition API that moves a tenant's storage authority, one Postgres transaction that decides each capability admission, the egress contract an HTTP client declares before it dials out, the canary that checks a release candidate, and the memory guard an embedder installs to refuse a retracted finding. A valid record at one owner establishes that record's claim, and the next owner still checks the authority its own action needs.

Four signed operations decide which store holds a tenant's history

PostgresFindingMarketReplicator::apply_authority_transition moves one tenant's storage authority between SQLite and Postgres through exactly four operations, one kind at a time, each a signed chio.finding.hosted-authority-transition.v1 record. Each operation also sets the tenant's mode: freeze sets frozen with mutations_enabled false, cutover sets rollback_window, rollback returns SQLite to shadow, and retirement sets retired.

Storage authorityChoose an operation.
sqlitepostgresfreezefreezefreezecutoverrollbackretire_sqliteshadowfrozenfrozenrollback_windowauthoritativeretiredsqlitepostgresfreezefreezefreezecutoverrollbackretire_sqliteshadowfrozenfrozenrollback_windowauthoritativeretired

Cutover hands authority to PostgreSQL for a seven-day window

From sqlite in mode frozen, cutover moves authority to postgres in mode rollback_window with mutations enabled. The window ends exactly 604800 seconds after the transition's creation time.

The six authority states of one tenant and the four operations chio_finding_market_apply_authority_transition applies; filled states admit domain writes. Every transition also requires a matching epoch, sequence, and configuration revision, and a signed replication check reporting zero lag and equal projection digests.The hosted profile

Freeze stops domain writes under whichever store holds authority, keeps that authority, and carries the next epoch with no rollback-window field. Cutover moves a frozen tenant from SQLite to Postgres and opens a rollback window that ends exactly 604,800 seconds, seven days, after the transition is created. Rollback returns a frozen tenant to SQLite while that stored window is still open and reconciles the original frozen history, and RetireSqlite keeps Postgres once the window has expired. A tenant's authority history is exactly this sequence of records.

crates/platform/chio-finding-market-store-postgres/src/replication.rs234–239rust
pub enum HostedAuthorityTransitionOperation {
    Freeze,
    Cutover,
    Rollback,
    RetireSqlite,
}

Before it changes a row, the store checks the operation against the authority pair and the rollback-window field: a transition whose shape does not match its operation is refused as HostedMarketStoreError::Invalid, and one whose source and target checkpoint digests differ is refused as HostedMarketStoreError::Conflict. A retry never extends the window, and retirement is its own authorized transition, separate from any successful cutover. Storage authority in the hosted profile covers the operating procedure; ADR-0021 records the design rationale.

Resume the original transition after a restart

A restart resumes the exact signed original from the owner's durable journal, with the same independently authenticated tenant and signer pin. A lost response never produces a replacement signed transition. The integration borrows the retained record, so a store error cannot consume it, and reads current authority even when applying returned an error after a possible commit. Inserted and ExactReplay both mean interpreting that original against current state, and a failed readback stays an open question until it resolves. Download the complete source.

security-host/market_authority.rsrust
use chio_core_types::PublicKey;
use chio_finding_market_store_postgres::{
    HostedAuthorityState, HostedJobWriteOutcome, HostedMarketStoreError, HostedTenantId,
    PostgresFindingMarketReplicator, SignedHostedAuthorityTransition,
};

pub struct TransitionObservation {
    pub application: Result<HostedJobWriteOutcome, HostedMarketStoreError>,
    pub current_authority: Result<HostedAuthorityState, HostedMarketStoreError>,
}

/// Load the SAME signed transition from the owner's durable journal on restart.
/// Keep the independently selected tenant and signer pin, and borrow the record
/// so a store error cannot consume or replace the original signed intent.
pub async fn resume_authority_transition(
    replicator: &PostgresFindingMarketReplicator,
    authenticated_tenant: &HostedTenantId,
    expected_signer: &PublicKey,
    retained_original: &SignedHostedAuthorityTransition,
) -> TransitionObservation {
    let application = replicator.apply_authority_transition(
        authenticated_tenant, expected_signer, retained_original,
    ).await;
    // Observe independently even if applying returned an error after a commit.
    let current_authority = replicator.authority_state(authenticated_tenant).await;
    TransitionObservation { application, current_authority }
}

One transaction admits each capability proof at most once

consume_capability_dpop_admission resolves a presented DPoP proof and nonce inside one Postgres transaction. It serializes on the tenant's admission-state row, locked FOR UPDATE before the live-nonce count is read, with a pg_advisory_xact_lock on the tenant kept for exclusion across releases. Whichever of two concurrent presentations commits first records the nonce; the other's nonce insert conflicts and returns Replay. The typed HostedCapabilityAdmissionOutcome names four results:

  1. Admitted. A fresh proof, with room in the capability's own invocation budget.
  2. RetriedSameRequest. A proof resuming the exact request it authorized, after the first caller lost its reply.
  3. Replay. A reused proof that resumes nothing; it admits no new work.
  4. BudgetExceeded. The capability's own invocation count already reached its bound.

The tenant-wide nonce ceiling is a separate limit, refused as HostedMarketStoreError::Capacity. Capacity in the hosted profile sets the ceilings.

An outbound client reaches only the authorities its contract names

An HttpEgressContract declares what one outbound HTTP client may do: its schemes, its exact authorities, whether loopback, link-local, and IPv6 unique-local literals are denied, and how many redirect hops and response bytes it tolerates.

crates/protocol/chio-egress-contract/src/lib.rs17–41rust
pub struct HttpEgressContract {
    /// Tenant-scoped namespace used by callers to bind egress receipts and
    /// deployment policy to one authority domain.
    pub tenant_egress_namespace: String,
    /// Lowercase HTTP URL schemes allowed by this contract: `http` or `https`.
    #[serde(default)]
    pub allowed_schemes: BTreeSet<String>,
    /// Exact normalized URL authorities allowed by this contract.
    ///
    /// Domain authorities are lowercase. IPv6 authorities use brackets, for
    /// example `[2001:db8::10]:443`.
    #[serde(default)]
    pub allowed_authority_set: BTreeSet<String>,
    /// Reject loopback address literals and localhost names even if an
    /// authority entry was configured.
    pub deny_loopback: bool,
    /// Reject IPv4 and IPv6 link-local address literals.
    pub deny_link_local: bool,
    /// Reject IPv6 unique-local address literals.
    pub deny_ipv6_ula: bool,
    /// Maximum redirect hop count accepted for a request chain.
    pub max_redirect_chain: u8,
    /// Maximum response bytes accepted before the substrate must abort.
    pub max_response_bytes: u64,
}

HttpEgressContract::enforce_required treats a missing contract as a refusal. At dispatch, build the client with client_builder_with_contract and send with send_with_contract, which revalidates the target and every redirect hop against the same immutable contract and runs the DNS and connect-time address checks. The check runs in the process about to dial out, so the caller receives the typed HttpEgressError directly. A finding, an agreement, or a counterparty's reputation grants no destination; only the contract does.

The workspace lint that finds uncontracted clients

The http-egress-contract gate reads each Rust file under crates/ that uses a reqwest client or top-level request helper and dispatches a request, apart from test files and the contract, HTTP-core, and MCP-adapter crates. It exits 1 and lists each offending line when a direct client construction, top-level request helper, .send(), or client .execute( line has no CHIO_EGRESS_LINT_ALLOW_DIRECT_REQWEST: classification comment on it or in the four lines above, or when a file calls send_with_contract without building its client with client_builder_with_contract. Its scope is reqwest call sites; a client on another HTTP library needs its own review. Every Chio release passes the http-egress-contract gate before it is tagged. chio release verify --evidence qualification shows its result for the release you run.

The prepared egress example refuses five destinations without opening a socket

The example builds one contract for the harbor-goods tenant's harbor-goods/market/v1 namespace, allowing HTTPS to market.example with no redirects and at most 64 KiB of response, then runs Chio's contract validator against the original URL and five changes to it. It resolves no DNS and sends no request. Download the complete source.

security-host/market_egress_cases.rsrust
use chio_egress_contract::{HttpEgressContract, HttpEgressError};
use std::collections::BTreeSet;

pub fn compare_market_egress() -> Result<(), HttpEgressError> {
    let contract = HttpEgressContract {
        tenant_egress_namespace: "harbor-goods/market/v1".to_owned(),
        allowed_schemes: BTreeSet::from(["https".to_owned()]),
        allowed_authority_set: BTreeSet::from(["market.example".to_owned()]),
        deny_loopback: true, deny_link_local: true, deny_ipv6_ula: true,
        max_redirect_chain: 0, max_response_bytes: 64 * 1_024,
    };
    let original = "https://market.example/quotes/sku-118";
    let selected = contract.enforce_attempt(original, 0, Some(1_024))?;
    assert_eq!(selected.authority, "market.example");
    assert!(matches!(HttpEgressContract::enforce_required(None, original, 0, None), Err(HttpEgressError::MissingContract)));
    assert!(matches!(contract.enforce_url("https://other.example/quotes/sku-118", 0), Err(HttpEgressError::AuthorityDenied { .. })));
    assert!(matches!(contract.enforce_url(original, 1), Err(HttpEgressError::RedirectLimitExceeded { .. })));
    assert!(matches!(contract.enforce_response_bytes(64 * 1_024 + 1), Err(HttpEgressError::ResponseTooLarge { .. })));
    // Literal protection still applies when a local authority is allowlisted.
    let mut local = contract.clone();
    local.allowed_authority_set.insert("127.0.0.1".to_owned());
    assert!(matches!(local.enforce_url("https://127.0.0.1/private", 0), Err(HttpEgressError::LoopbackDenied { .. })));
    println!("ALLOW selected URL contract; no DNS resolution or HTTP request performed");
    println!("REFUSE missing contract, another authority, redirect, excess bytes and loopback");
    Ok(())
}
Executed egress policy comparison
ALLOW selected URL contract; no DNS resolution or HTTP request performed
REFUSE missing contract, another authority, redirect, excess bytes and loopback

You should see one allowed attempt and one line of refusals: MissingContract, AuthorityDenied for other.example even when the uploaded record is valid, RedirectLimitExceeded on the first hop, ResponseTooLarge one byte past the ceiling, and LoopbackDenied for 127.0.0.1 even after it is added to the allowed authorities. The namespace labels the egress authority domain; it authenticates no tenant and grants no other endpoint.

The canary proves one exact release candidate on its own traffic

chio-finding-market-canary is its own binary crate with no public listener, and no tenant traffic routes through it. Before any network call, network_egress_contract builds a single-authority contract for the one HTTPS endpoint the operator named, with loopback, link-local, and IPv6 unique-local targets denied and redirects forbidden, and validates it against that endpoint before the canary's client exists.

load_network_finding_pool reads a private canonical chio.finding.network-canary-pool.v1 file listing 2 to 128 absolute finding paths, each verified, unexpired, and from one issuer. The canary publishes one listed finding the market does not hold, and require_fresh_network_publish fails the run unless that publish came back applied; a listed finding the market already holds must match the listed bytes exactly. A replayed retained artifact counts as no fresh canary effect.

candidate_binding_is_exact requires the deployed build's identity, the canary job's recorded candidate, and the operator profile's candidate to be the same 40-character lowercase-hex string, so a mismatch in case alone fails. The test kvm_job_is_bound_to_the_profile_candidate covers the match, a mismatch in the job or the profile, and an uppercase candidate refused even when all three agree. The job is a canonical chio.finding.kvm-canary-job.v1 file, kept separate from the pool. evaluate-canary takes the profile and an observation file, the same pattern as validate-hosted, and fails on rollback so promotion stops:

Terminalbash
chio finding operator evaluate-canary --profile <profile> --observation <observation>

Read more in operator commands.

A retracted finding quarantines the memory it fed

A buyer's agent may already have written a finding into its own memory when the finding is retracted. MemoryGovernanceGuard::with_config_and_retraction_resolver builds a memory-governance guard with a FindingRetractionGuardConfig and a FindingRetractionResolver; an embedder that installs it on its kernel checks each memory read against the finding's current status before the read is served.

Every read resolves the chain again: store and key to the write that produced them, the write to its delivery receipt, the delivery to the finding, and the finding to an authenticated status root. A read whose finding resolves to anything but a live status is denied and its store and key are quarantined locally; a key whose status returns to live is served again. Every resolver error, including a status root outside its validity window, collapses to the same guard Deny, and only the embedder's resolver keeps the specific cause. The test finding_quarantine_allows_only_fresh_live_exact_key_reads holds the rule; the resolver enforces the market's retract-and-propagate design on the memory side.

Read more in Finding Status for the signed status root, and the Threat Model for stale and forged statuses.

Each owner refuses in its own process, and only the edge speaks wire codes

Capability admission and egress are checked in process, so each reaches its immediate caller as a typed value. The hosted edge turns a store outcome into a fixed wire code for its external caller, and a kernel built with the retraction guard turns every resolver error into one signed Deny.

RefusalConditionReturned by
replay_rejected
A capability admission proof presented a second time
Hosted edge, from the Replay outcome
HttpEgressError::AuthorityDenied
The destination authority is outside the contract's allowed set
Egress contract, in process
HttpEgressError::LoopbackDenied
The request resolved to a loopback address
Egress contract, in process
Signed Deny, reason guard denied the request: guard "memory-governance" denied the request
A memory read's finding did not resolve to a live status, including FindingRetractionResolveError::StaleStatus
Retraction guard on the embedder's kernel

consume_capability_dpop_admission returns a replay as a plain Ok(HostedCapabilityAdmissionOutcome::Replay); the edge's authentication code, its only production caller, sends replay_rejected with the body "The proof was already used." (HostedEdgeError::ReplayRejected). The canary's candidate binding and the live retraction status stay separate checks from admission, and a resolver error fails closed.

For a refused transition, keep the original signature and compare its tenant, authority pair, epoch, configuration revision, checkpoints, and rollback window with the store's current state. For a refused HTTP attempt, repair the reviewed contract or destination at the egress owner. Each repair stays within its own owner's authority.

Next: operate the hosted profile