Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ciris-server"
version = "0.5.194" # 0.5.194 = THE READ API STOPS STARVING ON A SMALL HOST. CIRISServer#501: the canonical binds :4243 and never accepts — confirmed live INSIDE the container netns (`LISTEN Recv-Q 3`, curl timing out at 0 bytes, ONE tokio worker at 99.9% while every other thread idles). NOT a .193 regression: 0.5.192 reproduces it, so the rollback did not restore service. TWO causes, and either alone leaves it broken. (1) `prime_trusted_peers` was awaited INLINE at compose.rs:711 while `read_api_bind` sits ~180 lines below, so TIME-TO-SERVING SCALED WITH THE DIRECTORY — it walks `list_all_transport_destinations()`, 11,034 rows on the canonical against 748 keys, because a row is written per binding and pruned NEVER. 33s of boot with the socket bound and nothing accepted. Now spawned (announces root peers progressively anyway, so no later stage needs every peer rooted), yielding per batch (tokio reschedules only at an await that PENDS, and every await in that loop can resolve ready, so it owned its worker start to finish), and no longer paying two `knows_peer` probes plus an INFO line per peer purely to fill log fields. (2) A bare `#[tokio::main]` sizes to core count = TWO workers on 2 vCPU; one blocking task and HTTP is unschedulable. `MIN_WORKER_THREADS = 4` as a FLOOR, never a cap — a 32-core canonical still gets 32. THE FLOOR ALONE WOULD NOT HAVE CLOSED IT: more workers shortens the window, but the read API still could not bind until the prime returned. BACKPRESSURE ON THE ONE TABLE THAT IS ACTUALLY AN OBSERVATION LOG: persist v38.6.0 ships `prune_announced_peers_not_seen_since` and it had no caller here; now wired into the hourly retention pass at 30 days (vs the corpus's 90 — losing an announcement costs a round-trip, losing a trace costs the trace), capped at 2,000 rows/pass so hygiene cannot become the long task that starves accept. `transport_destinations` is deliberately NOT pruned even though it is the bigger table and the one boot prime walks: the measurement killed the premise (Codex, PR #502). ALL 11,034 rows have `last_seen_at IS NULL` — there are no observations in it, only assertions — and a 30-day prune would delete 5,408 rows, 100% of them never-observed asserted routes. Its keep-predicate spares SIGNED rows, but `TransportDestination` has no signature field at all, so every locally-asserted row is unsigned and prunable — including the accord-quorum-authorized canonical address update, which an explicit-hash canonical cannot announce back. Bounding it needs a predicate persist cannot express (stale OBSERVATION vs old assertion), asked for upstream; the outage is closed by taking the prime OFF the critical path, which makes table size cost background work rather than availability. KEYS ARE NOT PRUNED EITHER — a federation_keys row is an identity ANCHOR and deleting it makes every row it authored unverifiable; keys need EXPIRY, unreachable until CIRISVerify#267 lands. ALSO FIXED: .193's mesh-status refresh loop ran `storage_summary()` every 30s, whose SQLite path is `SUM(pgsize) FROM dbstat` — a walk of EVERY PAGE of a 1.3 GiB database, inline on a worker. A module whose own doc called an uncached read an amplification vector then scheduled it on a timer. Now lazy, single-flight (one walk in flight ever; the flag clears on Drop so a panic cannot wedge the surface stale), off the runtime via spawn_blocking, TTL 60s→900s pinned by test. Substrate unchanged: persist v38.6.0 / edge v18.11.0 / verify v13.6.1.
version = "0.5.195" # 0.5.195 = A NODE VOUCHES ABOUT ITSELF ON infra:attest. Third attempt at one thing, the first that reads the constitution instead of reasoning around it. THE AUTHORITY WAS ALREADY CONFERRED: every node holds `infra:attest` ("vouch as the delegator's infrastructure") from its owner-binding, the canonical from the accord's charter, conferred SO THE NODE CAN VOUCH ABOUT THE DELEGATOR'S INFRASTRUCTURE — and "I am at capacity" is the fact only the infrastructure can observe. Clause E: a node "may sign its own refusal without new authority". THE DIMENSION WAS ALREADY RESERVED: CC 3.1 `config:{scope}` ("a node's declared operating configuration… only ever about the emitting node"), with CC 3.4.5's emitter rule SELF-OR-OWNER ("a self-report; a third-party assertion of what you are running is a rumour"). So `config:load` joins admission/replication/moderation/transport, attesting == attested == subject by construction, `witness_relation: self`, federation scope so it REPLICATES, `expires_at` a real signed instant persist enforces at read. THE TWO WRONG VERSIONS: automating `OP_SELF_SHED` (an admin_action, refused at the write door without a delegation_id a node does not have); then a HardCaseEvent with an expiry (on NO replication plane, so "the artifact a peer reads" could never reach a peer); then an over-correction to "a node has no standing at all", which CC 4.2.1 does not say — that section governs `mesh_config`, the ROOT's plane, which changes what OTHER nodes carry. TWO PLANES, TWO AUTHORS, ONE REASON: the root relieves ACROSS nodes; a node vouches ABOUT itself. ONLY THIS NODE'S OWN STALL MAY RENEW: `/proc/pressure` describes the whole box, and a noisy neighbour would make this node ATTEST UNDER ITS OWN SIGNATURE that it is shedding — a false self-report to every peer. Only a Cgroup-scoped `full`-line reading at degradation's own bound counts; host scope, no `full` line, or PSI unavailable renews nothing, and the loop acts on the FRESH probe rather than the registry (which deliberately preserves a stale warning when PSI vanishes). TTL = 3 × 60s; recovery is the lift. Under the NODE key, not the engine's. Filed: CIRISConstitution#96 (RC4: two planes, `config:load` as a named scope), CIRISPersist#777 (the root's `load.ceiling` key), CIRISPersist#778 (CC 3.4.5 self-or-owner is NOT enforced at admission for config:* — anyone can attest what a node is running). Substrate unchanged: persist v38.6.0 / edge v18.11.0 / verify v13.6.1.
edition = "2021"
# MSRV floor is set by the substrate: ciris-verify v5.2.0 requires 1.86
# (persist v6.0.1 is 1.83). Build with the higher of the two.
Expand Down
16 changes: 8 additions & 8 deletions evidence/CIRISServer.cc_impl.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
#
# ─── RESOLVED ──────────────────────────────────────────────────────────────────
decimal_id claim_id repo path#symbol crate@version
3.1 CLM-nsproc-dimension CIRISServer src/compose_policy.rs#polarity_for ciris-server@v0.5.194
4.4.2 CLM-nsproc-aggregation-policy CIRISServer src/compose_policy.rs#polarity_for ciris-server@v0.5.194
4.4.3.4.3 CLM-nsproc-cohort-scope CIRISServer src/graph_config.rs#set_config ciris-server@v0.5.194
4.2.2.1 CLM-nsproc-attestation-evidence CIRISServer src/hardware_attestation.rs#admit_hardware_class_against_root ciris-server@v0.5.194
2.6.6.1 CLM-location CIRISServer src/location.rs#mint_location_proof ciris-server@v0.5.194
2.6.6 CLM-canonicalization-cell CIRISServer src/location.rs#mint_location_proof ciris-server@v0.5.194
3.4.5 CLM-capacity-score CIRISServer src/scorer.rs#score_and_emit ciris-server@v0.5.194
3.3.7 CLM-consent-directed CIRISServer src/peer.rs#emit_replication_consent ciris-server@v0.5.194
3.1 CLM-nsproc-dimension CIRISServer src/compose_policy.rs#polarity_for ciris-server@v0.5.195
4.4.2 CLM-nsproc-aggregation-policy CIRISServer src/compose_policy.rs#polarity_for ciris-server@v0.5.195
4.4.3.4.3 CLM-nsproc-cohort-scope CIRISServer src/graph_config.rs#set_config ciris-server@v0.5.195
4.2.2.1 CLM-nsproc-attestation-evidence CIRISServer src/hardware_attestation.rs#admit_hardware_class_against_root ciris-server@v0.5.195
2.6.6.1 CLM-location CIRISServer src/location.rs#mint_location_proof ciris-server@v0.5.195
2.6.6 CLM-canonicalization-cell CIRISServer src/location.rs#mint_location_proof ciris-server@v0.5.195
3.4.5 CLM-capacity-score CIRISServer src/scorer.rs#score_and_emit ciris-server@v0.5.195
3.3.7 CLM-consent-directed CIRISServer src/peer.rs#emit_replication_consent ciris-server@v0.5.195
#
# ─── DECLARED GAPS ─────────────────────────────────────────────────────────────
# Controls the Constitution attributes to this repo that are NOT enforced here.
Expand Down
19 changes: 19 additions & 0 deletions src/compose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1545,6 +1545,21 @@ pub async fn serve_with_adapter(cfg: ServerConfig, adapter: Arc<dyn Adapter>) ->
// the previous node's observer id and counts (Codex, PR #502).
crate::mesh_status::invalidate();

// The node vouches about ITSELF on `infra:attest` — a `config:load`
// self-attestation (CC 3.1, CC 3.4.5 self-or-owner), short-lived, replicated.
// Under the NODE key, not the engine's: on an agent-carrying node those differ
// and this is a statement about the infrastructure. It declares; enforcement
// stays cooperative. It is not `mesh_config` — that plane is the root's.
crate::compose_status::phase("load_observer");
let (load_observer_sd, load_observer_join) = crate::load_shed::spawn(
Arc::clone(&engine),
// The NODE's pen. On a split node the engine signs as the actor, and a row
// stamped as the node but signed by the actor fails verification at
// admission — `node_resolution.signer` exists for exactly this.
crate::load_shed::NodePen::from_resolution(&node_resolution),
node_resolution.node_key_id.clone(),
);

crate::compose_status::phase("retention_loop");
let (retention_sd_tx, retention_sd_rx) = watch::channel(false);
let retention_join = {
Expand Down Expand Up @@ -1633,6 +1648,10 @@ pub async fn serve_with_adapter(cfg: ServerConfig, adapter: Arc<dyn Adapter>) ->
// first would race its shutdown branch against a `changed()` error break.
let _ = retention_sd_tx.send(true);
let _ = retention_join.await;
// The observer holds an Arc<Engine>; on the in-process restart path a detached
// one would keep renewing attestations against the OLD node.
let _ = load_observer_sd.send(true);
let _ = load_observer_join.await;
// Tear down the mesh-config consumer refresh loop (CIRISServer#365). Its
// readers (the read API, the ingest router) are already gone by here.
let _ = mesh_config_sd_tx.send(true);
Expand Down
38 changes: 37 additions & 1 deletion src/federation_delivery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,19 @@ pub struct DeliveryController {
reconcile_shutdown: watch::Sender<bool>,
/// The reconcile-loop task handle (held so the task is not detached-and-lost).
_reconcile_join: tokio::task::JoinHandle<()>,
/// Shutdown signal for the config:load observer, owned here so it lives and
/// dies with delivery rather than being detached at spawn.
load_observer_shutdown: Option<watch::Sender<bool>>,
_load_observer_join: Option<tokio::task::JoinHandle<()>>,
}

impl Drop for DeliveryController {
fn drop(&mut self) {
let _ = self.reconcile_shutdown.send(true);
// The observer dies with delivery, never detached (Codex, PR #504).
if let Some(tx) = &self.load_observer_shutdown {
let _ = tx.send(true);
}
}
}

Expand Down Expand Up @@ -258,12 +266,38 @@ pub fn start_and_hold(cadence_seconds: Option<u64>, announce_logger: bool) -> Re
// absent there). Before `engine`/`edge` are moved into the controller below.
#[cfg(feature = "test-anchor")]
rt.block_on(crate::test_bless::maybe_test_bless_delivery_self(&engine))?;
let controller = rt.block_on(run_federation_delivery(

// The bare-agent topology never runs `serve_with_adapter`, so the config:load
// observer must start HERE too or agent-carrying wheel nodes never self-report
// under contention (Codex, PR #504). No identity split on this path — the
// engine key IS the node key — so the engine is the right pen. Held on the
// delivery runtime; it lives exactly as long as delivery does.
// THE WHEEL PATH SPLITS TOO. `provision_node_identity` mints a node key and
// sets both `set_actor_identity` and `set_wire_identity`, leaving the engine
// signing as the ACTOR — so a row stamped as the node and signed by the engine
// is rejected at admission (Codex, PR #504; the earlier claim that this path
// does not split was wrong). Provisioning RETAINS the node's pen for exactly
// this; when there is none, the engine is the node and is the right pen.
let engine_key = edge.signer_key_id().to_string();
let load_observer_key = crate::node_key::wire_identity()
.map(str::to_owned)
.unwrap_or_else(|| engine_key.clone());
let load_pen = match crate::node_key::node_pen_signer() {
Some(signer) => crate::load_shed::NodePen::Node(signer),
None => crate::load_shed::NodePen::Engine,
};
let (load_observer_sd, load_observer_join) = {
let _g = rt.enter();
crate::load_shed::spawn(Arc::clone(&engine), load_pen, load_observer_key)
};
let mut controller = rt.block_on(run_federation_delivery(
engine,
edge,
cadence_seconds,
announce_logger,
))?;
controller.load_observer_shutdown = Some(load_observer_sd);
controller._load_observer_join = Some(load_observer_join);
let controller = Arc::new(controller);
Ok(hold(rt, controller))
}
Expand Down Expand Up @@ -1504,6 +1538,8 @@ pub async fn run_federation_delivery(
canonical_targets: admitted_targets,
reconcile_shutdown,
_reconcile_join: reconcile_join,
load_observer_shutdown: None,
_load_observer_join: None,
})
}

Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ pub mod key_standing;
/// bidirectional replication with an out-of-group peer (Node B / `ciris-status`).
/// Public so the integration test (`tests/peer_replication.rs`) can drive the
/// admission + consent-emit logic directly.
/// A node's own `config:load` self-attestation, short-lived and replicated.
pub mod load_shed;
pub mod location;

/// Repetition-collapsing log layer — "event X occurred Y times in past Z".
Expand Down
Loading
Loading