PlatformFleet Evidence
Cluster
Operator Reports
Report routes compose read models over one node's receipt and budget stores at request time: what is signed, what is capped, what never replicates.
Where the neighbours stop
chio-risk-comptroller verifier for an inbound signed risk report, a separately registered schema. This page owns the report routes themselves, including the one that returns chio.comptroller.surface-report.v1.Read models, composed per request, over one node’s disk
A Chio report is not a stored document. Every route on this page opens the node’s local SQLite receipt store, and sometimes its local budget store, runs a fixed set of queries, and returns the result. There is no cache, no cursor, no materialized view, and no report table. Two consecutive requests against the same node can differ, because the second one recomposes from whatever the last replication round left on disk.
None of these routes forwards to the leader. handle_operator_report, handle_comptroller_surface_report, handle_cost_attribution_report, handle_settlement_report, and handle_exposure_ledger_report each resolve a read principal, open a store, and answer locally. That is the published class, not an oversight: docs/standards/CHIO_BOUNDED_OPERATIONAL_PROFILE.md rates trust-control reads leader-local, shipped as "bounded clustered visibility over local SQLite-backed state", with "no globally linearizable control-plane view" as the explicit non-claim. A report from node B and a report from node C over the same filters are two observations, not one truth.
The status is registered. docs/standards/CHIO_OPERATOR_CONTROL_SURFACE_PROFILE.json carries status: operator_control_surfaces_qualified_locally and names six entries: operator-report, settlement-reconciliation, metered-billing-reconciliation, authorization-context, underwriting-credit-capital-liability, and comptroller-surface. Its own constraints block says what that does and does not mean: the profile proves the routes are "defined and exercised locally", it "does not prove independent third-party operator adoption", and promotion outside the operator boundary "still requires qualification bundles and explicit service-auth governance".
The report families
Eleven GET routes under /v1/reports/ are in scope here. They divide cleanly on two axes that matter more than their names: does the response carry a schema id, and does it carry a signature.
| Route | Query type | Schema id in body | Signed | CLI verb |
|---|---|---|---|---|
/v1/reports/operator | OperatorReportQuery | None | No | None |
/v1/reports/comptroller-surface | OperatorReportQuery | chio.comptroller.surface-report.v1 | No | None |
/v1/reports/cost-attribution | CostAttributionQuery | None | No | None |
/v1/reports/settlements | OperatorReportQuery | None | No | None |
/v1/reports/metered-billing | OperatorReportQuery | None | No | None |
/v1/reports/economic-receipts | OperatorReportQuery | None | No | None |
/v1/reports/economic-completion-flow | ExposureLedgerQuery | chio.economic-completion-flow.v1 | No | None |
/v1/reports/exposure-ledger | ExposureLedgerQuery | chio.credit.exposure-ledger.v1 | Yes | chio trust exposure-ledger export |
/v1/reports/behavioral-feed | BehavioralFeedQuery | chio.behavioral-feed.v1 | Yes | chio trust behavioral-feed export |
/v1/reports/authorization-context | OperatorReportQuery | chio.oauth.authorization-context-report.v1 | No | chio trust authorization-context list |
/v1/reports/authorization-review-pack | OperatorReportQuery | chio.oauth.authorization-review-pack.v1 | No | chio trust authorization-context review-pack |
Seven of the eleven have no CLI path at all. The trust-control client does carry operator_report, comptroller_surface, and cost_attribution_report methods, and all three are marked #[allow(dead_code)] under an in-tree comment saying they are "kept for API parity with the trust-control service surface even though the current CLI command set does not invoke it directly". Reach those three over HTTP with a bearer token; there is no chio subcommand to reach for.
Only one of these schemas is registered
chio.comptroller.surface-report.v1 is the single report schema on this page that appears in spec/schemas/registry.json, has a JSON Schema file at spec/schemas/chio-comptroller/v1/surface-report.schema.json, and is registered in chio-core-types/src/signed_artifact.rs under artifact kind chio_comptroller_surface_report. chio.credit.exposure-ledger.v1, chio.behavioral-feed.v1, chio.oauth.authorization-context-report.v1, chio.oauth.authorization-review-pack.v1, and chio.economic-completion-flow.v1 appear in neither the registry nor the signed-artifact schema list. A consumer wanting a pinned, externally checkable contract has exactly one of these to pin, and it is the one with no signature on the wire.What /v1/reports/operator composes
build_operator_report runs eight read models in a fixed order and packs them into one struct with a generated_at stamp and an echo of the filters. Every one of the eight maps its own store error to 500, so a single failing sub-query fails the whole report and the response body is the store error string.
let activity = receipt_store
.query_receipt_analytics(&query.to_receipt_analytics_query())
.map_err(|error| plain_http_error(StatusCode::INTERNAL_SERVER_ERROR, &error.to_string()))?;
let cost_attribution = receipt_store
.query_cost_attribution_report(&query.to_cost_attribution_query())
.map_err(|error| plain_http_error(StatusCode::INTERNAL_SERVER_ERROR, &error.to_string()))?;
let budget_utilization = build_budget_utilization_report(receipt_store, budget_store, query)?;
let compliance = receipt_store.query_compliance_report(query)...;
let settlement_reconciliation = receipt_store.query_settlement_reconciliation_report(query)...;
let metered_billing_reconciliation = receipt_store.query_metered_billing_reconciliation_report(query)...;
let authorization_context = receipt_store.query_authorization_context_report(query)...;
let shared_evidence = receipt_store.query_shared_evidence_report(&query.to_shared_evidence_query())...;| Field | What it counts | Store |
|---|---|---|
activity | ReceiptAnalyticsMetrics over every matching receipt: totalReceipts, allow, deny, cancelled, incomplete, charged and attempted cost, plus derived reliabilityScore, complianceRate, and budgetUtilizationRate. Also broken out by agent, tool, and time bucket. | Receipts |
costAttribution | Delegation-chain attribution: byRoot, byLeaf, and per-receipt rows carrying the full chain of hops plus lineageComplete. | Receipts |
budgetUtilization | Per-grant usage, resolved against capability scope. The only part that opens a second database. | Budgets + receipts |
compliance | Checkpoint and lineage coverage: evidenceReadyReceipts, uncheckpointedReceipts, lineageGapReceipts, coverage rates, and the evidence-export scope this filter set would produce. | Receipts |
settlementReconciliation | The pending and failed settlement backlog, joined to the mutable sidecar state. | Receipts |
meteredBillingReconciliation | Post-execution metered evidence mismatches: evidenceMissing, exceedsQuotedUnits, exceedsMaxBilledUnits, exceedsQuotedCost, financialMismatch. | Receipts |
authorizationContext | The derived authorization-details and transaction-context projection, embedded whole, profile and all. | Receipts |
sharedEvidence | Federated evidence-share references and how many local receipts each matched. | Receipts |
The budget half reads a different store, and a different number
build_budget_utilization_report lists usages from the budget store, then resolves each row’s capability scope by reading get_lineage out of the receipt store and parsing grants_json. That is where toolServer, toolName, maxInvocations, maxTotalCostUnits, and currency come from. A budget row whose lineage snapshot is absent still appears, with scopeResolved: false and scopeResolutionError set to capability lineage snapshot not found for budget row, and it is counted in both rowsMissingLineage and rowsMissingScope.
Read the tool filters against that ordering. A row whose scope did not resolve has no tool_server, so a request that passes toolServer drops it silently rather than reporting it as unresolved. Both counters are incremented after the three continue guards, so a toolServer, toolName, or agentSubject filter removes every unresolved row before it can be counted. A capabilityId filter does not: it is applied by list_usages and drops nothing on scope grounds. since and until are never read by this half at all, so a time window narrows the receipt-side numbers next to it and leaves the budget rows untouched.
totalCostCharged is committed cost, not realized spend
committed_cost_units(), which chio-kernel/src/budget_store.rs defines as a checked total_cost_exposed + total_cost_realized_spend. Outstanding holds are inside that number. So is spend already reconciled against a hold. A budget row reading totalCostCharged: 850 may have realized nothing yet. remainingCostUnits is maxTotalCostUnits minus that same committed figure, saturating at zero. nearLimit trips at a utilization rate of 0.8 or above on either dimension, or whenever exhausted is already true; exhausted is used greater than or equal to limit. Neither threshold is configurable. What those numbers mean once several nodes are writing is Budgets Across Nodes.Every part is capped, and most caps report themselves
OperatorReportQuery carries a group limit and six row limits, each with a default and each clamped into 1..=200 before it reaches SQL. Five clamp in an _or_default accessor on the query itself. groupLimit and attributionLimit are passed through raw and clamped inside the store query that consumes them. The clamp is silent: asking for 5,000 rows returns 200 and no warning.
| Query field | Default | Ceiling | Governs |
|---|---|---|---|
groupLimit | 50 | MAX_ANALYTICS_GROUP_LIMIT 200 on the analytics half, MAX_SHARED_EVIDENCE_LIMIT 200 on the shared-evidence half | Analytics grouping and shared-evidence rows |
attributionLimit | 100 | MAX_COST_ATTRIBUTION_LIMIT 200 | Cost-attribution receipt rows |
budgetLimit | 50 | MAX_OPERATOR_BUDGET_LIMIT 200 | Budget utilization rows |
settlementLimit | 50 | MAX_SETTLEMENT_BACKLOG_LIMIT 200 | Settlement backlog rows |
meteredLimit | 50 | MAX_METERED_BILLING_LIMIT 200 | Metered-billing rows |
authorizationLimit | 50 | MAX_AUTHORIZATION_CONTEXT_LIMIT 200 | Authorization-context rows |
economicLimit | 50 | MAX_ECONOMIC_RECEIPT_LIMIT 200 | Economic receipt projection rows |
Most halves report their own truncation. budgetUtilization, costAttribution, settlementReconciliation, meteredBillingReconciliation, authorizationContext, and sharedEvidence each carry a matching* count computed without the limit and a truncated boolean, and every one of them but sharedEvidence also carries a returned* count. Two halves do not. activity is a ReceiptAnalyticsResponse, whose only fields are summary, byAgent, byTool, and byTime, so the three groupings are cut at groupLimit with nothing in the body saying they were. compliance carries matchingReceipts and neither a returned count nor a flag, because it aggregates the whole matching set under no row limit at all. Where the boolean exists, alert on it rather than on the row count, and remember there is no cursor: a truncated report has no page two. Narrow the filters or the time window instead.
One field on the query is never accepted from a caller. read_context is #[serde(skip)] on OperatorReportQuery, BehavioralFeedQuery, SharedEvidenceQuery, and CostAttributionQuery, under a doc comment on each that reads "auth-derived read authority. This is never accepted from request bodies." The handler overwrites it from the resolved principal before the store is opened.
The comptroller-surface projection, and the anchor it demands
/v1/reports/comptroller-surface is a pure projection over two read models it does not own. It builds a full OperatorReport, builds an ExposureLedgerReport against the same principal, and keeps five projected fields: exposurePositions from the credit half, and decisionSummary, settlementReconciliation, budgetUtilization, and filters from the operator half. A sixth field is declared and never filled. from_parts sets source_refs to ComptrollerSurfaceSourceRefs::default(), and nothing in the workspace ever populates operatorReportRef, exposureLedgerRef, or riskComptrollerReportRef, so every response carries sourceRefs: {}. The schema reserves three digest slots binding the projection back to the read models it projects, and the projection fills none of them. Two further fields, executionNonceRef and holdRef, are reserved and omitted from JSON entirely until a later schema revision; test_comptroller_surface_report_endpoint asserts their absence.
The projection validates itself before it is returned, and the check is fail-closed on overflow rather than saturating.
pub fn validate_consistency(&self) -> Result<(), String> {
for position in &self.exposure_positions {
if position.governed_max_exposure_units == 0 {
continue;
}
let Some(outstanding) = position.reserved_units.checked_add(position.pending_units)
else {
return Err(format!(
"exposure position {} outstanding holds overflow u64 (reserved {} + pending {})",
position.currency, position.reserved_units, position.pending_units
));
};
if outstanding > position.governed_max_exposure_units {
return Err(format!(
"exposure position {} outstanding holds {} exceed governed ceiling {}",
position.currency, outstanding, position.governed_max_exposure_units
));
}
}
Ok(())
}Read the exemptions. A governed ceiling of zero means "no governed ceiling" and is skipped, so a currency position with no governed maximum never fails this check no matter how large its holds are. The module comment states the other boundary outright: this is a credit-domain check only, and it deliberately does not compare against kernel budget cost units, "whose unit mapping to exposure units is undefined". The report carries both numbers side by side and asserts no relationship between them. A failed check is a 500 with comptroller surface consistency check failed: prefixed onto the message.
This route refuses an unfiltered request
OperatorReportQuery::to_exposure_ledger_query copies the four filters straight across, and ExposureLedgerQuery::validate requires at least one of capability_id, agent_subject, or tool_server. A bare GET /v1/reports/comptroller-surface therefore answers 400 exposure ledger queries require at least one anchor: --capability, --agent-subject, or --tool-server, while the identical request against /v1/reports/operator answers 200. The same validator rejects a toolName without a toolServer, any filter value that is empty or carries surrounding whitespace, and a since later than its until. Two routes take the same query type and disagree on what a valid one is.The two halves are also computed over different windows. to_exposure_ledger_query sets receipt_limit: None and decision_limit: None, which default to 100 receipts and 50 decisions, and no query field on the operator query can raise them. The decision counts, meanwhile, come from operator.activity.summary, which is aggregated over every matching receipt with no limit at all. ComptrollerSurfaceReport::from_parts then copies exposure.positions and drops exposure.summary, so the exposure half’s own truncatedReceipts and truncatedDecisions flags never reach the response. Positions computed over the first 100 matching receipts sit next to decision counts computed over all of them, and nothing in the body says so.
Exposure, settlement, and the sidecar that does not replicate
/v1/reports/exposure-ledger accumulates per-currency positions across nine counters (governedMaxExposureUnits, reservedUnits, settledUnits, pendingUnits, failedUnits, provisionalLossUnits, recoveredUnits, quotedPremiumUnits, activeQuotedPremiumUnits) and pairs them with underwriting decision entries. Its summary carries mixedCurrencyBook, true whenever more than one currency appears, and its supportBoundary block is a constant: governed receipts, underwriting decisions, and settlement reconciliation are marked authoritative; cross-currency netting, claim adjudication, and recovery lifecycle are marked unsupported. Positions in different currencies are never summed.
/v1/reports/settlements is a backlog, not a ledger. Its SQL filters json_extract(r.raw_json, '$.metadata.financial.settlement_status') IN ('pending', 'failed'), so a receipt that settled cleanly never appears. Each row left-joins a mutable sidecar table, settlement_reconciliations, keyed by receipt_id and defaulting to open. The derived actionRequired flag is the conjunction: the signed status is pending or failed and the operator has not set the sidecar to reconciled or ignored. The four sidecar states are open, reconciled, ignored, and retry_scheduled, and retry_scheduled still counts as actionable.
Reconciliation state is node-local and stays node-local
POST /v1/settlements/reconcile validates the service token, opens the local receipt store, and calls upsert_settlement_reconciliation. It does not forward to the leader, and the settlement_reconciliations and metered_billing_reconciliations tables appear in no cluster path at all: ClusterStateSnapshotResponse carries revocations, tool receipts, child receipts, lineage, budget projections, budget mutation events, and authority, and nothing else, and the five internal delta routes cover the same set. Mark a backlog row reconciled through node B and node C still reports it as actionRequired: true, forever. Route reconciliation writes to one node deliberately, or expect two operators to work the same backlog twice. The mechanism that would have replicated it is described in Replication & Convergence; it does not cover these tables.The signed half of the row is unaffected by any of this. The receipt carries financial.settlement_status inside its signature and that value replicates with the receipt; only the operator’s annotation is local. Same shape on the metered side: POST /v1/metered-billing/reconcile writes evidence and a state into a local sidecar, and /v1/reports/metered-billing reads it back on that node only. The economics of the underlying obligation belong to Reconciliation.
Which reports are signed, by which key, and what that proves
Two of the eleven routes sign. /v1/reports/exposure-ledger wraps its body in SignedExposureLedgerReport and /v1/reports/behavioral-feed in SignedBehavioralFeed. Both are SignedExportEnvelope<T>: three fields, body, signer_key, and signature, with the signature taken over the canonical JSON of the body.
The key comes from load_behavioral_feed_signing_keypair, which takes exactly one of --authority-seed-file or --authority-db. Passing both is an error reading behavioral feed export requires either --authority-seed-file or --authority-db, not both; passing neither is an error reading ... so the export can be signed. On the seed path it loads or creates a keypair from the file. On the database path it calls SqliteCapabilityAuthority::local_keypair.
A signed report is signed by the node, not by the cluster
local_keypair and current_keypair are two different reads of the same database.pub fn current_keypair(&self) -> Result<Keypair, AuthorityStoreError> {
self.read_current_keypair()
}
pub fn local_keypair(&self) -> Result<Keypair, AuthorityStoreError> {
let connection = Self::open_connection(&self.path)?;
Self::read_keypair_from_connection(&connection)
}read_current_keypair also reads the replicated authority status and refuses with local signing seed public key ... does not match replicated authority public key ... when the two disagree. local_keypair performs no such comparison. Report signing takes the second path, so a follower whose replicated public key has moved past its local seed still signs, with its own seed-derived key. Do not treat signer_key as the cluster’s authority key. The two can differ, and a rotation is exactly when they do. What replicates and what does not is Authority & Rotation.Read the verification helper narrowly too. SignedExportEnvelope::verify_signature verifies the signature against the signer_key carried inside the same envelope. It proves the body was not edited after signing. It proves nothing about who signed, because the claimed signer travels with the claim. A consumer that cares must pin the expected public key out of band and compare it. That is the same discipline the signed-report builders already apply internally: both the behavioral feed and the credit scorecard load the keypair first and seed the reputation trust set from its public key, with a comment noting that chio-reputation::receipt_integrity_valid fails closed on an empty set.
There is a third signed report in the tree with no way to ask for it. build_signed_comptroller_surface_report exists, revalidates consistency, and produces a SignedComptrollerSurfaceReport. Its only caller in the workspace is its own unit test, signed_comptroller_surface_report_verifies_and_is_canonical. No route returns it and no CLI verb produces it. The HTTP route returns the unsigned ComptrollerSurfaceReport.
Read authority and denial behavior
Every route on this page resolves an admin read context before it opens anything. resolve_admin_report_read_context accepts only ResolvedControlReadPrincipal::AdminService and answers a tenant read token with 403 {surface} requires admin receipt read authority, where the string is the report’s own name. Which token produces which principal is Tenancy & Read Scoping.
A second, independent gate sits inside the store. A dozen report queries call the same helper before touching SQL, and it names the reason directly.
fn require_admin_receipt_read_context(
context: Option<&ReceiptReadContext>,
surface: &str,
) -> Result<(), ReceiptStoreError> {
match context {
Some(ReceiptReadContext { boundary: ReceiptReadBoundary::AdminAll, .. }) => Ok(()),
Some(ReceiptReadContext { boundary: ReceiptReadBoundary::TenantScoped { .. }, .. }) =>
Err(ReceiptStoreError::ReadBoundary(format!(
"{surface} requires admin receipt read authority until tenant-scoped report filtering is implemented"
))),
None => Err(ReceiptStoreError::ReadBoundary(format!(
"{surface} requires an explicit receipt read context"
))),
}
}The refusal says why, and the why is worth reading: reports are admin-only because tenant-scoped report filtering does not exist, not because reporting is privileged by design. The handler gate fires first on every HTTP path, so this one is defense in depth rather than an observable behavior, and it is the gate that catches a library caller that forgot to set a context at all.
| Condition | Status | Body |
|---|---|---|
| Tenant read token on any report route | 403 | {surface} requires admin receipt read authority |
Service started without --receipt-db | 409 | trust control service requires --receipt-db |
Service started without --budget-db, on a route that needs it | 409 | trust control service requires --budget-db |
/v1/reports/behavioral-feed with no receipt database path | 409 | behavioral feed export requires --receipt-db on the trust-control service |
| Exposure-anchored query with no capability, agent, or tool server | 400 | exposure ledger queries require at least one anchor: ... |
| Comptroller-surface consistency check fails | 500 | comptroller surface consistency check failed: ... |
| No signing key configured on a signed route | 500 | ... requires --authority-seed-file or --authority-db so the export can be signed |
Any store failure inside build_operator_report | 500 | The store error string, verbatim |
Informational-only: what a derived projection may not become
docs/standards/CHIO_BOUNDED_OPERATIONAL_PROFILE.md rates the authorization-context report and the reviewer pack informational-only, and neither response says so. The reviewer pack gets closer than the report does: it embeds a ChioOAuthAuthorizationMetadataReport whose supportBoundary block is twelve booleans, among them delegatedCallChainProjection, senderConstrainedProjection, and runtimeAssuranceProjection, plus a discovery.discoveryInformationalOnly flag. The authorization-context report carries no boundary block at all: its four fields are schema, profile, summary, and receipts. Both are given the same two columns in the profile. The shipped truth is "derived projection over signed receipt metadata". The explicit non-claim is that the projection "does not upgrade asserted call-chain fields into verified upstream truth". The same document states the rule in general form: no report or export surface may collapse asserted lineage into verified truth.
That rule is enforced, not just stated. delegated_call_chain_is_sender_bound returns false immediately whenever the call chain’s evidence_class is Asserted, before it inspects anything else, and a row that claims senderConstraint.delegatedCallChainBound without corroborated provenance is rejected as an invalid profile with senderConstraint.delegatedCallChainBound requires corroborated call-chain provenance. The summary counts the three classes separately, as assertedCallChainReceipts, observedCallChainReceipts, and verifiedCallChainReceipts, so a reviewer can see the split rather than a single total.
Two more scope notes ride along in the same report family, and both are text the report writes about itself. compliance.directEvidenceExportSupported is false whenever a tool filter is set, because EvidenceExportQuery can scope by capability, agent, and time window but not by tool. When that happens, exportScopeNote says so: tool filters narrow the operator report only; direct evidence export can scope by capability, agent, and time window. The child-receipt note has two forms, and EvidenceExportQuery::child_receipt_scope picks between them. A capability or agent scope carrying a time window reads time_window_context_only and notes that child receipts are included only as time-window context for this export scope. The same scope without a time window, and any tenant-filtered or tenant-scoped export, reads omitted_no_join_path with child receipts are omitted for this export scope because no capability/agent join exists yet. Outside those cases, a query with neither a subject nor a capability scope reads full_query_window and writes no note at all. What that means for a package you hand to someone else is Evidence Export.
A count is not a proof
compliance.evidenceReadyReceipts and uncheckpointedReceipts tell you how much of the matching set is covered by a local Merkle checkpoint. Checkpoints do not replicate, so those two numbers describe the node you asked and no other. The bounded profile rates the receipt and checkpoint plane local-only, with "no public transparency-log, cross-node append-only coverage, or strong non-repudiation semantics" as the non-claim. See Receipt Aggregation for why a fleet-wide coverage figure cannot be read off one node.The seven market claims a verifier policy may not require
One crate in this family verifies rather than reports. chio-trust-market-context checks the trust-market section of a transaction proof bundle: provider discovery, provider selection, trust scorecards, portable reputation imports, SLA commitments and performance, collateral, guarantees, adjudication jurisdiction, and the risk-comptroller report that binds them to reserves. Its README states its scope in one sentence: it "verifies a bundle after the fact and does not select providers, update reputation, enforce SLAs live, or authorize settlement".
Among its responsibilities is a refusal list. The crate enumerates seven market capabilities Chio does not implement, and rejects any verifier policy that requires one of them.
const BLOCKED_MARKET_CLAIMS: &[&str] = &[
"claim.market.permissionless_provider_marketplace_operated",
"claim.market.global_trust_score_published",
"claim.market.liquidity_pool_operated",
"claim.market.risk_syndication_operated",
"claim.market.underwriter_market_operated",
"claim.market.autonomous_guarantee_product_sold",
"claim.market.slashing_court_operated",
];In plain terms, Chio does not operate a permissionless provider marketplace, does not publish a global trust score, does not operate a liquidity pool, does not syndicate risk, does not run an underwriter market, does not sell autonomous guarantee products, and does not operate a slashing court. A verifier policy that lists any of these under required_claims is rejected before a single artifact is parsed, by reject_required_unsupported_market_claims, with unsupported market claim cannot be required: {claim}.
The check has a second direction that is easy to miss. validate_unsupported_claims requires the policy’s unsupported_claims list to be non-empty and to contain only entries from that same seven. An empty list fails with unsupported market claims missing; an entry outside the set fails with unknown unsupported market claim: {claim}. A policy must therefore disclose the refusals explicitly, and only then does the verifier record claim.trust_market.unsupported_market_claims_limited among its verified claims. Silence about what is not supported is itself a verification failure. The report format the verifier binds to is Risk Comptroller Reports.
Guarantees and limits
| Status | Claim | Evidence |
|---|---|---|
| Shipped | The eleven GET /v1/reports/* routes on this page compose read models at request time from the local receipt store, and two of them also from the local budget store. None caches, pages, or forwards to the leader. | service_runtime/router.rs; receipt_handlers.rs; risk_finance_handlers/exposure.rs |
| Registered | Operator control routes are declared and locally exercised, with comptroller-surface naming its own witness test. | docs/standards/CHIO_OPERATOR_CONTROL_SURFACE_PROFILE.json, status: operator_control_surfaces_qualified_locally |
| Proved by test | The operator report returns analytics counts, budget dimensions with limits and remainders, checkpoint coverage counts, the export scope note, and cost-attribution rows carrying budget authority metadata. | test_operator_report_endpoint in crates/products/chio-cli/tests/receipt_query_export.rs |
| Proved by test | The comptroller-surface route returns chio.comptroller.surface-report.v1, allow and deny counts, an exposurePositions array, and omits both reserved linkage fields. | test_comptroller_surface_report_endpoint, the witness named in the control profile |
| Proved by test | The consistency check rejects outstanding holds above a governed ceiling, treats a zero ceiling as no ceiling, and fails closed rather than saturating when reserved plus pending overflows. | Four tests in operator_report/comptroller_surface.rs |
| Shipped | Read authority is auth-derived and never caller-supplied. read_context is #[serde(skip)] on all four query types and is overwritten by the handler. | operator_report/queries.rs; cost_attribution.rs; every handler in receipt_handlers.rs |
| Limit | Reports are admin-only. A report query carries no tenant predicate, so there is no narrower authority to hand out: a tenant read token is refused 403 at the handler and again at the store. | resolve_admin_report_read_context; require_admin_receipt_read_context |
| Limit | Every list is capped at 200 and silently clamped, and there is no cursor on any report route. A truncated report has no second page. | The MAX_* constants in operator_report/constants.rs, cost_attribution.rs, and receipt_analytics.rs; clamp(1, ...) in each accessor or store query |
| Limit | The comptroller-surface report drops the exposure half’s truncation flags and computes its exposure positions over at most 100 receipts and 50 decisions, while its decision counts cover the whole matching set. | ComptrollerSurfaceReport::from_parts copies positions only; to_exposure_ledger_query passes None for both limits |
| Limit | Settlement and metered-billing reconciliation state never leaves the node it was written on. It is absent from the cluster snapshot and from all five internal delta routes, and the write path does not forward to the leader. | ClusterStateSnapshotResponse in service_types/cluster_budget.rs; handle_record_settlement_reconciliation |
| Limit | A signed report is signed with the node’s local seed key. local_keypair skips the replicated-key fence that current_keypair applies, so signer_key may differ from the cluster authority key after a rotation. | SqliteCapabilityAuthority::local_keypair vs read_current_keypair in chio-store-sqlite/src/authority.rs |
| Limit | verify_signature checks the body against the key embedded in the same envelope. It detects tampering, never impersonation. Pin the expected key out of band. | SignedExportEnvelope::verify_signature in chio-core-types/src/receipt/lineage.rs |
| Informational-only | Authorization-context reports and reviewer packs are derived projections over signed receipt metadata and do not upgrade asserted call-chain fields into verified upstream truth. | CHIO_BOUNDED_OPERATIONAL_PROFILE.md guarantee table; delegated_call_chain_is_sender_bound returning false on Asserted |
| Not implemented | Cross-currency netting, claim adjudication, and recovery lifecycle on the exposure ledger. All three are constants set to false in the report’s own support boundary. | ExposureLedgerSupportBoundary::default in chio-credit/src/lib.rs |
| Not wired | A signed comptroller-surface export. build_signed_comptroller_surface_report compiles and is tested, and nothing calls it: no route, no CLI verb. | Its only workspace caller is signed_comptroller_surface_report_verifies_and_is_canonical in the same file |
| Not wired | CLI access to the operator, comptroller-surface, and cost-attribution reports, whose three client methods exist and carry #[allow(dead_code)] with a comment saying the command set does not invoke them; and to the settlement report, which has no client method at all. | service_runtime/client/operations.rs; SETTLEMENT_REPORT_PATH appears in the router and the path constants and nowhere in the client |
| Unregistered | Five of the six report schema ids have no entry in spec/schemas/registry.json and no schema file. Only chio.comptroller.surface-report.v1 is registered. | spec/schemas/registry.json; chio-core-types/src/signed_artifact.rs |
| Not claimed | A globally linearizable view. Reports are leader-local: bounded clustered visibility over local SQLite state, with no cross-node read consistency claim. | CHIO_BOUNDED_OPERATIONAL_PROFILE.md, trust-control reads row |
| Refused | Seven market capabilities cannot be required by a verifier policy: permissionless marketplace, published global trust score, liquidity pool, risk syndication, underwriter market, autonomous guarantee sales, slashing court. Their disclosure is mandatory and closed to that set. | BLOCKED_MARKET_CLAIMS, reject_required_unsupported_market_claims, validate_unsupported_claims |
Next Steps
- Receipt Aggregation · what converges into the store these reports read, and the checkpoint plane that does not
- Tenancy & Read Scoping · the two bearer tokens, and why only one of them can ask for a report
- Budgets Across Nodes · what committed cost means once several nodes are writing to the same grant
- Evidence Export · the digest-bound package a report cannot substitute for
- Risk Comptroller Reports · the verifier on the other side of the boundary, and the report it binds to a proof bundle