From 234432856c58465b623bcab6ff32ab2d5c82eaef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesu=CC=81s=20Pe=CC=81rez?= Date: Sun, 19 Jul 2026 18:23:49 +0100 Subject: [PATCH] feat(daemon,ops): daemon surface + ontology-content implementation Substantive changes across the crate tree (cargo check --workspace: clean): - ontoref-daemon: UI handlers/pages, MCP surface, api, domain query delegation - ontoref-ops: runtime - ontoref-ontology / ontoref-ontology-content / oplog / triples / commit: supporting - domains/rustelo: schema surface sync --- crates/ontoref-blobs/src/backend.rs | 21 +- crates/ontoref-commit/src/error.rs | 3 +- crates/ontoref-commit/src/layer.rs | 5 +- crates/ontoref-commit/src/lib.rs | 20 +- crates/ontoref-commit/src/tree.rs | 5 +- crates/ontoref-commit/tests/basic.rs | 37 ++-- crates/ontoref-core/src/lib.rs | 3 +- crates/ontoref-core/tests/apply_pipeline.rs | 9 +- crates/ontoref-core/tests/crash_recovery.rs | 24 +-- .../tests/multi_actor_convergence.rs | 76 ++++++-- .../ontoref-core/tests/snapshot_recovery.rs | 5 +- crates/ontoref-daemon/src/actors.rs | 23 ++- crates/ontoref-daemon/src/api.rs | 95 ++++----- crates/ontoref-daemon/src/bin/ontoref-tier.rs | 4 +- crates/ontoref-daemon/src/domain.rs | 88 ++++++--- crates/ontoref-daemon/src/federation.rs | 6 +- .../src/graphql/memory_types.rs | 24 ++- crates/ontoref-daemon/src/graphql/mod.rs | 71 ++++--- crates/ontoref-daemon/src/keypair.rs | 9 +- crates/ontoref-daemon/src/lib.rs | 24 +-- crates/ontoref-daemon/src/main.rs | 52 +++-- crates/ontoref-daemon/src/mcp/mod.rs | 170 +++++++++------- crates/ontoref-daemon/src/memory.rs | 58 ++++-- crates/ontoref-daemon/src/memory_types.rs | 9 +- crates/ontoref-daemon/src/nats_sync.rs | 6 +- crates/ontoref-daemon/src/ops_surface.rs | 36 ++-- crates/ontoref-daemon/src/positioning.rs | 20 +- crates/ontoref-daemon/src/registry.rs | 35 ++-- crates/ontoref-daemon/src/rotation.rs | 13 +- crates/ontoref-daemon/src/session.rs | 3 +- .../ontoref-daemon/src/ui/handlers/assets.rs | 8 +- .../ontoref-daemon/src/ui/handlers/backlog.rs | 39 ++-- .../ontoref-daemon/src/ui/handlers/common.rs | 5 +- .../src/ui/handlers/dashboard.rs | 27 ++- .../ontoref-daemon/src/ui/handlers/manage.rs | 8 +- crates/ontoref-daemon/src/ui/handlers/mod.rs | 64 +++--- .../ontoref-daemon/src/ui/handlers/pages.rs | 183 +++++++++++------- .../ontoref-daemon/src/ui/handlers/picker.rs | 16 +- .../ontoref-daemon/src/ui/handlers/search.rs | 9 +- .../ontoref-daemon/src/ui/handlers/session.rs | 7 +- crates/ontoref-daemon/src/ui/watcher.rs | 5 +- crates/ontoref-daemon/src/views.rs | 40 ++-- crates/ontoref-daemon/src/watcher.rs | 5 +- .../ontoref-daemon/tests/dispatch_surfaces.rs | 26 ++- .../ontoref-daemon/tests/save_reconciler.rs | 15 +- crates/ontoref-derive/src/lib.rs | 40 ++-- .../src/classification.rs | 24 +-- .../ontoref-ontology-content/src/extract.rs | 49 ++--- crates/ontoref-ontology-content/src/fetch.rs | 26 +-- crates/ontoref-ontology-content/src/lib.rs | 4 +- .../ontoref-ontology-content/src/publish.rs | 4 +- .../ontoref-ontology-content/src/registry.rs | 17 +- .../tests/cross_project_witness.rs | 47 ++--- crates/ontoref-ontology/src/layout.rs | 9 +- crates/ontoref-ontology/src/ontology.rs | 36 ++-- crates/ontoref-ontology/src/render.rs | 3 +- .../ontoref-ontology/src/render_pipeline.rs | 11 +- crates/ontoref-ontology/src/types.rs | 25 ++- .../tests/render_deterministic.rs | 19 +- .../tests/render_roundtrip.rs | 9 +- .../tests/render_typecheck.rs | 24 ++- crates/ontoref-oplog/src/error.rs | 3 +- crates/ontoref-oplog/src/oplog.rs | 3 +- crates/ontoref-oplog/tests/basic.rs | 9 +- crates/ontoref-ops/src/capabilities.rs | 45 +++-- crates/ontoref-ops/src/config_reload.rs | 8 +- crates/ontoref-ops/src/lib.rs | 4 +- .../ontoref-ops/src/ops/close_dev_session.rs | 9 +- crates/ontoref-ops/src/ops/evaluate_ondaod.rs | 11 +- .../src/ops/manage_backlog_item.rs | 18 +- crates/ontoref-ops/src/ops/move_fsm_state.rs | 19 +- crates/ontoref-ops/src/ops/propose_adr.rs | 13 +- .../ontoref-ops/src/ops/start_dev_session.rs | 12 +- crates/ontoref-ops/src/ops/sync_apply.rs | 18 +- crates/ontoref-ops/src/ops/transition_adr.rs | 6 +- crates/ontoref-ops/src/ops/transition_tier.rs | 18 +- .../src/ops/update_ontology_edge.rs | 8 +- .../src/ops/update_ontology_node.rs | 21 +- crates/ontoref-ops/src/precondition/ondaod.rs | 56 ++++-- crates/ontoref-ops/src/reconcile.rs | 27 ++- crates/ontoref-ops/src/runtime.rs | 80 ++++---- crates/ontoref-ops/src/tier_guard.rs | 7 +- crates/ontoref-ops/src/tier_transition.rs | 71 +++---- .../src/validation/criteria_validators.rs | 17 +- crates/ontoref-ops/src/validation/traits.rs | 6 +- crates/ontoref-ops/src/witness.rs | 13 +- .../ontoref-ops/tests/e2e/e2e/cross_domain.rs | 26 +-- crates/ontoref-ops/tests/e2e/e2e/extra_ops.rs | 32 ++- .../tests/e2e/e2e/move_fsm_state.rs | 16 +- .../tests/e2e/e2e/witness_seam_full_cycle.rs | 13 +- crates/ontoref-query/src/engine.rs | 4 +- crates/ontoref-query/tests/basic.rs | 14 +- crates/ontoref-sync/src/sync_backend.rs | 14 +- crates/ontoref-triples/src/error.rs | 3 +- crates/ontoref-triples/src/key.rs | 19 +- crates/ontoref-triples/src/store.rs | 7 +- crates/ontoref-triples/tests/basic.rs | 46 +++-- crates/ontoref-types/src/canonical.rs | 4 +- crates/ontoref-types/src/op_id.rs | 3 +- crates/ontoref-types/src/operation.rs | 3 +- crates/ontoref-types/src/signature.rs | 9 +- crates/ontoref-types/src/succession.rs | 13 +- crates/ontoref-types/tests/determinism.rs | 9 +- domains/rustelo/domain.ncl | 2 +- install/install.nu | 20 ++ install/install.sh | 9 + 106 files changed, 1448 insertions(+), 1078 deletions(-) diff --git a/crates/ontoref-blobs/src/backend.rs b/crates/ontoref-blobs/src/backend.rs index 3e6bb3a..3c7f927 100644 --- a/crates/ontoref-blobs/src/backend.rs +++ b/crates/ontoref-blobs/src/backend.rs @@ -15,17 +15,16 @@ use crate::store::BlobId; /// /// Contract any backend MUST honour: /// -/// 1. `put(bytes)` returns the address `BlobId::from_bytes(bytes)` — -/// addressing is by blake3, identical across backends. -/// 2. `get(id)` returns the exact bytes that produced `id`, or an -/// error. Corrupted bytes MUST be rejected with -/// [`crate::Error::Corrupt`]. -/// 3. `exists(id)` is consistent with `get(id)`: it returns true iff -/// a subsequent `get(id)` would not return `NotFound`. It MUST NOT -/// perform an integrity check (use `get` for that). -/// 4. `list_prefix(prefix_hex)` returns every id whose hex -/// representation starts with `prefix_hex`. An empty `prefix_hex` -/// returns every id known to the backend. +/// 1. `put(bytes)` returns the address `BlobId::from_bytes(bytes)` — addressing +/// is by blake3, identical across backends. +/// 2. `get(id)` returns the exact bytes that produced `id`, or an error. +/// Corrupted bytes MUST be rejected with [`crate::Error::Corrupt`]. +/// 3. `exists(id)` is consistent with `get(id)`: it returns true iff a +/// subsequent `get(id)` would not return `NotFound`. It MUST NOT perform an +/// integrity check (use `get` for that). +/// 4. `list_prefix(prefix_hex)` returns every id whose hex representation +/// starts with `prefix_hex`. An empty `prefix_hex` returns every id known to +/// the backend. pub trait BlobBackend { /// Store `bytes` and return its content address. /// diff --git a/crates/ontoref-commit/src/error.rs b/crates/ontoref-commit/src/error.rs index 992065c..41b9705 100644 --- a/crates/ontoref-commit/src/error.rs +++ b/crates/ontoref-commit/src/error.rs @@ -6,7 +6,8 @@ pub enum Error { /// Canonical encoding/decoding failure. #[error("canonical: {0}")] Canonical(#[from] ontoref_types::Error), - /// Requested cell does not exist in the commit layer; no witness can be built. + /// Requested cell does not exist in the commit layer; no witness can be + /// built. #[error("cell not found in commit layer")] CellNotFound, } diff --git a/crates/ontoref-commit/src/layer.rs b/crates/ontoref-commit/src/layer.rs index 01526d7..fe4ea4f 100644 --- a/crates/ontoref-commit/src/layer.rs +++ b/crates/ontoref-commit/src/layer.rs @@ -57,10 +57,7 @@ impl MergePolicy { /// Merge kind for `attr` — `Lww` when undeclared. pub fn kind_for(&self, attr: &AttrId) -> CellMergeKind { - self.by_attr - .get(attr.as_str()) - .copied() - .unwrap_or_default() + self.by_attr.get(attr.as_str()).copied().unwrap_or_default() } } diff --git a/crates/ontoref-commit/src/lib.rs b/crates/ontoref-commit/src/lib.rs index ae9d140..9687581 100644 --- a/crates/ontoref-commit/src/lib.rs +++ b/crates/ontoref-commit/src/lib.rs @@ -24,9 +24,8 @@ pub mod tree; pub use error::{Error, Result}; pub use layer::{BinaryMerkle, CellMergeKind, MergePolicy}; -pub use tree::{cell_key, empty_root, verify_witness, CellKey, PathStep, StateRoot, Witness}; - use ontoref_types::{AttrId, EntityId, Operation}; +pub use tree::{cell_key, empty_root, verify_witness, CellKey, PathStep, StateRoot, Witness}; /// Pluggable commitment-backend trait (ADR-026 / D15). /// @@ -39,15 +38,14 @@ use ontoref_types::{AttrId, EntityId, Operation}; /// Contract any backend MUST honour: /// /// 1. `apply(op)` is the only mutation channel; calling it with the same -/// sequence of operations from any initial state must yield the same -/// state root. -/// 2. `root()` is deterministic — no clock, no randomness, no -/// instance-specific state may influence the bytes. -/// 3. A witness emitted by `witness(entity, attr)` MUST verify against -/// the root returned by the same instance immediately after the call. -/// 4. `verify_witness(w, r)` is a pure function — backends MUST NOT -/// consult any external state and MUST return the same bool for the -/// same inputs. +/// sequence of operations from any initial state must yield the same state +/// root. +/// 2. `root()` is deterministic — no clock, no randomness, no instance-specific +/// state may influence the bytes. +/// 3. A witness emitted by `witness(entity, attr)` MUST verify against the root +/// returned by the same instance immediately after the call. +/// 4. `verify_witness(w, r)` is a pure function — backends MUST NOT consult any +/// external state and MUST return the same bool for the same inputs. pub trait CommitmentBackend { /// Apply one operation. Successful application updates internal state /// such that `root()` reflects the operation's effects. diff --git a/crates/ontoref-commit/src/tree.rs b/crates/ontoref-commit/src/tree.rs index bc4ce98..b60f5a3 100644 --- a/crates/ontoref-commit/src/tree.rs +++ b/crates/ontoref-commit/src/tree.rs @@ -103,10 +103,7 @@ pub(crate) fn merkle_root(entries: &[(CellKey, &[u8])]) -> StateRoot { /// `entries`. Returns `None` if the key is absent. /// /// `entries` must be sorted by key (same constraint as `merkle_root`). -pub(crate) fn build_witness( - entries: &[(CellKey, &[u8])], - target_key: &CellKey, -) -> Option { +pub(crate) fn build_witness(entries: &[(CellKey, &[u8])], target_key: &CellKey) -> Option { let index = entries.iter().position(|(k, _)| k == target_key)?; let value = entries[index].1.to_vec(); diff --git a/crates/ontoref-commit/tests/basic.rs b/crates/ontoref-commit/tests/basic.rs index 16d1f7c..cfbe535 100644 --- a/crates/ontoref-commit/tests/basic.rs +++ b/crates/ontoref-commit/tests/basic.rs @@ -1,11 +1,10 @@ use ed25519_dalek::SigningKey; +use ontoref_commit::{verify_witness, CommitLayer}; use ontoref_types::{ operation::{OpBody, OpPayload, Operation}, AttrId, EntityId, Hlc, PublicKey, Value, }; -use ontoref_commit::{verify_witness, CommitLayer}; - fn signing_key() -> SigningKey { SigningKey::from_bytes(&[11u8; 32]) } @@ -40,17 +39,10 @@ fn fixture_ops() -> Vec { ), assert_op( "bob", - vec![ - ("name", Value::Str("Bob".into())), - ("age", Value::Int(25)), - ], + vec![("name", Value::Str("Bob".into())), ("age", Value::Int(25))], Hlc::new(2, 0), ), - assert_op( - "alice", - vec![("age", Value::Int(31))], - Hlc::new(3, 0), - ), + assert_op("alice", vec![("age", Value::Int(31))], Hlc::new(3, 0)), ] } @@ -87,7 +79,10 @@ fn determinism_root_changes_with_value_overwrite() { Hlc::new(2, 0), )); let r2 = layer.root(); - assert_ne!(r1, r2, "root must change when an existing cell's value changes"); + assert_ne!( + r1, r2, + "root must change when an existing cell's value changes" + ); } #[test] @@ -224,8 +219,16 @@ fn union_cell_accumulates_and_is_order_independent() { // Two actors assert different tag elements on the same cell. Applied in // either order, the cell must hold BOTH and hash to the same root. - let a = assert_op("doc", vec![("tags", Value::Str("red".into()))], Hlc::new(1, 0)); - let b = assert_op("doc", vec![("tags", Value::Str("blue".into()))], Hlc::new(2, 0)); + let a = assert_op( + "doc", + vec![("tags", Value::Str("red".into()))], + Hlc::new(1, 0), + ); + let b = assert_op( + "doc", + vec![("tags", Value::Str("blue".into()))], + Hlc::new(2, 0), + ); let mut forward = CommitLayer::new().with_policy(policy.clone()); forward.apply(&a); @@ -266,5 +269,9 @@ fn lww_is_unchanged_without_a_policy() { // Equals a layer that only ever saw the winning (b) assertion. let mut only_b = CommitLayer::new(); only_b.apply(&b); - assert_eq!(layer.root(), only_b.root(), "default policy stays last-writer-wins"); + assert_eq!( + layer.root(), + only_b.root(), + "default policy stays last-writer-wins" + ); } diff --git a/crates/ontoref-core/src/lib.rs b/crates/ontoref-core/src/lib.rs index 50417fe..d4aa211 100644 --- a/crates/ontoref-core/src/lib.rs +++ b/crates/ontoref-core/src/lib.rs @@ -1,4 +1,5 @@ -//! Composition layer integrating L1 through L5 of the ontoref verifiable substrate. +//! Composition layer integrating L1 through L5 of the ontoref verifiable +//! substrate. //! //! `Substrate::open(path)` materialises a project on disk: blob store, op log, //! triple store; the in-memory commit layer is rebuilt by replaying the op log diff --git a/crates/ontoref-core/tests/apply_pipeline.rs b/crates/ontoref-core/tests/apply_pipeline.rs index f79244e..5d17d5c 100644 --- a/crates/ontoref-core/tests/apply_pipeline.rs +++ b/crates/ontoref-core/tests/apply_pipeline.rs @@ -1,12 +1,11 @@ use ed25519_dalek::SigningKey; use ontoref_commit::{empty_root, verify_witness}; +use ontoref_core::Substrate; use ontoref_types::{ operation::{OpBody, OpPayload, Operation}, AttrId, EntityId, Hlc, PublicKey, Value, }; -use ontoref_core::Substrate; - fn signing_key() -> SigningKey { SigningKey::from_bytes(&[17u8; 32]) } @@ -109,11 +108,7 @@ fn apply_pipeline_materialises_triples_visible_to_query() { let attrs = substrate .query() .triples() - .entity_attrs( - &EntityId::new("alice"), - Hlc::new(10, 0), - Hlc::new(10, 0), - ) + .entity_attrs(&EntityId::new("alice"), Hlc::new(10, 0), Hlc::new(10, 0)) .unwrap(); assert_eq!(attrs.len(), 1); assert_eq!(attrs[0].0, AttrId::new("name")); diff --git a/crates/ontoref-core/tests/crash_recovery.rs b/crates/ontoref-core/tests/crash_recovery.rs index 4bb63e7..309bae8 100644 --- a/crates/ontoref-core/tests/crash_recovery.rs +++ b/crates/ontoref-core/tests/crash_recovery.rs @@ -7,8 +7,8 @@ //! //! 1. The op log holds at least every op the child reported as completed. //! 2. At most one extra op is present (the in-flight one at kill time). -//! 3. Reopening the substrate twice produces the same state root — -//! i.e. recovery from the durable layer is deterministic. +//! 3. Reopening the substrate twice produces the same state root — i.e. +//! recovery from the durable layer is deterministic. //! //! Marked `#[ignore]` because it kills a real process; run with //! `cargo test -p ontoref-core --test crash_recovery -- --ignored`. @@ -26,10 +26,10 @@ const PROGRESS_PATH_ENV: &str = "ONTOREF_CRASH_RECOVERY_PROGRESS"; const TEST_NAME: &str = "crash_recovery_kill9_during_apply"; fn run_as_child() -> ! { - let sub_path = std::env::var(SUBSTRATE_PATH_ENV) - .expect("child requires SUBSTRATE_PATH env var"); - let progress_path = std::env::var(PROGRESS_PATH_ENV) - .expect("child requires PROGRESS_PATH env var"); + let sub_path = + std::env::var(SUBSTRATE_PATH_ENV).expect("child requires SUBSTRATE_PATH env var"); + let progress_path = + std::env::var(PROGRESS_PATH_ENV).expect("child requires PROGRESS_PATH env var"); let mut substrate = Substrate::open(&sub_path).expect("child failed to open substrate"); let key = SigningKey::from_bytes(&[42u8; 32]); @@ -48,8 +48,7 @@ fn run_as_child() -> ! { }; let op = Operation::sign(body, &key); substrate.apply(&op).expect("child apply failed"); - std::fs::write(&progress_path, format!("{counter}")) - .expect("child progress write failed"); + std::fs::write(&progress_path, format!("{counter}")).expect("child progress write failed"); std::thread::sleep(std::time::Duration::from_millis(20)); counter += 1; } @@ -96,11 +95,13 @@ fn crash_recovery_kill9_during_apply() { assert!( oplog_len > last_completed, - "oplog must hold every reported op (last_completed={last_completed}, oplog_len={oplog_len})" + "oplog must hold every reported op (last_completed={last_completed}, \ + oplog_len={oplog_len})" ); assert!( oplog_len < last_completed + 3, - "oplog should not be more than one op past the last reported (last_completed={last_completed}, oplog_len={oplog_len})" + "oplog should not be more than one op past the last reported \ + (last_completed={last_completed}, oplog_len={oplog_len})" ); let root_first = substrate.state_root(); @@ -110,6 +111,7 @@ fn crash_recovery_kill9_during_apply() { assert_eq!( substrate_again.state_root(), root_first, - "state root must be deterministic across reopens — recovery is a pure function of the durable oplog" + "state root must be deterministic across reopens — recovery is a pure function of the \ + durable oplog" ); } diff --git a/crates/ontoref-core/tests/multi_actor_convergence.rs b/crates/ontoref-core/tests/multi_actor_convergence.rs index df09f2b..b715c4c 100644 --- a/crates/ontoref-core/tests/multi_actor_convergence.rs +++ b/crates/ontoref-core/tests/multi_actor_convergence.rs @@ -80,13 +80,21 @@ fn two_actors_converge_on_the_same_cell() { bob.apply(&bob_op).expect("bob apply"); // Diverged before sync. - assert_ne!(alice.state_root(), bob.state_root(), "actors diverge pre-sync"); + assert_ne!( + alice.state_root(), + bob.state_root(), + "actors diverge pre-sync" + ); // Each shares its op, then pulls + ingests the peer's. share(&alice_sync, &alice_op); - alice_sync.announce(&hex(&alice_op.id().0)).expect("alice announce"); + alice_sync + .announce(&hex(&alice_op.id().0)) + .expect("alice announce"); share(&bob_sync, &bob_op); - bob_sync.announce(&hex(&bob_op.id().0)).expect("bob announce"); + bob_sync + .announce(&hex(&bob_op.id().0)) + .expect("bob announce"); let into_bob = pull_and_ingest(&bob_sync, "alice", &mut bob); let into_alice = pull_and_ingest(&alice_sync, "bob", &mut alice); @@ -105,10 +113,15 @@ fn two_actors_converge_on_the_same_cell() { let root_before = alice.state_root(); let again = pull_and_ingest(&alice_sync, "bob", &mut alice); assert_eq!(again, 0, "re-ingest is idempotent"); - assert_eq!(alice.state_root(), root_before, "idempotent ingest leaves the root"); + assert_eq!( + alice.state_root(), + root_before, + "idempotent ingest leaves the root" + ); for op in alice.oplog().all_ops_in_hlc_order().expect("ops") { - op.verify_signature().expect("every converged witness verifies"); + op.verify_signature() + .expect("every converged witness verifies"); } } @@ -159,7 +172,9 @@ fn two_actors_converge_with_a_removal() { // The convergent state has the cell removed: it equals a substrate that // only ever saw the (higher-HLC) retract. let mut reference = Substrate::open(dir.path().join("ref")).expect("ref"); - reference.apply(&retract_op(&b_key, "shared", &["v"], Hlc::new(20, 0))).expect("ref"); + reference + .apply(&retract_op(&b_key, "shared", &["v"], Hlc::new(20, 0))) + .expect("ref"); assert_eq!( alice.state_root(), reference.state_root(), @@ -195,16 +210,29 @@ fn union_domain_converges_across_actors_via_substrate() { let a_key = SigningKey::from_bytes(&[1u8; 32]); let b_key = SigningKey::from_bytes(&[2u8; 32]); - let mut alice = - Substrate::open_with_policy(dir.path().join("a"), policy.clone()).expect("a"); + let mut alice = Substrate::open_with_policy(dir.path().join("a"), policy.clone()).expect("a"); let mut bob = Substrate::open_with_policy(dir.path().join("b"), policy.clone()).expect("b"); - let oa = assert_attr(&a_key, "doc", "tags", Value::Str("red".into()), Hlc::new(10, 0)); - let ob = assert_attr(&b_key, "doc", "tags", Value::Str("blue".into()), Hlc::new(20, 0)); + let oa = assert_attr( + &a_key, + "doc", + "tags", + Value::Str("red".into()), + Hlc::new(10, 0), + ); + let ob = assert_attr( + &b_key, + "doc", + "tags", + Value::Str("blue".into()), + Hlc::new(20, 0), + ); alice.apply(&oa).expect("alice"); bob.apply(&ob).expect("bob"); - alice.ingest(std::slice::from_ref(&ob)).expect("alice ingest"); + alice + .ingest(std::slice::from_ref(&ob)) + .expect("alice ingest"); bob.ingest(std::slice::from_ref(&oa)).expect("bob ingest"); assert_eq!( @@ -215,10 +243,15 @@ fn union_domain_converges_across_actors_via_substrate() { // The converged root retains BOTH tags: it differs from a substrate that // only ever saw one element (proving union, not last-writer-wins). - let mut only_one = - Substrate::open_with_policy(dir.path().join("c"), policy).expect("c"); + let mut only_one = Substrate::open_with_policy(dir.path().join("c"), policy).expect("c"); only_one - .apply(&assert_attr(&a_key, "doc", "tags", Value::Str("red".into()), Hlc::new(10, 0))) + .apply(&assert_attr( + &a_key, + "doc", + "tags", + Value::Str("red".into()), + Hlc::new(10, 0), + )) .expect("c apply"); assert_ne!( alice.state_root(), @@ -242,13 +275,11 @@ fn semantic_validator_quarantines_a_peer_op_post_merge() { // Domain rule: reject assertions to the "forbidden" entity. let report = local - .ingest_with(&[good.clone(), bad.clone()], |op| { - match &op.body.payload { - OpPayload::EntityAssert { entity, .. } if entity.0 == "forbidden" => { - Err("entity 'forbidden' is not admissible".to_owned()) - } - _ => Ok(()), + .ingest_with(&[good.clone(), bad.clone()], |op| match &op.body.payload { + OpPayload::EntityAssert { entity, .. } if entity.0 == "forbidden" => { + Err("entity 'forbidden' is not admissible".to_owned()) } + _ => Ok(()), }) .expect("ingest_with"); @@ -265,5 +296,8 @@ fn semantic_validator_quarantines_a_peer_op_post_merge() { .map(|o| o.id()) .collect(); assert!(ids.contains(&good.id())); - assert!(!ids.contains(&bad.id()), "quarantined op never reaches the oplog"); + assert!( + !ids.contains(&bad.id()), + "quarantined op never reaches the oplog" + ); } diff --git a/crates/ontoref-core/tests/snapshot_recovery.rs b/crates/ontoref-core/tests/snapshot_recovery.rs index 8746235..6da4eba 100644 --- a/crates/ontoref-core/tests/snapshot_recovery.rs +++ b/crates/ontoref-core/tests/snapshot_recovery.rs @@ -54,7 +54,10 @@ fn snapshot_enables_incremental_recovery() { snap.state_root, root_at_snapshot, "snapshot records the state root for direct offline verification" ); - assert!(snap.boundary.is_some(), "boundary pins the replay resume point"); + assert!( + snap.boundary.is_some(), + "boundary pins the replay resume point" + ); // Reopen via the snapshot reproduces the root with nothing new to replay. let root_final = { diff --git a/crates/ontoref-daemon/src/actors.rs b/crates/ontoref-daemon/src/actors.rs index a5b6c0b..51a98be 100644 --- a/crates/ontoref-daemon/src/actors.rs +++ b/crates/ontoref-daemon/src/actors.rs @@ -438,12 +438,12 @@ fn process_alive(_pid: u32) -> bool { #[cfg(test)] #[test] fn keypair_lifecycle() { - use crate::keypair::{ - gen_for_actor, load_for_actor, public_key_hex, register_actor_in_ncl, - sign_witness_payload, - }; use ontoref_types::signature::{PublicKey, Sig}; + use crate::keypair::{ + gen_for_actor, load_for_actor, public_key_hex, register_actor_in_ncl, sign_witness_payload, + }; + let tmp = tempfile::tempdir().expect("tempdir"); let key_dir = tmp.path().join("keys"); let actors_ncl = tmp.path().join(".ontoref").join("actors.ncl"); @@ -466,14 +466,23 @@ fn keypair_lifecycle() { register_actor_in_ncl(&actors_ncl, actor, &public_key, "2026-05-26T00:00:00Z") .expect("register ok"); let ncl = std::fs::read_to_string(&actors_ncl).expect("read actors.ncl"); - assert!(ncl.contains("public_key"), "actors.ncl must contain public_key"); - assert!(ncl.contains(&public_key_hex(&public_key)), "actors.ncl must carry exact pubkey hex"); + assert!( + ncl.contains("public_key"), + "actors.ncl must contain public_key" + ); + assert!( + ncl.contains(&public_key_hex(&public_key)), + "actors.ncl must carry exact pubkey hex" + ); assert!(ncl.contains(actor), "actors.ncl must carry the actor id"); // 3. load — round-trips the private key from disk let signing_key = load_for_actor(actor, &key_dir).expect("load ok"); let derived = PublicKey::from_signing_key(&signing_key); - assert_eq!(derived.0, public_key.0, "loaded key must derive same public key"); + assert_eq!( + derived.0, public_key.0, + "loaded key must derive same public key" + ); // 4. sign — produces a valid signature over the canonical witness payload let op_id = b"op:move_fsm_state:00000001"; diff --git a/crates/ontoref-daemon/src/api.rs b/crates/ontoref-daemon/src/api.rs index 60b892d..af1596e 100644 --- a/crates/ontoref-daemon/src/api.rs +++ b/crates/ontoref-daemon/src/api.rs @@ -226,7 +226,8 @@ pub struct AppState { /// /// Exposes its JWKS at `GET /.well-known/ontoref-keys.json` and is the /// signing key for tokens accepted by SSO-mode panels (federation contract - /// with sibling crates that consume `ontoref-auth`'s `SignedTokenProvider`). + /// with sibling crates that consume `ontoref-auth`'s + /// `SignedTokenProvider`). pub token_issuer: Arc, /// Verifies daemon-minted JWTs against the issuer's JWKS. Refreshed in /// lockstep with `token_issuer` on key rotation. @@ -536,8 +537,8 @@ pub fn router(state: AppState) -> axum::Router { // SSO surface: JWKS publication + token introspection (RFC 7662-shaped). // No auth on either endpoint: // - JWKS is public by definition (anyone verifying a token needs it). - // - Introspection takes the token in the request body and reveals only - // what was already cryptographically vouched by the issuer. + // - Introspection takes the token in the request body and reveals only what was + // already cryptographically vouched by the issuer. let app = app .route("/.well-known/ontoref-keys.json", get(jwks_handler)) .route("/.session/introspect", post(introspect_handler)); @@ -684,7 +685,10 @@ async fn api_catalog_handler() -> impl IntoResponse { async fn ops_dispatch_handler( axum::extract::Path(op_id): axum::extract::Path, Json(request): Json, -) -> Result, (axum::http::StatusCode, Json)> { +) -> Result< + Json, + (axum::http::StatusCode, Json), +> { match crate::ops_surface::dispatch_op_request(&op_id, request) { Ok(response) => Ok(Json(response)), Err(e) => Err(( @@ -702,7 +706,8 @@ async fn ops_dispatch_handler( path = "/ops", auth = "none", actors = "agent, developer, ci, admin", - description = "List every registered domain operation with its declarative shape (MCP-aligned projection)", + description = "List every registered domain operation with its declarative shape (MCP-aligned \ + projection)", tags = "ops, catalog" )] async fn ops_list_handler() -> Json { @@ -727,8 +732,7 @@ struct ChronicleQuery { auth = "none", actors = "agent, developer, ci, admin", params = "since:string:optional:ISO date lower bound (inclusive); \ - kind:string:optional:adr|migration; \ - domain:string:optional:Domain filter; \ + kind:string:optional:adr|migration; domain:string:optional:Domain filter; \ slug:string:optional:Project slug", tags = "memory, chronicle" )] @@ -764,8 +768,7 @@ struct RoadmapQuery { auth = "none", actors = "agent, developer, ci, admin", params = "status:string:optional:Open|InProgress|Done|Cancelled; \ - horizon:string:optional:now|next|later; \ - slug:string:optional:Project slug", + horizon:string:optional:now|next|later; slug:string:optional:Project slug", tags = "memory, roadmap" )] async fn roadmap_handler( @@ -807,13 +810,8 @@ async fn criteria_handler( ) -> Json { state.touch_activity(); let (root, cache, ip) = project_ctx_tuple(&state, q.slug.as_deref()); - let criteria = crate::memory::criteria_list( - &root, - &cache, - ip.as_deref(), - q.kind.as_deref(), - ) - .await; + let criteria = + crate::memory::criteria_list(&root, &cache, ip.as_deref(), q.kind.as_deref()).await; Json(serde_json::json!({ "count": criteria.len(), "criteria": criteria })) } @@ -829,8 +827,8 @@ struct PanelsQuery { path = "/api/panels", auth = "none", actors = "agent, developer, ci, admin", - params = "kind:string:optional:Timeline|Gantt|Graph|Table|Board; \ - slug:string:optional:Project slug", + params = "kind:string:optional:Timeline|Gantt|Graph|Table|Board; slug:string:optional:Project \ + slug", tags = "memory, panels" )] async fn panels_handler( @@ -839,13 +837,7 @@ async fn panels_handler( ) -> Json { state.touch_activity(); let (root, cache, ip) = project_ctx_tuple(&state, q.slug.as_deref()); - let panels = crate::memory::panels_list( - &root, - &cache, - ip.as_deref(), - q.kind.as_deref(), - ) - .await; + let panels = crate::memory::panels_list(&root, &cache, ip.as_deref(), q.kind.as_deref()).await; Json(serde_json::json!({ "count": panels.len(), "panels": panels })) } @@ -1104,9 +1096,8 @@ struct RegisterResponse { auth = "none", actors = "agent, developer, ci", params = "actor_type:string:required:Actor type (agent|developer|ci|admin); \ - hostname:string:required:Caller hostname (audit trail); \ - pid:number:required:Caller process id (audit trail); \ - project:string:required:Project slug to associate with; \ + hostname:string:required:Caller hostname (audit trail); pid:number:required:Caller \ + process id (audit trail); project:string:required:Project slug to associate with; \ role:string:optional:Actor role; preferences:object:optional:Free-form actor \ preferences", tags = "actors, auth" @@ -1439,11 +1430,7 @@ async fn notifications_emit( None, ); - ( - StatusCode::ACCEPTED, - Json(EmitResponse { id, project }), - ) - .into_response() + (StatusCode::ACCEPTED, Json(EmitResponse { id, project })).into_response() } // ── SSE Notification Stream ─────────────────────────────────────────────── @@ -3902,8 +3889,8 @@ struct IntrospectRequest { path = "/.session/introspect", auth = "none", actors = "agent, developer, ci, admin", - params = "token:string:required:JWT to introspect; \ - audience:string:optional:Expected aud (Exact match)", + params = "token:string:required:JWT to introspect; audience:string:optional:Expected aud \ + (Exact match)", tags = "meta, sso" )] async fn introspect_handler( @@ -3959,7 +3946,8 @@ fn persist_keys_overlay(config_dir: &std::path::Path, registry: &crate::registry } } -// ── Domain graph endpoints ──────────────────────────────────────────────────── +// ── Domain graph endpoints +// ──────────────────────────────────────────────────── #[derive(Deserialize)] struct DomainQuery { @@ -3981,11 +3969,16 @@ async fn domain_list( Query(q): Query, ) -> impl IntoResponse { state.touch_activity(); - let ctx = q.slug.as_deref() + let ctx = q + .slug + .as_deref() .and_then(|s| state.registry.get(s)) .unwrap_or_else(|| state.registry.primary()); let domains = crate::domain::project_domains(&ctx.root); - (StatusCode::OK, Json(serde_json::json!({ "slug": ctx.slug, "domains": domains }))) + ( + StatusCode::OK, + Json(serde_json::json!({ "slug": ctx.slug, "domains": domains })), + ) } /// All registered projects implementing a given domain id @@ -4004,10 +3997,18 @@ async fn domain_consumers( state.touch_activity(); let id = match q.id { Some(ref s) if !s.is_empty() => s.as_str(), - _ => return (StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "id required" }))), + _ => { + return ( + StatusCode::BAD_REQUEST, + Json(serde_json::json!({ "error": "id required" })), + ) + } }; let consumers = crate::domain::consumers_of(&state.registry, id); - (StatusCode::OK, Json(serde_json::json!({ "domain_id": id, "consumers": consumers }))) + ( + StatusCode::OK, + Json(serde_json::json!({ "domain_id": id, "consumers": consumers })), + ) } /// Domain metadata from the ontoref domain registry @@ -4026,7 +4027,12 @@ async fn domain_show( state.touch_activity(); let id = match q.id { Some(ref s) if !s.is_empty() => s.as_str(), - _ => return (StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "id required" }))), + _ => { + return ( + StatusCode::BAD_REQUEST, + Json(serde_json::json!({ "error": "id required" })), + ) + } }; let root = state.registry.primary().root.clone(); // Walk up to find ontoref root (domains/ dir). @@ -4160,9 +4166,10 @@ async fn tier_next( ( StatusCode::OK, - Json(serde_json::to_value(projection).unwrap_or_else(|e| { - serde_json::json!({ "error": e.to_string() }) - })), + Json( + serde_json::to_value(projection) + .unwrap_or_else(|e| serde_json::json!({ "error": e.to_string() })), + ), ) } diff --git a/crates/ontoref-daemon/src/bin/ontoref-tier.rs b/crates/ontoref-daemon/src/bin/ontoref-tier.rs index 22135ac..30878bd 100644 --- a/crates/ontoref-daemon/src/bin/ontoref-tier.rs +++ b/crates/ontoref-daemon/src/bin/ontoref-tier.rs @@ -15,7 +15,6 @@ use std::path::PathBuf; use std::process::ExitCode; use clap::{Parser, Subcommand}; - use ontoref_daemon::config::{OpsConfig, Phase, Tier}; use ontoref_daemon::tier_transition::{execute, TransitionContext, TransitionError}; @@ -181,7 +180,8 @@ fn run() -> Result<(), String> { Ok(outcome) => { if json { println!( - "{{\"ok\":true,\"from\":\"{:?}\",\"to\":\"{:?}\",\"phase\":\"{:?}\",\"rewritten\":{}}}", + "{{\"ok\":true,\"from\":\"{:?}\",\"to\":\"{:?}\",\"phase\":\"{:?}\",\"\ + rewritten\":{}}}", outcome.from_tier, outcome.to_tier, outcome.phase, diff --git a/crates/ontoref-daemon/src/domain.rs b/crates/ontoref-daemon/src/domain.rs index 46a8321..473e6d4 100644 --- a/crates/ontoref-daemon/src/domain.rs +++ b/crates/ontoref-daemon/src/domain.rs @@ -48,7 +48,10 @@ fn extract_config_flags(content: &str) -> Vec { let eq_pos = rest.find('=')?; let id = rest[..eq_pos].trim(); let val = rest[eq_pos + 1..].trim(); - if val.starts_with("true") && id.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') && !id.is_empty() { + if val.starts_with("true") + && id.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') + && !id.is_empty() + { Some(id.to_string()) } else { None @@ -74,7 +77,10 @@ fn extract_domain_origin(content: &str) -> Option<(String, String)> { '{' => depth += 1, '}' => { depth -= 1; - if depth == 0 { close = open + i; break; } + if depth == 0 { + close = open + i; + break; + } } _ => {} } @@ -84,16 +90,23 @@ fn extract_domain_origin(content: &str) -> Option<(String, String)> { let id = inner.lines().find_map(|l| { let t = l.trim(); let rest = t.strip_prefix("id")?.trim_start_matches([' ', '=']).trim(); - rest.strip_prefix('"')?.split('"').next().map(|s| { - s.replace("-framework", "").replace("-domain", "") - }) + rest.strip_prefix('"')? + .split('"') + .next() + .map(|s| s.replace("-framework", "").replace("-domain", "")) })?; - let path = inner.lines().find_map(|l| { - let t = l.trim(); - let rest = t.strip_prefix("path")?.trim_start_matches([' ', '=']).trim(); - rest.strip_prefix('"')?.split('"').next().map(String::from) - }).unwrap_or_default(); + let path = inner + .lines() + .find_map(|l| { + let t = l.trim(); + let rest = t + .strip_prefix("path")? + .trim_start_matches([' ', '=']) + .trim(); + rest.strip_prefix('"')?.split('"').next().map(String::from) + }) + .unwrap_or_default(); Some((id, path)) } @@ -107,7 +120,11 @@ pub fn project_domains(root: &Path) -> Vec { if let Ok(content) = std::fs::read_to_string(&config_path) { for id in extract_config_flags(&content) { if !domains.iter().any(|d| d.id == id) { - domains.push(DomainDecl { id, kind: DomainDeclKind::ConfigFlag, path: String::new() }); + domains.push(DomainDecl { + id, + kind: DomainDeclKind::ConfigFlag, + path: String::new(), + }); } } } @@ -117,7 +134,11 @@ pub fn project_domains(root: &Path) -> Vec { if let Ok(content) = std::fs::read_to_string(&manifest_path) { if let Some((id, path)) = extract_domain_origin(&content) { if !domains.iter().any(|d| d.id == id) { - domains.push(DomainDecl { id, kind: DomainDeclKind::DomainOrigin, path }); + domains.push(DomainDecl { + id, + kind: DomainDeclKind::DomainOrigin, + path, + }); } } } @@ -136,7 +157,11 @@ pub fn project_domains(root: &Path) -> Vec { .trim_end_matches(".ontoref") .to_string(); if !domains.iter().any(|d| d.id == id) { - domains.push(DomainDecl { id, kind: DomainDeclKind::ImplDir, path: String::new() }); + domains.push(DomainDecl { + id, + kind: DomainDeclKind::ImplDir, + path: String::new(), + }); } } } @@ -152,16 +177,20 @@ pub fn consumers_of(registry: &ProjectRegistry, domain_id: &str) -> Vec Vec { let domains_dir = ontoref_root.join("domains"); std::fs::read_dir(&domains_dir) @@ -183,15 +212,21 @@ pub struct DomainMeta { } fn extract_ncl_string_field(content: &str, field: &str) -> String { - content.lines().find_map(|l| { - let t = l.trim(); - let rest = t.strip_prefix(field)?.trim_start_matches([' ', '=']).trim(); - rest.strip_prefix('"')?.split('"').next().map(String::from) - }).unwrap_or_default() + content + .lines() + .find_map(|l| { + let t = l.trim(); + let rest = t.strip_prefix(field)?.trim_start_matches([' ', '=']).trim(); + rest.strip_prefix('"')?.split('"').next().map(String::from) + }) + .unwrap_or_default() } pub fn domain_meta(ontoref_root: &Path, domain_id: &str) -> Option { - let ncl = ontoref_root.join("domains").join(domain_id).join("domain.ncl"); + let ncl = ontoref_root + .join("domains") + .join(domain_id) + .join("domain.ncl"); let content = std::fs::read_to_string(&ncl).ok()?; Some(DomainMeta { id: domain_id.to_string(), @@ -207,7 +242,8 @@ mod tests { #[test] fn extract_config_flags_finds_domain_declarations() { - let content = "ops = { tier = 'Tier0 },\n domain_provisioning = true,\n domain_rustelo = true,\n other = false,"; + let content = "ops = { tier = 'Tier0 },\n domain_provisioning = true,\n domain_rustelo \ + = true,\n other = false,"; let flags = extract_config_flags(content); assert!(flags.contains(&"provisioning".to_string())); assert!(flags.contains(&"rustelo".to_string())); diff --git a/crates/ontoref-daemon/src/federation.rs b/crates/ontoref-daemon/src/federation.rs index 306fb40..20c36a9 100644 --- a/crates/ontoref-daemon/src/federation.rs +++ b/crates/ontoref-daemon/src/federation.rs @@ -184,7 +184,8 @@ impl FederatedQuery { if ctx.push_only { return; } - let conn_path = ontoref_ontology::layout::resolve_section(&ctx.root, "ontology/connections.ncl"); + let conn_path = + ontoref_ontology::layout::resolve_section(&ctx.root, "ontology/connections.ncl"); if !conn_path.exists() { return; } @@ -343,7 +344,8 @@ pub async fn validate_connections(registry: &Arc, slug: &str) - let Some(ctx) = registry.get(slug) else { return vec![format!("slug '{slug}' not found in registry")]; }; - let conn_path = ontoref_ontology::layout::resolve_section(&ctx.root, "ontology/connections.ncl"); + let conn_path = + ontoref_ontology::layout::resolve_section(&ctx.root, "ontology/connections.ncl"); if !conn_path.exists() { return vec![]; } diff --git a/crates/ontoref-daemon/src/graphql/memory_types.rs b/crates/ontoref-daemon/src/graphql/memory_types.rs index af32704..00a82c0 100644 --- a/crates/ontoref-daemon/src/graphql/memory_types.rs +++ b/crates/ontoref-daemon/src/graphql/memory_types.rs @@ -1,17 +1,20 @@ -//! Constellation-memory GraphQL output types with single-source cross-surface equivalence. +//! Constellation-memory GraphQL output types with single-source cross-surface +//! equivalence. //! -//! Re-exports from [`crate::memory_types`] — the canonical single definition that uses -//! `#[cfg_attr(feature = "graphql", derive(SimpleObject))]` so the types compile on every -//! surface (HTTP, MCP, GraphQL) without duplication. G-4 coherence tests live here where -//! they can reference the `async_graphql` SDL generation logic. +//! Re-exports from [`crate::memory_types`] — the canonical single definition +//! that uses `#[cfg_attr(feature = "graphql", derive(SimpleObject))]` so the +//! types compile on every surface (HTTP, MCP, GraphQL) without duplication. G-4 +//! coherence tests live here where they can reference the `async_graphql` SDL +//! generation logic. pub use crate::memory_types::{ChronicleEntry, Criterion, PanelSpec, RoadmapItem}; #[cfg(test)] mod tests { - use super::*; use async_graphql::{EmptyMutation, EmptySubscription, Object, Schema}; + use super::*; + fn serde_keys(value: &T) -> Vec { let v = serde_json::to_value(value).expect("entity must serialize"); let mut keys: Vec = v @@ -91,7 +94,14 @@ mod tests { surfaces: vec!["Ui".into()], actors: vec!["Developer".into()], }), - sorted(&["id", "title", "kind", "data_source_op", "surfaces", "actors"]), + sorted(&[ + "id", + "title", + "kind", + "data_source_op", + "surfaces", + "actors" + ]), ); } diff --git a/crates/ontoref-daemon/src/graphql/mod.rs b/crates/ontoref-daemon/src/graphql/mod.rs index 9857a82..5c713a1 100644 --- a/crates/ontoref-daemon/src/graphql/mod.rs +++ b/crates/ontoref-daemon/src/graphql/mod.rs @@ -888,15 +888,19 @@ impl QueryRoot { slug: Option, ) -> async_graphql::Result> { let state = ctx.data::()?; - let pctx = slug.as_deref() + let pctx = slug + .as_deref() .and_then(|s| state.registry.get(s)) .unwrap_or_else(|| state.registry.primary()); let decls = crate::domain::project_domains(&pctx.root); - Ok(decls.into_iter().map(|d| DomainDeclGql { - id: d.id, - kind: format!("{:?}", d.kind), - path: d.path, - }).collect()) + Ok(decls + .into_iter() + .map(|d| DomainDeclGql { + id: d.id, + kind: format!("{:?}", d.kind), + path: d.path, + }) + .collect()) } /// All registered projects implementing a given domain. @@ -907,11 +911,14 @@ impl QueryRoot { ) -> async_graphql::Result> { let state = ctx.data::()?; let consumers = crate::domain::consumers_of(&state.registry, &domain_id); - Ok(consumers.into_iter().map(|c| DomainConsumerGql { - slug: c.slug, - root: c.root, - kind: format!("{:?}", c.kind), - }).collect()) + Ok(consumers + .into_iter() + .map(|c| DomainConsumerGql { + slug: c.slug, + root: c.root, + kind: format!("{:?}", c.kind), + }) + .collect()) } /// Domain metadata + all its consumers. @@ -933,11 +940,14 @@ impl QueryRoot { name: meta.name, description: meta.description, schema_cmd: meta.schema_cmd, - consumers: consumers.into_iter().map(|c| DomainConsumerGql { - slug: c.slug, - root: c.root, - kind: format!("{:?}", c.kind), - }).collect(), + consumers: consumers + .into_iter() + .map(|c| DomainConsumerGql { + slug: c.slug, + root: c.root, + kind: format!("{:?}", c.kind), + }) + .collect(), })) } @@ -948,7 +958,9 @@ impl QueryRoot { slug: Option, ) -> async_graphql::Result { let state = ctx.data::()?; - let effective = slug.as_deref().unwrap_or_else(|| state.registry.primary_slug()); + let effective = slug + .as_deref() + .unwrap_or_else(|| state.registry.primary_slug()); let pctx = state .registry .get(effective) @@ -979,7 +991,9 @@ impl QueryRoot { slug: Option, ) -> async_graphql::Result { let state = ctx.data::()?; - let effective = slug.as_deref().unwrap_or_else(|| state.registry.primary_slug()); + let effective = slug + .as_deref() + .unwrap_or_else(|| state.registry.primary_slug()); let pctx = state .registry .get(effective) @@ -1002,7 +1016,8 @@ impl QueryRoot { }) } - /// Chronicle timeline entries woven from Accepted ADRs and protocol migrations. + /// Chronicle timeline entries woven from Accepted ADRs and protocol + /// migrations. async fn chronicle( &self, ctx: &Context<'_>, @@ -1053,13 +1068,10 @@ impl QueryRoot { ) -> async_graphql::Result> { let state = ctx.data::()?; let (root, cache, import_path) = project_ctx(state, slug.as_deref()); - Ok(crate::memory::criteria_list( - &root, - &cache, - import_path.as_deref(), - kind.as_deref(), + Ok( + crate::memory::criteria_list(&root, &cache, import_path.as_deref(), kind.as_deref()) + .await, ) - .await) } /// Resolved panel specs from the presentation directory (ADR-053). @@ -1071,13 +1083,10 @@ impl QueryRoot { ) -> async_graphql::Result> { let state = ctx.data::()?; let (root, cache, import_path) = project_ctx(state, slug.as_deref()); - Ok(crate::memory::panels_list( - &root, - &cache, - import_path.as_deref(), - kind.as_deref(), + Ok( + crate::memory::panels_list(&root, &cache, import_path.as_deref(), kind.as_deref()) + .await, ) - .await) } /// Combined config.ncl + card.ncl for a project. diff --git a/crates/ontoref-daemon/src/keypair.rs b/crates/ontoref-daemon/src/keypair.rs index 7fdd120..fb26105 100644 --- a/crates/ontoref-daemon/src/keypair.rs +++ b/crates/ontoref-daemon/src/keypair.rs @@ -175,10 +175,10 @@ pub fn sign_payload_for_actor( /// Verify a payload signature given a hex-encoded public key (32 bytes) and a /// hex-encoded Ed25519 signature (64 bytes). Returns `false` for any malformed -/// input rather than erroring — the caller treats verification as a boolean gate. +/// input rather than erroring — the caller treats verification as a boolean +/// gate. pub fn verify_payload_hex(public_key_hex: &str, payload: &[u8], signature_hex: &str) -> bool { - let (Some(pk_bytes), Some(sig_bytes)) = - (hex_decode(public_key_hex), hex_decode(signature_hex)) + let (Some(pk_bytes), Some(sig_bytes)) = (hex_decode(public_key_hex), hex_decode(signature_hex)) else { return false; }; @@ -212,7 +212,8 @@ pub fn register_actor_in_ncl( validate_actor_id(actor_id)?; let pk_hex = public_key_hex(public_key); let entry = format!( - " {{ id = \"{actor_id}\", public_key = \"{pk_hex}\", created_at = \"{created_at}\" }},\n" + " {{ id = \"{actor_id}\", public_key = \"{pk_hex}\", created_at = \"{created_at}\" \ + }},\n" ); if !actors_ncl.exists() { diff --git a/crates/ontoref-daemon/src/lib.rs b/crates/ontoref-daemon/src/lib.rs index 9b7aa59..ff42c4f 100644 --- a/crates/ontoref-daemon/src/lib.rs +++ b/crates/ontoref-daemon/src/lib.rs @@ -19,30 +19,30 @@ pub mod config_coherence; pub mod domain; pub mod error; pub mod federation; -pub mod keypair; -#[cfg(feature = "nats")] -pub mod nats_sync; -pub mod ops_surface; -#[cfg(feature = "substrate")] -pub mod rotation; #[cfg(feature = "graphql")] pub mod graphql; +pub mod keypair; #[cfg(feature = "mcp")] pub mod mcp; -#[cfg(feature = "nats")] -pub mod nats; -pub mod notifications; -pub mod positioning; -pub mod registry; pub mod memory; pub mod memory_types; -pub mod views; +#[cfg(feature = "nats")] +pub mod nats; +#[cfg(feature = "nats")] +pub mod nats_sync; +pub mod notifications; +pub mod ops_surface; +pub mod positioning; +pub mod registry; +#[cfg(feature = "substrate")] +pub mod rotation; pub mod search; #[cfg(feature = "db")] pub mod seed; #[cfg(feature = "ui")] pub mod session; pub mod sync; +pub mod views; // ADR-033 tier/transition/reload primitives — moved to ontoref-ops so the // catalogued op handler can reuse them. Re-exported here for backward // compatibility with consumers that imported via ontoref_daemon::*. diff --git a/crates/ontoref-daemon/src/main.rs b/crates/ontoref-daemon/src/main.rs index cfa05b0..055cc0b 100644 --- a/crates/ontoref-daemon/src/main.rs +++ b/crates/ontoref-daemon/src/main.rs @@ -320,14 +320,14 @@ struct Cli { #[arg(long)] actor: Option, - /// Sign an arbitrary payload (e.g. a substrate-view adjustment digest, ADR-046 - /// C2) with the actor's Ed25519 key, print `{actor, payload, signature, - /// public_key}` as JSON, then exit. Requires --actor. + /// Sign an arbitrary payload (e.g. a substrate-view adjustment digest, + /// ADR-046 C2) with the actor's Ed25519 key, print `{actor, payload, + /// signature, public_key}` as JSON, then exit. Requires --actor. #[arg(long, value_name = "PAYLOAD", requires = "actor")] sign_payload: Option, - /// Verify a payload signature. Requires --actor-pubkey and --signature. Prints - /// `{valid}` and exits 0 (valid) or 4 (invalid). + /// Verify a payload signature. Requires --actor-pubkey and --signature. + /// Prints `{valid}` and exits 0 (valid) or 4 (invalid). #[arg(long, value_name = "PAYLOAD", requires_all = ["actor_pubkey", "signature"])] verify_payload: Option, @@ -516,9 +516,7 @@ fn apply_keys_overlay( fn init_tera( templates_dir: Option<&std::path::Path>, ) -> Option>> { - use ui::{ - AssetUrls, Brand, BrandKind, SessionOptions, TeraOptions, ThemeOptions, UiFeatures, - }; + use ui::{AssetUrls, Brand, BrandKind, SessionOptions, TeraOptions, ThemeOptions, UiFeatures}; let tdir = templates_dir?; let cache_buster = std::time::SystemTime::now() @@ -681,7 +679,9 @@ async fn main() { let Some(classification) = ontoref_ontology_content::OntologyType::parse(type_tag) else { eprintln!( - "extract-ontology: unknown --extract-type-tag '{type_tag}'; expected Type1Protocol | Type2aFederable | Type2bMandatoryDecentralized | Type3ProjectSpecific | Type4Private" + "extract-ontology: unknown --extract-type-tag '{type_tag}'; expected \ + Type1Protocol | Type2aFederable | Type2bMandatoryDecentralized | \ + Type3ProjectSpecific | Type4Private" ); std::process::exit(2); }; @@ -756,8 +756,11 @@ async fn main() { std::process::exit(2); } }; - match ontoref_daemon::keypair::sign_payload_for_actor(actor_id, &key_dir, payload.as_bytes()) - { + match ontoref_daemon::keypair::sign_payload_for_actor( + actor_id, + &key_dir, + payload.as_bytes(), + ) { Ok((signature, public_key)) => { println!( "{}", @@ -791,8 +794,8 @@ async fn main() { // FIPS-204 plug-in slot: recognized scheme, backend not built into // this binary (would require the `pqc` feature + a validated module). eprintln!( - "verify-payload: scheme 'ml-dsa-65' recognized but not built — \ - rebuild with the pqc feature" + "verify-payload: scheme 'ml-dsa-65' recognized but not built — rebuild with \ + the pqc feature" ); std::process::exit(5); } @@ -1229,8 +1232,8 @@ async fn main() { } } else { warn!( - "ONTOREF_SIGNING_KEY_FILE not set — generating ephemeral Ed25519 key; \ - JWKS will change on every restart" + "ONTOREF_SIGNING_KEY_FILE not set — generating ephemeral Ed25519 key; JWKS will \ + change on every restart" ); Arc::new(auth::TokenIssuer::generate(issuer_id)) } @@ -1380,11 +1383,7 @@ async fn main() { // the guard via `ProjectContext::apply_ops_reload`; rejected reloads // log a Block warning and the in-memory ops value is retained. let _ops_watcher = { - let ops_config_path = registry - .primary() - .root - .join(".ontoref") - .join("config.ncl"); + let ops_config_path = registry.primary().root.join(".ontoref").join("config.ncl"); if ops_config_path.exists() { let primary_ref = registry.primary(); match ontoref_daemon::watcher::OpsWatcher::start( @@ -1461,8 +1460,8 @@ async fn main() { .layer(axum::middleware::from_fn( |req: axum::extract::Request, next: axum::middleware::Next| async move { let method = req.method().clone(); - let uri = req.uri().clone(); - let resp = next.run(req).await; + let uri = req.uri().clone(); + let resp = next.run(req).await; let status = resp.status().as_u16(); if status >= 500 { tracing::error!(method = %method, uri = %uri, status = status, "5xx"); @@ -1954,8 +1953,8 @@ fn extract_ontology_now( String::from_utf8_lossy(&output.stderr).trim() )); } - let json: serde_json::Value = serde_json::from_slice(&output.stdout) - .map_err(|e| format!("parsing nickel JSON: {e}"))?; + let json: serde_json::Value = + serde_json::from_slice(&output.stdout).map_err(|e| format!("parsing nickel JSON: {e}"))?; // Deterministic signing key derived from the ontology name — keeps // re-runs idempotent without requiring per-extraction keypair setup. @@ -1973,9 +1972,8 @@ fn extract_ontology_now( .map_err(|e| format!("creating {}: {e}", parent.display()))?; } - let stats = - ontoref_ontology_content::extract_core_to_oplog(&json, &oplog_path, &signing_key) - .map_err(|e| format!("extraction: {e}"))?; + let stats = ontoref_ontology_content::extract_core_to_oplog(&json, &oplog_path, &signing_key) + .map_err(|e| format!("extraction: {e}"))?; let refs_path = ontoref_ontology::layout::resolve_section(project_root, "ontology/_refs.ncl"); ontoref_ontology_content::write_ontology_refs( diff --git a/crates/ontoref-daemon/src/mcp/mod.rs b/crates/ontoref-daemon/src/mcp/mod.rs index 90eb426..4b46aa2 100644 --- a/crates/ontoref-daemon/src/mcp/mod.rs +++ b/crates/ontoref-daemon/src/mcp/mod.rs @@ -103,7 +103,8 @@ struct GetItemInput { #[derive(Deserialize, JsonSchema, Default)] struct DisciplineInput { - /// Discipline (work area) to mount — e.g. `coding`, `positioning`, `difusion`. + /// Discipline (work area) to mount — e.g. `coding`, `positioning`, + /// `difusion`. discipline: String, /// Project slug. Omit to use the default project. project: Option, @@ -602,11 +603,12 @@ impl AsyncTool for ViewListTool { #[ontoref_derive::onto_mcp_tool( name = "ontoref_view_mount", - description = "Mount a discipline's substrate view: the composed recipe (declared prior \ - plus learned tuning) of which levels to load, ordered by salience, with \ - out-of-repo levels resolved by witness. The agent-facing provisioning surface.", + description = "Mount a discipline's substrate view: the composed recipe (declared prior plus \ + learned tuning) of which levels to load, ordered by salience, with out-of-repo \ + levels resolved by witness. The agent-facing provisioning surface.", category = "discovery", - params = "discipline:string:required:Discipline/work area (e.g. coding, positioning); project:string:optional:Project slug" + params = "discipline:string:required:Discipline/work area (e.g. coding, positioning); \ + project:string:optional:Project slug" )] struct ViewMountTool; @@ -620,7 +622,11 @@ impl ToolBase for ViewMountTool { } fn description() -> Option> { - Some("Mount a discipline's substrate view — composed recipe, prior plus learned tuning (ADR-046).".into()) + Some( + "Mount a discipline's substrate view — composed recipe, prior plus learned tuning \ + (ADR-046)." + .into(), + ) } fn output_schema() -> Option> { @@ -691,7 +697,8 @@ impl AsyncTool for DescribeProjectTool { ) -> Result { debug!(tool = "describe_project", project = ?param.project); let ctx = service.project_ctx(param.project.as_deref()); - let manifest_path = ontoref_ontology::layout::resolve_section(&ctx.root, "ontology/manifest.ncl"); + let manifest_path = + ontoref_ontology::layout::resolve_section(&ctx.root, "ontology/manifest.ncl"); let manifest = if manifest_path.exists() { match ctx @@ -1001,7 +1008,8 @@ impl AsyncTool for GetOntologyExtensionTool { ))); } - let path = ontoref_ontology::layout::resolve_section(&ctx.root, &format!("ontology/{file}")); + let path = + ontoref_ontology::layout::resolve_section(&ctx.root, &format!("ontology/{file}")); if !path.exists() { return Err(ToolError(format!( "ontology extension '{}' not found", @@ -1203,7 +1211,8 @@ impl AsyncTool for GetBacklogTool { ) -> Result { debug!(tool = "get_backlog", project = ?param.project, status = ?param.status); let ctx = service.project_ctx(param.project.as_deref()); - let backlog_path = ontoref_ontology::layout::resolve_section(&ctx.root, "reflection/backlog.ncl"); + let backlog_path = + ontoref_ontology::layout::resolve_section(&ctx.root, "reflection/backlog.ncl"); if !backlog_path.exists() { return Ok(serde_json::json!({ "items": [] })); @@ -1555,7 +1564,8 @@ impl AsyncTool for ProjectStatusTool { let recent_notifications = service.state.notifications.all_recent().len(); // Backlog stats - let backlog_path = ontoref_ontology::layout::resolve_section(&ctx.root, "reflection/backlog.ncl"); + let backlog_path = + ontoref_ontology::layout::resolve_section(&ctx.root, "reflection/backlog.ncl"); let backlog_stats = if backlog_path.exists() { match ctx .cache @@ -1585,25 +1595,30 @@ impl AsyncTool for ProjectStatusTool { }; // ADR count - let adr_count = std::fs::read_dir(ontoref_ontology::layout::resolve_section(&ctx.root, "adrs")) - .map(|rd| { - rd.flatten() - .filter(|e| e.path().extension().and_then(|x| x.to_str()) == Some("ncl")) - .count() - }) - .unwrap_or(0); + let adr_count = + std::fs::read_dir(ontoref_ontology::layout::resolve_section(&ctx.root, "adrs")) + .map(|rd| { + rd.flatten() + .filter(|e| e.path().extension().and_then(|x| x.to_str()) == Some("ncl")) + .count() + }) + .unwrap_or(0); // Mode count - let mode_count = std::fs::read_dir(ontoref_ontology::layout::resolve_section(&ctx.root, "reflection/modes")) - .map(|rd| { - rd.flatten() - .filter(|e| e.path().extension().and_then(|x| x.to_str()) == Some("ncl")) - .count() - }) - .unwrap_or(0); + let mode_count = std::fs::read_dir(ontoref_ontology::layout::resolve_section( + &ctx.root, + "reflection/modes", + )) + .map(|rd| { + rd.flatten() + .filter(|e| e.path().extension().and_then(|x| x.to_str()) == Some("ncl")) + .count() + }) + .unwrap_or(0); // Manifest - let manifest_path = ontoref_ontology::layout::resolve_section(&ctx.root, "ontology/manifest.ncl"); + let manifest_path = + ontoref_ontology::layout::resolve_section(&ctx.root, "ontology/manifest.ncl"); let manifest = if manifest_path.exists() { match ctx .cache @@ -1732,7 +1747,8 @@ impl AsyncTool for BacklogOpTool { ) -> Result { debug!(tool = "backlog", operation = %param.operation, project = ?param.project); let ctx = service.project_ctx(param.project.as_deref()); - let backlog_path = ontoref_ontology::layout::resolve_section(&ctx.root, "reflection/backlog.ncl"); + let backlog_path = + ontoref_ontology::layout::resolve_section(&ctx.root, "reflection/backlog.ncl"); if !backlog_path.exists() { return Err(ToolError(format!( @@ -2816,7 +2832,8 @@ impl AsyncTool for BookmarkListTool { ) -> Result { debug!(tool = "bookmark_list", project = ?param.project); let ctx = service.project_ctx(param.project.as_deref()); - let bm_path = ontoref_ontology::layout::resolve_section(&ctx.root, "reflection/search_bookmarks.ncl"); + let bm_path = + ontoref_ontology::layout::resolve_section(&ctx.root, "reflection/search_bookmarks.ncl"); if !bm_path.exists() { return Ok(serde_json::json!({ "entries": [], "count": 0 })); @@ -2902,7 +2919,8 @@ impl AsyncTool for BookmarkAddTool { ) -> Result { debug!(tool = "bookmark_add", project = ?param.project, node_id = %param.node_id); let ctx = service.project_ctx(param.project.as_deref()); - let bm_path = ontoref_ontology::layout::resolve_section(&ctx.root, "reflection/search_bookmarks.ncl"); + let bm_path = + ontoref_ontology::layout::resolve_section(&ctx.root, "reflection/search_bookmarks.ncl"); if !bm_path.exists() { return Err(ToolError(format!( @@ -3537,12 +3555,7 @@ impl AsyncTool for PositioningGetTool { let live_value_props = p .value_props() .iter() - .filter(|v| { - matches!( - v.status, - ontoref_ontology::types::PositioningStatus::Live - ) - }) + .filter(|v| matches!(v.status, ontoref_ontology::types::PositioningStatus::Live)) .count(); Ok(serde_json::json!({ "audiences": p.audiences(), @@ -3582,8 +3595,8 @@ impl ToolBase for PositioningGetValuePropTool { fn description() -> Option> { Some( - "Single value-prop by id with audiences expanded inline. Saves a second tool call: \ - an agent reasoning about an audience for a claim gets the resolved Audience records \ + "Single value-prop by id with audiences expanded inline. Saves a second tool call: an \ + agent reasoning about an audience for a claim gets the resolved Audience records \ alongside the value-prop in one response." .into(), ) @@ -3643,9 +3656,10 @@ impl ToolBase for PositioningAuditTool { fn description() -> Option> { Some( - "Cross-reference evidence_adrs against ADR status across all value-props. Drift kinds: \ - Missing (cited file absent), Superseded (with optional by= replacement), Deprecated, \ - Proposed (claim cites uncommitted decision). Returns { checked_value_props, drift }." + "Cross-reference evidence_adrs against ADR status across all value-props. Drift \ + kinds: Missing (cited file absent), Superseded (with optional by= replacement), \ + Deprecated, Proposed (claim cites uncommitted decision). Returns { \ + checked_value_props, drift }." .into(), ) } @@ -3686,25 +3700,34 @@ impl ToolBase for DomainListTool { type Output = serde_json::Value; type Error = ToolError; - fn name() -> Cow<'static, str> { "ontoref_domain_list".into() } + fn name() -> Cow<'static, str> { + "ontoref_domain_list".into() + } fn description() -> Option> { Some( - "Return all domains the project implements as { id, kind, path }[]. \ - kind: config_flag (domain_X=true in config.ncl), domain_origin (manifest.ncl), \ - or impl_dir (.{domain}.ontoref/ directory). \ - Use to navigate from a consumer project to its domain source." + "Return all domains the project implements as { id, kind, path }[]. kind: config_flag \ + (domain_X=true in config.ncl), domain_origin (manifest.ncl), or impl_dir \ + (.{domain}.ontoref/ directory). Use to navigate from a consumer project to its \ + domain source." .into(), ) } - fn output_schema() -> Option> { None } + fn output_schema() -> Option> { + None + } } impl AsyncTool for DomainListTool { - async fn invoke(service: &OntoreServer, param: ProjectParam) -> Result { + async fn invoke( + service: &OntoreServer, + param: ProjectParam, + ) -> Result { debug!(tool = "domain_list", project = ?param.project); - let pctx = param.project.as_deref() + let pctx = param + .project + .as_deref() .and_then(|s| service.state.registry.get(s)) .unwrap_or_else(|| service.state.registry.primary()); let decls = crate::domain::project_domains(&pctx.root); @@ -3722,7 +3745,8 @@ struct DomainIdInput { #[ontoref_derive::onto_mcp_tool( name = "ontoref_domain_consumers", - description = "List all registered projects that implement a given domain (domain → consumers direction).", + description = "List all registered projects that implement a given domain (domain → consumers \ + direction).", category = "domain", params = "domain_id:string:required:Domain id (e.g. rustelo, provisioning)" )] @@ -3733,22 +3757,29 @@ impl ToolBase for DomainConsumersTool { type Output = serde_json::Value; type Error = ToolError; - fn name() -> Cow<'static, str> { "ontoref_domain_consumers".into() } + fn name() -> Cow<'static, str> { + "ontoref_domain_consumers".into() + } fn description() -> Option> { Some( - "Return all registered projects implementing the given domain id. \ - Result: { domain_id, consumers: [{ slug, root, kind }] }. \ - Use to navigate from a domain project to all its consumer implementations." + "Return all registered projects implementing the given domain id. Result: { \ + domain_id, consumers: [{ slug, root, kind }] }. Use to navigate from a domain \ + project to all its consumer implementations." .into(), ) } - fn output_schema() -> Option> { None } + fn output_schema() -> Option> { + None + } } impl AsyncTool for DomainConsumersTool { - async fn invoke(service: &OntoreServer, param: DomainIdInput) -> Result { + async fn invoke( + service: &OntoreServer, + param: DomainIdInput, + ) -> Result { debug!(tool = "domain_consumers", id = %param.domain_id); let consumers = crate::domain::consumers_of(&service.state.registry, ¶m.domain_id); Ok(serde_json::json!({ "domain_id": param.domain_id, "consumers": consumers })) @@ -3770,21 +3801,28 @@ impl ToolBase for DomainShowTool { type Output = serde_json::Value; type Error = ToolError; - fn name() -> Cow<'static, str> { "ontoref_domain_show".into() } + fn name() -> Cow<'static, str> { + "ontoref_domain_show".into() + } fn description() -> Option> { Some( - "Return domain metadata (name, description, schema_cmd) and all registered \ - consumer projects. Combines the domain definition with the live consumer graph." + "Return domain metadata (name, description, schema_cmd) and all registered consumer \ + projects. Combines the domain definition with the live consumer graph." .into(), ) } - fn output_schema() -> Option> { None } + fn output_schema() -> Option> { + None + } } impl AsyncTool for DomainShowTool { - async fn invoke(service: &OntoreServer, param: DomainIdInput) -> Result { + async fn invoke( + service: &OntoreServer, + param: DomainIdInput, + ) -> Result { debug!(tool = "domain_show", id = %param.domain_id); let ontoref_root = std::env::var("ONTOREF_ROOT") .map(std::path::PathBuf::from) @@ -3971,9 +4009,8 @@ impl AsyncTool for TierNextMcpTool { name = "ontoref_chronicle", description = "Chronicle timeline: Accepted ADRs and protocol migrations, newest first.", category = "memory", - params = "project:string:optional:Project slug; \ - since:string:optional:ISO date lower bound (e.g. 2026-01-01); \ - kind:string:optional:adr|migration; \ + params = "project:string:optional:Project slug; since:string:optional:ISO date lower bound \ + (e.g. 2026-01-01); kind:string:optional:adr|migration; \ domain:string:optional:Domain filter" )] struct ChronicleListTool; @@ -4069,8 +4106,7 @@ impl AsyncTool for RoadmapListTool { name = "ontoref_criteria", description = "Criteria promoted to catalog validators (ADR-049/050).", category = "memory", - params = "project:string:optional:Project slug; \ - kind:string:optional:normative" + params = "project:string:optional:Project slug; kind:string:optional:normative" )] struct CriteriaListTool; @@ -4106,10 +4142,8 @@ impl AsyncTool for CriteriaListTool { param.kind.as_deref(), ) .await; - serde_json::to_value( - serde_json::json!({ "count": criteria.len(), "criteria": criteria }), - ) - .map_err(ToolError::from) + serde_json::to_value(serde_json::json!({ "count": criteria.len(), "criteria": criteria })) + .map_err(ToolError::from) } } diff --git a/crates/ontoref-daemon/src/memory.rs b/crates/ontoref-daemon/src/memory.rs index f6088d1..39b9769 100644 --- a/crates/ontoref-daemon/src/memory.rs +++ b/crates/ontoref-daemon/src/memory.rs @@ -1,8 +1,8 @@ //! Read-query functions for the constellation-memory surface (component J, //! ADR-051 / ADR-053). Each function reads NCL files via the cache and returns -//! typed vectors — the same underlying data regardless of which surface calls it -//! (HTTP GET, GraphQL resolver, MCP tool). This is the G-4 equivalence contract -//! made concrete: one data path, four display surfaces. +//! typed vectors — the same underlying data regardless of which surface calls +//! it (HTTP GET, GraphQL resolver, MCP tool). This is the G-4 equivalence +//! contract made concrete: one data path, four display surfaces. use std::path::Path; use std::sync::Arc; @@ -16,9 +16,10 @@ fn str_val<'a>(v: &'a serde_json::Value, key: &str) -> &'a str { /// Chronicle entries woven from ADRs and protocol migrations. /// -/// Sources: `.ontoref/adrs/*.ncl` (Accepted status) + `.ontoref/reflection/migrations/*.ncl`. -/// Optional filters: `since` (ISO date prefix), `kind` ("adr" | "migration"), `domain`. -/// Result is sorted descending by date. +/// Sources: `.ontoref/adrs/*.ncl` (Accepted status) + +/// `.ontoref/reflection/migrations/*.ncl`. Optional filters: `since` (ISO date +/// prefix), `kind` ("adr" | "migration"), `domain`. Result is sorted descending +/// by date. pub async fn chronicle_entries( root: &Path, cache: &Arc, @@ -47,13 +48,20 @@ pub async fn chronicle_entries( let id = str_val(&json, "id").to_owned(); let date = str_val(&json, "date").to_owned(); let title_raw = str_val(&json, "title"); - let title = if title_raw.is_empty() { id.clone() } else { title_raw.to_owned() }; + let title = if title_raw.is_empty() { + id.clone() + } else { + title_raw.to_owned() + }; entries.push(ChronicleEntry { id: format!("adr:{id}"), date, kind: "adr".to_owned(), title, - summary: json.get("context").and_then(|v| v.as_str()).map(str::to_owned), + summary: json + .get("context") + .and_then(|v| v.as_str()) + .map(str::to_owned), domain: None, refs: if id.is_empty() { vec![] } else { vec![id] }, }); @@ -121,7 +129,8 @@ pub async fn chronicle_entries( /// Roadmap items derived from backlog entries. /// -/// Source: `.ontoref/reflection/backlog.ncl`. Optional filters: `status`, `horizon`. +/// Source: `.ontoref/reflection/backlog.ncl`. Optional filters: `status`, +/// `horizon`. pub async fn roadmap_items( root: &Path, cache: &Arc, @@ -185,8 +194,9 @@ pub async fn roadmap_items( /// Criteria derived from catalog validator declarations. /// -/// Source: `.ontoref/catalog/validators/*.ncl`. Optional filter: `kind` ("normative"). -/// All catalog-declared validators are normative (explicitly promoted per ADR-050). +/// Source: `.ontoref/catalog/validators/*.ncl`. Optional filter: `kind` +/// ("normative"). All catalog-declared validators are normative (explicitly +/// promoted per ADR-050). pub async fn criteria_list( root: &Path, cache: &Arc, @@ -232,11 +242,19 @@ pub async fn criteria_list( provenance: None, validator_ref: { let r = str_val(&json, "predicate_ref"); - if r.is_empty() { None } else { Some(r.to_owned()) } + if r.is_empty() { + None + } else { + Some(r.to_owned()) + } }, related_adr: { let r = str_val(&json, "criterion_ref"); - if r.is_empty() { None } else { Some(r.to_owned()) } + if r.is_empty() { + None + } else { + Some(r.to_owned()) + } }, }); } @@ -245,9 +263,11 @@ pub async fn criteria_list( criteria } -/// Panel specs resolved from dedicated panel-*.ncl files in the presentation directory. +/// Panel specs resolved from dedicated panel-*.ncl files in the presentation +/// directory. /// -/// Source: `.ontoref/presentation/panel-*.ncl`. Optional filter: `kind` (Timeline/Gantt/…). +/// Source: `.ontoref/presentation/panel-*.ncl`. Optional filter: `kind` +/// (Timeline/Gantt/…). pub async fn panels_list( root: &Path, cache: &Arc, @@ -266,12 +286,8 @@ pub async fn panels_list( let mut panels = Vec::new(); for entry in rd.flatten() { let path = entry.path(); - let stem = path - .file_stem() - .and_then(|s| s.to_str()) - .unwrap_or(""); - if path.extension().and_then(|e| e.to_str()) != Some("ncl") || !stem.starts_with("panel-") - { + let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or(""); + if path.extension().and_then(|e| e.to_str()) != Some("ncl") || !stem.starts_with("panel-") { continue; } let Ok((json, _)) = cache.export(&path, import_path).await else { diff --git a/crates/ontoref-daemon/src/memory_types.rs b/crates/ontoref-daemon/src/memory_types.rs index 51f2b9e..d314041 100644 --- a/crates/ontoref-daemon/src/memory_types.rs +++ b/crates/ontoref-daemon/src/memory_types.rs @@ -8,7 +8,8 @@ use serde::{Deserialize, Serialize}; -/// One woven point on the retrospective chronicle timeline (ADR-051, component B). +/// One woven point on the retrospective chronicle timeline (ADR-051, component +/// B). #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "graphql", derive(async_graphql::SimpleObject))] pub struct ChronicleEntry { @@ -21,7 +22,8 @@ pub struct ChronicleEntry { pub refs: Vec, } -/// A roadmap item composed from the backlog roadmap and positioning roadmap (component B/G). +/// A roadmap item composed from the backlog roadmap and positioning roadmap +/// (component B/G). #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "graphql", derive(async_graphql::SimpleObject))] pub struct RoadmapItem { @@ -46,7 +48,8 @@ pub struct Criterion { pub related_adr: Option, } -/// A resolved panel descriptor — identifying and dispatch fields of a Panel (ADR-053). +/// A resolved panel descriptor — identifying and dispatch fields of a Panel +/// (ADR-053). #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "graphql", derive(async_graphql::SimpleObject))] pub struct PanelSpec { diff --git a/crates/ontoref-daemon/src/nats_sync.rs b/crates/ontoref-daemon/src/nats_sync.rs index daf2d6e..7536782 100644 --- a/crates/ontoref-daemon/src/nats_sync.rs +++ b/crates/ontoref-daemon/src/nats_sync.rs @@ -20,12 +20,11 @@ use std::collections::HashMap; use std::sync::Mutex; use bytes::Bytes; -use tokio::runtime::Runtime; - use ontoref_sync::{SyncBackend, SyncError}; use ontoref_types::{canonical, Operation}; use platform_nats::topology::{AckPolicy, ConsumerDef, Retention, Storage, StreamDef}; use platform_nats::{EventStream, NatsConnectionConfig, TopologyConfig}; +use tokio::runtime::Runtime; /// NATS JetStream sync backend bound to one project stream and one durable /// consumer (per local peer). Holds its own runtime — see module docs. @@ -239,13 +238,14 @@ fn anyhow_io(context: &str, e: &anyhow::Error) -> SyncError { #[cfg(test)] mod tests { - use super::*; use ed25519_dalek::SigningKey; use ontoref_types::{ operation::{OpBody, OpPayload}, AttrId, EntityId, Hlc, PublicKey, Value, }; + use super::*; + /// End-to-end round-trip against a real broker. Env-gated on /// `ONTOREF_NATS_URL` (e.g. `nats://0.0.0.0:4222`); skips when unset so /// CI without a broker stays green. Run with the broker: diff --git a/crates/ontoref-daemon/src/ops_surface.rs b/crates/ontoref-daemon/src/ops_surface.rs index a08761c..7242972 100644 --- a/crates/ontoref-daemon/src/ops_surface.rs +++ b/crates/ontoref-daemon/src/ops_surface.rs @@ -8,22 +8,20 @@ //! produce equivalent witnesses for the same `(op_id, inputs)`; this //! function is the contract. -use std::path::PathBuf; - -use ed25519_dalek::SigningKey; -use ontoref_ontology::types::StateConfig; -use ontoref_ops::{dispatch_op, OpContext, OpError, PipelineResult}; -use serde::{Deserialize, Serialize}; - #[cfg(feature = "substrate")] use std::collections::HashMap; #[cfg(feature = "substrate")] use std::path::Path; +use std::path::PathBuf; #[cfg(feature = "substrate")] use std::sync::{Arc, Mutex, OnceLock}; +use ed25519_dalek::SigningKey; +use ontoref_ontology::types::StateConfig; #[cfg(feature = "substrate")] use ontoref_oplog::OpLog; +use ontoref_ops::{dispatch_op, OpContext, OpError, PipelineResult}; +use serde::{Deserialize, Serialize}; /// JSON envelope every surface decodes into. `inputs` is the op-specific /// payload; the remaining fields configure the [`OpContext`]. @@ -102,10 +100,11 @@ fn parse_seed_hex(hex: &str) -> Result<[u8; 32], DispatchSurfaceError> { } let mut out = [0u8; 32]; for i in 0..32 { - out[i] = u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16) - .map_err(|_| DispatchSurfaceError::InvalidSeed { + out[i] = u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16).map_err(|_| { + DispatchSurfaceError::InvalidSeed { reason: format!("non-hex byte at position {}", i * 2), - })?; + } + })?; } Ok(out) } @@ -186,11 +185,12 @@ fn oplog_for(project_root: &Path) -> Result>, DispatchSurfaceE if let Some(existing) = cache.get(&oplog_path) { return Ok(Some(Arc::clone(existing))); } - let opened = Arc::new(OpLog::open(&oplog_path).map_err(|e| { - DispatchSurfaceError::OplogUnavailable { - reason: format!("{}: {e}", oplog_path.display()), - } - })?); + let opened = + Arc::new( + OpLog::open(&oplog_path).map_err(|e| DispatchSurfaceError::OplogUnavailable { + reason: format!("{}: {e}", oplog_path.display()), + })?, + ); cache.insert(oplog_path, Arc::clone(&opened)); Ok(Some(opened)) } @@ -352,7 +352,11 @@ mod substrate_tests { "no oplog file → tier-0 → in-memory, no persistence" ); assert!( - !root.join(".ontoref").join("substrate").join("oplog.redb").exists(), + !root + .join(".ontoref") + .join("substrate") + .join("oplog.redb") + .exists(), "in-memory dispatch must not create an oplog" ); } diff --git a/crates/ontoref-daemon/src/positioning.rs b/crates/ontoref-daemon/src/positioning.rs index dd2a5e6..fe3c7ef 100644 --- a/crates/ontoref-daemon/src/positioning.rs +++ b/crates/ontoref-daemon/src/positioning.rs @@ -8,7 +8,8 @@ //! - `GET /api/positioning` — full layer //! - `GET /api/positioning/audience/:id` — single audience //! - `GET /api/positioning/value-prop/:id` — single value-prop -//! - `POST /api/positioning/audit` — drift report (evidence_adrs → ADR status) +//! - `POST /api/positioning/audit` — drift report (evidence_adrs → ADR +//! status) use std::path::{Path, PathBuf}; @@ -92,9 +93,7 @@ pub async fn positioning_get( let realized_viability_paths = p .viability_paths() .iter() - .filter(|v| { - v.status != PositioningStatus::Draft && v.realized_outcome.is_some() - }) + .filter(|v| v.status != PositioningStatus::Draft && v.realized_outcome.is_some()) .count(); let counts = PositioningCounts { audiences: p.audiences().len(), @@ -194,7 +193,8 @@ pub enum AdrIssue { /// - `Missing` — cited ADR file does not exist /// - `Superseded` — ADR status is `'Superseded` /// - `Deprecated` — ADR status is `'Deprecated` -/// - `Proposed` — ADR is still `'Proposed` (claim cites uncommitted decision) +/// - `Proposed` — ADR is still `'Proposed` (claim cites uncommitted +/// decision) /// /// Only Accepted ADRs are clean backing for a Live value-prop. The audit /// reports drift across ALL value-props regardless of status — `Draft` ones @@ -218,10 +218,7 @@ pub async fn positioning_audit( /// /// Extracted from [`positioning_audit`] so the MCP surface and any other /// caller can reuse it without going through axum extractors. -pub(crate) fn audit_positioning( - p: &Positioning, - adrs_dir: &Path, -) -> AuditReport { +pub(crate) fn audit_positioning(p: &Positioning, adrs_dir: &Path) -> AuditReport { let mut drift: Vec = Vec::new(); for vp in p.value_props() { for adr_id in &vp.evidence_adrs { @@ -317,10 +314,7 @@ mod tests { #[test] fn classify_proposed_returns_proposed() { let txt = "status = 'Proposed,"; - assert!(matches!( - classify_adr_status(txt), - Some(AdrIssue::Proposed) - )); + assert!(matches!(classify_adr_status(txt), Some(AdrIssue::Proposed))); } #[test] diff --git a/crates/ontoref-daemon/src/registry.rs b/crates/ontoref-daemon/src/registry.rs index d3ba1cf..b7ae711 100644 --- a/crates/ontoref-daemon/src/registry.rs +++ b/crates/ontoref-daemon/src/registry.rs @@ -532,16 +532,16 @@ impl ProjectRegistry { /// In addition to the project's declared paths, two sets of extras are always /// appended: /// -/// 1. **Project-local schema dirs** — derived from [`LayoutConfig::detect`] -/// via [`project_schema_dirs`]. Covers `.ontoref/ontology/`, +/// 1. **Project-local schema dirs** — derived from [`LayoutConfig::detect`] via +/// [`project_schema_dirs`]. Covers `.ontoref/ontology/`, /// `.ontoref/ontology/schemas/`, `.ontoref/adrs/`, etc. so imports like /// `import "manifest"` or `import "schemas/connections"` resolve from /// anywhere inside the project. Both canonical and legacy layouts are /// included when present on disk. /// -/// 2. **User-global schemas dir** — `~/.config/ontoref/schemas/` so -/// `import "ontoref-project.ncl"` (the canonical project schema) -/// resolves even when the project does not declare it. +/// 2. **User-global schemas dir** — `~/.config/ontoref/schemas/` so `import +/// "ontoref-project.ncl"` (the canonical project schema) resolves even when +/// the project does not declare it. fn user_global_schemas_path() -> Option { let home = std::env::var_os("HOME")?; let p = Path::new(&home) @@ -646,12 +646,13 @@ pub struct ContextSpec { /// - the manifest has no `config_surface` field /// - nickel export or deserialisation fails (logged at warn level) pub fn load_config_surface(root: &Path, import_path: Option<&str>) -> Option { - // This used to hardcode the pre-0023 manifest location with no fallback, so on any - // project migrated to the consolidated layout the file was never found: the function - // returned None and the config surface silently vanished. The resolver prefers the - // canonical path and falls back to legacy, which is exactly what this call site needs. - // (Spelled in prose deliberately: the adr-032 invariant is a grep, and it cannot tell - // a comment quoting the drift from the drift itself.) + // This used to hardcode the pre-0023 manifest location with no fallback, so on + // any project migrated to the consolidated layout the file was never found: + // the function returned None and the config surface silently vanished. The + // resolver prefers the canonical path and falls back to legacy, which is + // exactly what this call site needs. (Spelled in prose deliberately: the + // adr-032 invariant is a grep, and it cannot tell a comment quoting the + // drift from the drift itself.) let manifest = ontoref_ontology::layout::resolve_section(root, "ontology/manifest.ncl"); if !manifest.exists() { return None; @@ -930,8 +931,7 @@ mod tests { #[test] fn absolute_path_returned_as_is() { - let result = - resolve_import_path_inner(&["/abs/path".to_string()], Path::new("/root"), &[]); + let result = resolve_import_path_inner(&["/abs/path".to_string()], Path::new("/root"), &[]); assert_eq!(result, Some("/abs/path".to_string())); } @@ -989,10 +989,11 @@ mod tests { ); } - /// Materialise a PRE-0023 section dir. Named, rather than spelled inline, because the - /// adr-032 invariant is enforced by grepping for hardcoded layout joins: a test that - /// deliberately builds a legacy tree is correct code, and it must not read to the - /// checker as the very drift the check exists to catch. + /// Materialise a PRE-0023 section dir. Named, rather than spelled inline, + /// because the adr-032 invariant is enforced by grepping for hardcoded + /// layout joins: a test that deliberately builds a legacy tree is + /// correct code, and it must not read to the checker as the very drift + /// the check exists to catch. fn legacy_section(root: &Path, relative: &str) -> PathBuf { root.join(relative) } diff --git a/crates/ontoref-daemon/src/rotation.rs b/crates/ontoref-daemon/src/rotation.rs index f2a4b33..b9553c7 100644 --- a/crates/ontoref-daemon/src/rotation.rs +++ b/crates/ontoref-daemon/src/rotation.rs @@ -82,9 +82,10 @@ pub fn rotate_actor_key( #[cfg(test)] mod tests { - use super::*; use ontoref_types::{operation::OpPayload, valid_keys_for_actor, PublicKey}; + use super::*; + #[test] fn rotation_records_succession_and_preserves_old_key_validity() { let tmp = tempfile::tempdir().unwrap(); @@ -117,11 +118,17 @@ mod tests { let ops = oplog.all_ops_in_hlc_order().unwrap(); assert_eq!(ops.len(), 1); ops[0].verify_signature().expect("succession verifies"); - assert!(matches!(ops[0].body.payload, OpPayload::KeySuccession { .. })); + assert!(matches!( + ops[0].body.payload, + OpPayload::KeySuccession { .. } + )); assert_eq!(ops[0].body.actor, old_pub, "signed by the retiring key"); let valid = valid_keys_for_actor(&ops, "agent", new_pub); - assert!(valid.contains(&old_pub), "witnesses under the old key still verify"); + assert!( + valid.contains(&old_pub), + "witnesses under the old key still verify" + ); assert!(valid.contains(&new_pub)); } } diff --git a/crates/ontoref-daemon/src/session.rs b/crates/ontoref-daemon/src/session.rs index a371b4d..0c943ef 100644 --- a/crates/ontoref-daemon/src/session.rs +++ b/crates/ontoref-daemon/src/session.rs @@ -8,13 +8,12 @@ use std::time::Duration; +pub use auth::session::SessionView; use auth::session::{ self as auth_session, RevokeAuthorisation, RevokeResult as AuthRevokeResult, Session, SessionStore as AuthSessionStore, }; -pub use auth::session::SessionView; - use crate::registry::Role; /// Cookie name used by the daemon UI for browser sessions. diff --git a/crates/ontoref-daemon/src/ui/handlers/assets.rs b/crates/ontoref-daemon/src/ui/handlers/assets.rs index 730ce6c..98b5602 100644 --- a/crates/ontoref-daemon/src/ui/handlers/assets.rs +++ b/crates/ontoref-daemon/src/ui/handlers/assets.rs @@ -2,9 +2,8 @@ use axum::extract::{Path, State}; use axum::http::{header, HeaderValue, StatusCode}; use axum::response::{IntoResponse, Response}; -use crate::api::AppState; - use super::common::UiError; +use crate::api::AppState; /// Serve a file from `{project_root}/assets/` by slug + relative path. /// Validates that the resolved path stays under the assets directory. @@ -88,10 +87,7 @@ pub(crate) fn asset_content_type(path: &std::path::Path) -> &'static str { } /// Generic file server for an arbitrary base directory. -pub(crate) async fn serve_dir_from( - base: &std::path::Path, - rel: &str, -) -> Result { +pub(crate) async fn serve_dir_from(base: &std::path::Path, rel: &str) -> Result { let Ok(canonical_base) = base.canonicalize() else { return Ok(StatusCode::NOT_FOUND.into_response()); }; diff --git a/crates/ontoref-daemon/src/ui/handlers/backlog.rs b/crates/ontoref-daemon/src/ui/handlers/backlog.rs index 8fe3c02..b5cf8e3 100644 --- a/crates/ontoref-daemon/src/ui/handlers/backlog.rs +++ b/crates/ontoref-daemon/src/ui/handlers/backlog.rs @@ -99,7 +99,8 @@ pub(crate) fn backlog_stats(items: &[serde_json::Value]) -> BacklogStats { pub async fn backlog_page(State(state): State) -> Result, UiError> { let tera = tera_ref(&state)?; - let backlog_path = ontoref_ontology::layout::resolve_section(&state.project_root, "reflection/backlog.ncl"); + let backlog_path = + ontoref_ontology::layout::resolve_section(&state.project_root, "reflection/backlog.ncl"); let items = if backlog_path.exists() { load_backlog_items( &state.cache, @@ -136,7 +137,8 @@ pub async fn backlog_page_mp( ) -> Result, UiError> { let tera = tera_ref(&state)?; let proj = state.registry.get(&slug).ok_or(UiError::NotConfigured)?; - let backlog_path = ontoref_ontology::layout::resolve_section(&proj.root, "reflection/backlog.ncl"); + let backlog_path = + ontoref_ontology::layout::resolve_section(&proj.root, "reflection/backlog.ncl"); let items = if backlog_path.exists() { load_backlog_items(&proj.cache, &backlog_path, proj.import_path.as_deref()).await } else { @@ -170,13 +172,14 @@ pub async fn backlog_update_status( State(state): State, Form(form): Form, ) -> Result { - let backlog_path = ontoref_ontology::layout::resolve_section(&state.project_root, "reflection/backlog.ncl"); + let backlog_path = + ontoref_ontology::layout::resolve_section(&state.project_root, "reflection/backlog.ncl"); let _guard = state.ncl_write_lock.acquire(&backlog_path).await; super::super::backlog_ncl::update_status(&backlog_path, &form.id, &form.status, &today_iso()) .map_err(|e| UiError::NclExport { - path: "backlog.ncl".into(), - reason: e.to_string(), - })?; + path: "backlog.ncl".into(), + reason: e.to_string(), + })?; state .cache .invalidate_file(&backlog_path.canonicalize().unwrap_or(backlog_path)); @@ -196,13 +199,14 @@ pub async fn backlog_update_status_mp( )); } let proj = state.registry.get(&slug).ok_or(UiError::NotConfigured)?; - let backlog_path = ontoref_ontology::layout::resolve_section(&proj.root, "reflection/backlog.ncl"); + let backlog_path = + ontoref_ontology::layout::resolve_section(&proj.root, "reflection/backlog.ncl"); let _guard = state.ncl_write_lock.acquire(&backlog_path).await; super::super::backlog_ncl::update_status(&backlog_path, &form.id, &form.status, &today_iso()) .map_err(|e| UiError::NclExport { - path: "backlog.ncl".into(), - reason: e.to_string(), - })?; + path: "backlog.ncl".into(), + reason: e.to_string(), + })?; proj.cache .invalidate_file(&backlog_path.canonicalize().unwrap_or(backlog_path.clone())); @@ -224,7 +228,8 @@ pub async fn backlog_add( State(state): State, Form(form): Form, ) -> Result { - let backlog_path = ontoref_ontology::layout::resolve_section(&state.project_root, "reflection/backlog.ncl"); + let backlog_path = + ontoref_ontology::layout::resolve_section(&state.project_root, "reflection/backlog.ncl"); let _guard = state.ncl_write_lock.acquire(&backlog_path).await; super::super::backlog_ncl::add_item( &backlog_path, @@ -257,7 +262,8 @@ pub async fn backlog_add_mp( )); } let proj = state.registry.get(&slug).ok_or(UiError::NotConfigured)?; - let backlog_path = ontoref_ontology::layout::resolve_section(&proj.root, "reflection/backlog.ncl"); + let backlog_path = + ontoref_ontology::layout::resolve_section(&proj.root, "reflection/backlog.ncl"); let _guard = state.ncl_write_lock.acquire(&backlog_path).await; super::super::backlog_ncl::add_item( &backlog_path, @@ -301,7 +307,8 @@ pub async fn backlog_edit_mp( )); } let proj = state.registry.get(&slug).ok_or(UiError::NotConfigured)?; - let backlog_path = ontoref_ontology::layout::resolve_section(&proj.root, "reflection/backlog.ncl"); + let backlog_path = + ontoref_ontology::layout::resolve_section(&proj.root, "reflection/backlog.ncl"); let _guard = state.ncl_write_lock.acquire(&backlog_path).await; super::super::backlog_ncl::update_item( &backlog_path, @@ -352,7 +359,8 @@ pub async fn backlog_delete_mp( )); } let proj = state.registry.get(&slug).ok_or(UiError::NotConfigured)?; - let backlog_path = ontoref_ontology::layout::resolve_section(&proj.root, "reflection/backlog.ncl"); + let backlog_path = + ontoref_ontology::layout::resolve_section(&proj.root, "reflection/backlog.ncl"); let _guard = state.ncl_write_lock.acquire(&backlog_path).await; super::super::backlog_ncl::delete_item(&backlog_path, &form.id).map_err(|e| { UiError::NclExport { @@ -386,7 +394,8 @@ pub async fn backlog_propose_status_mp( ) -> Result { let proj = state.registry.get(&slug).ok_or(UiError::NotConfigured)?; - let backlog_path = ontoref_ontology::layout::resolve_section(&proj.root, "reflection/backlog.ncl"); + let backlog_path = + ontoref_ontology::layout::resolve_section(&proj.root, "reflection/backlog.ncl"); let items = load_backlog_items(&proj.cache, &backlog_path, proj.import_path.as_deref()).await; let item_title = items .iter() diff --git a/crates/ontoref-daemon/src/ui/handlers/common.rs b/crates/ontoref-daemon/src/ui/handlers/common.rs index 9c24130..49e0c0b 100644 --- a/crates/ontoref-daemon/src/ui/handlers/common.rs +++ b/crates/ontoref-daemon/src/ui/handlers/common.rs @@ -1,8 +1,8 @@ use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; -use axum::response::{Html, IntoResponse, Response}; use axum::http::StatusCode; +use axum::response::{Html, IntoResponse, Response}; use tera::Context; use tokio::sync::RwLock; @@ -479,7 +479,8 @@ pub(crate) async fn resolve_domain_ctx( .unwrap_or("") .to_string(); for proj_ctx in registry.all() { - let mpath = ontoref_ontology::layout::resolve_section(&proj_ctx.root, "ontology/manifest.ncl"); + let mpath = + ontoref_ontology::layout::resolve_section(&proj_ctx.root, "ontology/manifest.ncl"); if !mpath.exists() { continue; } diff --git a/crates/ontoref-daemon/src/ui/handlers/dashboard.rs b/crates/ontoref-daemon/src/ui/handlers/dashboard.rs index c66b5d1..c941ceb 100644 --- a/crates/ontoref-daemon/src/ui/handlers/dashboard.rs +++ b/crates/ontoref-daemon/src/ui/handlers/dashboard.rs @@ -11,10 +11,12 @@ use super::common::{ ncl_export, now_hms, readme_description, render, tera_ref, UiError, }; use super::graph_helpers::{ - api_catalog_graph_nodes, manifest_graph_nodes, read_project_api_catalogs, - state_graph_nodes, workspace_crate_nodes, + api_catalog_graph_nodes, manifest_graph_nodes, read_project_api_catalogs, state_graph_nodes, + workspace_crate_nodes, +}; +use super::picker::{ + count_ncl_files, detect_generated, detect_showcase, git_remotes, load_pending_migrations, }; -use super::picker::{count_ncl_files, detect_generated, detect_showcase, load_pending_migrations, git_remotes}; use crate::api::AppState; #[derive(serde::Serialize)] @@ -148,7 +150,8 @@ pub async fn dashboard_mp( }; let base_url = format!("/ui/{slug}"); - let backlog_path = ontoref_ontology::layout::resolve_section(&ctx_ref.root, "reflection/backlog.ncl"); + let backlog_path = + ontoref_ontology::layout::resolve_section(&ctx_ref.root, "reflection/backlog.ncl"); let backlog_items = if backlog_path.exists() { load_backlog_items( &ctx_ref.cache, @@ -160,8 +163,14 @@ pub async fn dashboard_mp( vec![] }; let backlog = backlog_stats(&backlog_items); - let adr_count = count_ncl_files(&ontoref_ontology::layout::resolve_section(&ctx_ref.root, "adrs")); - let mode_count = count_ncl_files(&ontoref_ontology::layout::resolve_section(&ctx_ref.root, "reflection/modes")); + let adr_count = count_ncl_files(&ontoref_ontology::layout::resolve_section( + &ctx_ref.root, + "adrs", + )); + let mode_count = count_ncl_files(&ontoref_ontology::layout::resolve_section( + &ctx_ref.root, + "reflection/modes", + )); // ── Project identity (same data as project_picker card) ────────────────── let config_json = load_config_json( @@ -184,7 +193,8 @@ pub async fn dashboard_mp( } else { String::new() }; - let manifest_path = ontoref_ontology::layout::resolve_section(&ctx_ref.root, "ontology/manifest.ncl"); + let manifest_path = + ontoref_ontology::layout::resolve_section(&ctx_ref.root, "ontology/manifest.ncl"); let (layers, op_modes, default_mode, repo_kind) = if manifest_path.exists() { match ctx_ref .cache @@ -388,7 +398,8 @@ pub async fn graph_mp( let (ws_nodes, ws_edges) = workspace_crate_nodes(&ctx_ref.root); // Merge manifest config sections + requirements. - let manifest_path = ontoref_ontology::layout::resolve_section(&ctx_ref.root, "ontology/manifest.ncl"); + let manifest_path = + ontoref_ontology::layout::resolve_section(&ctx_ref.root, "ontology/manifest.ncl"); let (manifest_nodes, manifest_edges) = if manifest_path.exists() { match ctx_ref .cache diff --git a/crates/ontoref-daemon/src/ui/handlers/manage.rs b/crates/ontoref-daemon/src/ui/handlers/manage.rs index 5cb9cdf..7f1c0e0 100644 --- a/crates/ontoref-daemon/src/ui/handlers/manage.rs +++ b/crates/ontoref-daemon/src/ui/handlers/manage.rs @@ -168,8 +168,9 @@ async fn load_registry_entry( .export(&project_ncl, state.nickel_import_path.as_deref()) .await { - Ok((json, _)) => serde_json::from_value(json) - .unwrap_or_else(|_| make_registry_entry_from_root(root)), + Ok((json, _)) => { + serde_json::from_value(json).unwrap_or_else(|_| make_registry_entry_from_root(root)) + } Err(_) => make_registry_entry_from_root(root), } } else { @@ -177,7 +178,8 @@ async fn load_registry_entry( } } -/// Guarded variant: requires `AdminGuard` (any admin session or no auth configured). +/// Guarded variant: requires `AdminGuard` (any admin session or no auth +/// configured). pub async fn manage_page_guarded( State(state): State, _guard: super::super::auth::AdminGuard, diff --git a/crates/ontoref-daemon/src/ui/handlers/mod.rs b/crates/ontoref-daemon/src/ui/handlers/mod.rs index add1117..1680df6 100644 --- a/crates/ontoref-daemon/src/ui/handlers/mod.rs +++ b/crates/ontoref-daemon/src/ui/handlers/mod.rs @@ -12,58 +12,42 @@ pub mod session; // ── Re-exports — public surface identical to the old handlers.rs ───────────── -pub(crate) use common::{ - insert_brand_ctx, insert_mcp_ctx, readme_description, render, UiError, +// Assets + public file serving +pub use assets::{serve_asset_mp, serve_asset_single, serve_public_mp, serve_public_single}; +// Backlog +pub use backlog::{ + backlog_add, backlog_add_mp, backlog_delete_mp, backlog_edit_mp, backlog_page, backlog_page_mp, + backlog_propose_status_mp, backlog_update_status, backlog_update_status_mp, BacklogAddForm, + BacklogDeleteForm, BacklogEditForm, BacklogProposeForm, BacklogStatusForm, }; - +pub(crate) use common::{insert_brand_ctx, insert_mcp_ctx, readme_description, render, UiError}; +// Compose +pub use compose::{compose_form_schema_mp, compose_page_mp, compose_send_mp, ComposeSendBody}; // Dashboard + graph pub use dashboard::{ api_catalog_page_mp, dashboard, dashboard_mp, dashboard_stats_mp, graph, graph_mp, }; - -// Sessions + notifications -pub use session::{ - emit_notification_mp, notification_action_mp, notifications_mp, notifications_page, - sessions, sessions_mp, EmitNotifForm, NotifActionForm, -}; - -// Project picker -pub(crate) use picker::{pending_migrations_fragment_mp, project_picker}; - // Manage pub use manage::{ - manage_add, manage_add_guarded, manage_logout, manage_page, manage_page_guarded, - manage_remove, manage_remove_guarded, service_toggle_ui, AddProjectForm, RemoveProjectForm, + manage_add, manage_add_guarded, manage_logout, manage_page, manage_page_guarded, manage_remove, + manage_remove_guarded, service_toggle_ui, AddProjectForm, RemoveProjectForm, }; - -// Backlog -pub use backlog::{ - backlog_add, backlog_add_mp, backlog_delete_mp, backlog_edit_mp, backlog_page, - backlog_page_mp, backlog_propose_status_mp, backlog_update_status, backlog_update_status_mp, - BacklogAddForm, BacklogDeleteForm, BacklogEditForm, BacklogProposeForm, BacklogStatusForm, +// Pages — modes, actions, qa, config, adrs, domain, tier, timeline, roadmap, panel +pub use pages::{ + actions_page, actions_page_mp, actions_run, actions_run_mp, adrs_page_mp, career_page_mp, + config_page_mp, modes, modes_mp, panel_page_mp, personal_page_mp, provisioning_page_mp, qa_add, + qa_delete, qa_page, qa_page_mp, qa_update, roadmap_page_mp, tier_page_mp, timeline_page_mp, + views_page, views_page_mp, ActionRunForm, QaAddRequest, QaDeleteRequest, QaUpdateRequest, }; - +// Project picker +pub(crate) use picker::{pending_migrations_fragment_mp, project_picker}; // Search + bookmarks pub use search::{ search_bookmark_add, search_bookmark_delete, search_page, search_page_mp, BookmarkAddRequest, BookmarkDeleteRequest, }; - -// Pages — modes, actions, qa, config, adrs, domain, tier, timeline, roadmap, panel -pub use pages::{ - actions_page, actions_page_mp, actions_run, actions_run_mp, adrs_page_mp, career_page_mp, - config_page_mp, modes, modes_mp, panel_page_mp, personal_page_mp, provisioning_page_mp, - qa_add, qa_delete, qa_page, qa_page_mp, qa_update, roadmap_page_mp, tier_page_mp, - timeline_page_mp, views_page, views_page_mp, ActionRunForm, QaAddRequest, QaDeleteRequest, - QaUpdateRequest, -}; - -// Compose -pub use compose::{ - compose_form_schema_mp, compose_page_mp, compose_send_mp, ComposeSendBody, -}; - -// Assets + public file serving -pub use assets::{ - serve_asset_mp, serve_asset_single, serve_public_mp, serve_public_single, +// Sessions + notifications +pub use session::{ + emit_notification_mp, notification_action_mp, notifications_mp, notifications_page, sessions, + sessions_mp, EmitNotifForm, NotifActionForm, }; diff --git a/crates/ontoref-daemon/src/ui/handlers/pages.rs b/crates/ontoref-daemon/src/ui/handlers/pages.rs index 7ce1a5a..f64177c 100644 --- a/crates/ontoref-daemon/src/ui/handlers/pages.rs +++ b/crates/ontoref-daemon/src/ui/handlers/pages.rs @@ -9,9 +9,7 @@ use tera::Context; use tracing::warn; use super::super::auth::AuthUser; -use super::common::{ - auth_role_str, insert_brand_ctx, now_iso, render, tera_ref, UiError, -}; +use super::common::{auth_role_str, insert_brand_ctx, now_iso, render, tera_ref, UiError}; use super::picker::{detect_generated, detect_showcase}; use crate::api::AppState; use crate::memory_types::ChronicleEntry; @@ -21,7 +19,8 @@ use crate::memory_types::ChronicleEntry; pub async fn modes(State(state): State) -> Result, UiError> { let tera = tera_ref(&state)?; - let modes_dir = ontoref_ontology::layout::resolve_section(&state.project_root, "reflection/modes"); + let modes_dir = + ontoref_ontology::layout::resolve_section(&state.project_root, "reflection/modes"); let mut mode_entries: Vec = Vec::new(); if let Ok(entries) = std::fs::read_dir(&modes_dir) { @@ -362,7 +361,8 @@ fn resolve_qa_ctx( } /// Substrate views panel (ADR-046). Read-only — the page fetches /api/views and -/// /api/views/{discipline}/mount client-side, so the handler only renders the shell. +/// /api/views/{discipline}/mount client-side, so the handler only renders the +/// shell. pub async fn views_page(State(state): State) -> Result, UiError> { let tera = tera_ref(&state)?; let mut ctx = Context::new(); @@ -506,7 +506,15 @@ pub async fn qa_add( let related = body.related.as_deref().unwrap_or(&[]); let now = now_iso(); - match super::super::qa_ncl::add_entry(&qa_path, &body.question, answer, actor, &now, tags, related) { + match super::super::qa_ncl::add_entry( + &qa_path, + &body.question, + answer, + actor, + &now, + tags, + related, + ) { Ok(id) => { cache.invalidate_file(&qa_path); ( @@ -767,7 +775,8 @@ pub async fn adrs_page_mp( render(tera, "pages/adrs.html", &ctx).await } -// ── Domain extension pages ──────────────────────────────────────────────────── +// ── Domain extension pages +// ──────────────────────────────────────────────────── /// Personal ontology page — contents pipeline + opportunities. pub async fn personal_page_mp( @@ -869,9 +878,8 @@ pub async fn career_page_mp( .unwrap_or_else(|_| "[]".to_string()); let talks = serde_json::to_string(v.get("talks").unwrap_or(&empty)) .unwrap_or_else(|_| "[]".to_string()); - let positioning = - serde_json::to_string(v.get("positioning").unwrap_or(&empty)) - .unwrap_or_else(|_| "[]".to_string()); + let positioning = serde_json::to_string(v.get("positioning").unwrap_or(&empty)) + .unwrap_or_else(|_| "[]".to_string()); let publications = serde_json::to_string(v.get("publications").unwrap_or(&empty)) .unwrap_or_else(|_| "[]".to_string()); @@ -934,7 +942,8 @@ pub async fn provisioning_page_mp( let cache = ctx_ref.cache.clone(); let root = ctx_ref.root.clone(); - let connections_path = ontoref_ontology::layout::resolve_section(&root, "ontology/connections.ncl"); + let connections_path = + ontoref_ontology::layout::resolve_section(&root, "ontology/connections.ncl"); let connections_json = if connections_path.exists() { match cache .export(&connections_path, import_path.as_deref()) @@ -987,7 +996,8 @@ pub async fn provisioning_page_mp( render(tera, "pages/provisioning.html", &ctx).await } -// ── Tier page ───────────────────────────────────────────────────────────────── +// ── Tier page +// ───────────────────────────────────────────────────────────────── pub async fn tier_page_mp( State(state): State, @@ -1083,7 +1093,8 @@ pub async fn tier_page_mp( render(tera, "pages/tier.html", &ctx).await } -// ── Chronicle timeline ──────────────────────────────────────────────────────── +// ── Chronicle timeline +// ──────────────────────────────────────────────────────── /// `GET /{slug}/timeline` — render the chronicle timeline panel. /// Reads all `entries.jsonl` files under `{root}/.coder/` and serves them as @@ -1099,8 +1110,7 @@ pub async fn timeline_page_mp( let coder_dir = ctx_ref.root.join(".coder"); let entries = collect_chronicle_entries(&coder_dir); - let timeline_json = - serde_json::to_string(&entries).unwrap_or_else(|_| "[]".to_string()); + let timeline_json = serde_json::to_string(&entries).unwrap_or_else(|_| "[]".to_string()); let mut ctx = Context::new(); ctx.insert("slug", &slug); @@ -1142,31 +1152,60 @@ fn parse_chronicle_line(line: &str, idx: usize) -> Option { return Some(e); } let v = serde_json::from_str::(line).ok()?; - let id = v.get("id") + let id = v + .get("id") .and_then(|x| x.as_str()) .map(String::from) .unwrap_or_else(|| { let date = v.get("date").and_then(|x| x.as_str()).unwrap_or("0000"); format!("{date}-{idx}") }); - let title = v.get("title").and_then(|x| x.as_str()).unwrap_or("(untitled)").to_string(); - let date = v.get("date").and_then(|x| x.as_str()).unwrap_or("1970-01-01").to_string(); - let kind = v.get("kind").and_then(|x| x.as_str()).unwrap_or("info").to_string(); - let refs: Vec = v.get("relates_to") + let title = v + .get("title") + .and_then(|x| x.as_str()) + .unwrap_or("(untitled)") + .to_string(); + let date = v + .get("date") + .and_then(|x| x.as_str()) + .unwrap_or("1970-01-01") + .to_string(); + let kind = v + .get("kind") + .and_then(|x| x.as_str()) + .unwrap_or("info") + .to_string(); + let refs: Vec = v + .get("relates_to") .or_else(|| v.get("refs")) .and_then(|x| x.as_array()) - .map(|a| a.iter().filter_map(|r| r.as_str().map(String::from)).collect()) + .map(|a| { + a.iter() + .filter_map(|r| r.as_str().map(String::from)) + .collect() + }) .unwrap_or_default(); - let summary = v.get("content") + let summary = v + .get("content") .or_else(|| v.get("summary")) .and_then(|x| x.as_str()) .map(|s| s.chars().take(200).collect()); let domain = v.get("domain").and_then(|x| x.as_str()).map(String::from); - Some(ChronicleEntry { id, title, date, kind, summary, domain, refs }) + Some(ChronicleEntry { + id, + title, + date, + kind, + summary, + domain, + refs, + }) } fn collect_entries_recursive(dir: &std::path::Path, out: &mut Vec) { - let Ok(rd) = std::fs::read_dir(dir) else { return }; + let Ok(rd) = std::fs::read_dir(dir) else { + return; + }; for entry in rd.flatten() { let path = entry.path(); if path.is_dir() { @@ -1174,14 +1213,18 @@ fn collect_entries_recursive(dir: &std::path::Path, out: &mut Vec { - let mechs: Vec = pos + let (mechanisms, milestones, orphan_milestones) = if pos_dir.exists() { + match ontoref_ontology::ontology::Positioning::load(&pos_dir) { + Ok(pos) => { + let mechs: Vec = pos .mechanisms() .iter() .map(|m| serde_json::json!({ "id": m.id, "label": m.name, "status": format!("{:?}", m.status) })) .collect(); - // milestones come from the chronicle entries in .coder/ - let coder_dir = ctx_ref.root.join(".coder"); - let all_entries = collect_chronicle_entries(&coder_dir); - let ms: Vec = all_entries - .into_iter() - .filter(|e| e.kind == "milestone") - .map(|e| serde_json::json!({ + // milestones come from the chronicle entries in .coder/ + let coder_dir = ctx_ref.root.join(".coder"); + let all_entries = collect_chronicle_entries(&coder_dir); + let ms: Vec = all_entries + .into_iter() + .filter(|e| e.kind == "milestone") + .map(|e| { + serde_json::json!({ "id": e.id, "title": e.title, "date": e.date, "refs": e.refs, "mechanism": e.refs.first().cloned(), - })) - .collect(); - let diff_adrs: std::collections::HashSet = pos - .differentiators() - .iter() - .flat_map(|d| d.evidence_adrs.clone()) - .collect(); - let orphans: Vec = ms - .iter() - .filter(|m| { - let refs = m.get("refs") - .and_then(|r| r.as_array()) - .map(|a| a.iter().filter_map(|v| v.as_str().map(String::from)).collect::>()) - .unwrap_or_default(); - !refs.iter().any(|r| diff_adrs.contains(r)) }) - .cloned() - .collect(); - (mechs, ms, orphans) - } - Err(e) => { - warn!(slug = %slug, error = %e, "roadmap: positioning load failed"); - (vec![], vec![], vec![]) - } + }) + .collect(); + let diff_adrs: std::collections::HashSet = pos + .differentiators() + .iter() + .flat_map(|d| d.evidence_adrs.clone()) + .collect(); + let orphans: Vec = ms + .iter() + .filter(|m| { + let refs = m + .get("refs") + .and_then(|r| r.as_array()) + .map(|a| { + a.iter() + .filter_map(|v| v.as_str().map(String::from)) + .collect::>() + }) + .unwrap_or_default(); + !refs.iter().any(|r| diff_adrs.contains(r)) + }) + .cloned() + .collect(); + (mechs, ms, orphans) } - } else { - (vec![], vec![], vec![]) - }; + Err(e) => { + warn!(slug = %slug, error = %e, "roadmap: positioning load failed"); + (vec![], vec![], vec![]) + } + } + } else { + (vec![], vec![], vec![]) + }; let roadmap_json = serde_json::to_string(&serde_json::json!({ "mechanisms": mechanisms, @@ -1271,7 +1320,8 @@ pub async fn roadmap_page_mp( render(tera, "pages/roadmap.html", &ctx).await } -// ── Generic panel ───────────────────────────────────────────────────────────── +// ── Generic panel +// ───────────────────────────────────────────────────────────── /// `GET /{slug}/panel/{id}` — render a presentation-spec panel by id. /// Loads the panel NCL from `.ontoref/presentation/panel-{id}.ncl` and @@ -1286,7 +1336,8 @@ pub async fn panel_page_mp( let base_url = format!("/ui/{slug}"); let _ = auth; - let panel_path = ctx_ref.root + let panel_path = ctx_ref + .root .join(".ontoref") .join("presentation") .join(format!("panel-{panel_id}.ncl")); diff --git a/crates/ontoref-daemon/src/ui/handlers/picker.rs b/crates/ontoref-daemon/src/ui/handlers/picker.rs index 84bb773..f8e3165 100644 --- a/crates/ontoref-daemon/src/ui/handlers/picker.rs +++ b/crates/ontoref-daemon/src/ui/handlers/picker.rs @@ -4,8 +4,8 @@ use tera::Context; use super::super::auth::AuthUser; use super::common::{ - epoch_secs, extract_card_from_config, extract_registry_ctx, extract_vault_ctx, - insert_mcp_ctx, load_config_json, readme_description, render, tera_ref, UiError, + epoch_secs, extract_card_from_config, extract_registry_ctx, extract_vault_ctx, insert_mcp_ctx, + load_config_json, readme_description, render, tera_ref, UiError, }; use crate::api::AppState; @@ -20,7 +20,8 @@ pub(crate) fn count_ncl_files(dir: &std::path::Path) -> usize { } /// Read git remote names+URLs from `{root}/.git/config` by parsing the ini file -/// directly. Returns `[{ "name": "origin", "url": "git@github.com:..." }, ...]`. +/// directly. Returns `[{ "name": "origin", "url": "git@github.com:..." }, +/// ...]`. pub(crate) fn git_remotes(root: &std::path::Path) -> Vec { let Ok(content) = std::fs::read_to_string(root.join(".git").join("config")) else { return vec![]; @@ -197,7 +198,8 @@ pub async fn project_picker(State(state): State) -> Result) -> Result = if manifest_path.exists() { proj.cache .export(&manifest_path, proj.import_path.as_deref()) @@ -319,7 +322,8 @@ pub async fn project_picker(State(state): State) -> Result, ) -> Vec { - let bm_path = ontoref_ontology::layout::resolve_section(root, "reflection/search_bookmarks.ncl"); + let bm_path = + ontoref_ontology::layout::resolve_section(root, "reflection/search_bookmarks.ncl"); if !bm_path.exists() { return vec![]; } @@ -122,7 +123,8 @@ pub async fn search_bookmark_add( Json(body): Json, ) -> impl axum::response::IntoResponse { let (root, cache) = resolve_bookmark_ctx(&state, body.slug.as_deref()); - let bm_path = ontoref_ontology::layout::resolve_section(&root, "reflection/search_bookmarks.ncl"); + let bm_path = + ontoref_ontology::layout::resolve_section(&root, "reflection/search_bookmarks.ncl"); let _guard = state.ncl_write_lock.acquire(&bm_path).await; if !bm_path.exists() { return ( @@ -180,7 +182,8 @@ pub async fn search_bookmark_delete( Json(body): Json, ) -> impl axum::response::IntoResponse { let (root, cache) = resolve_bookmark_ctx(&state, body.slug.as_deref()); - let bm_path = ontoref_ontology::layout::resolve_section(&root, "reflection/search_bookmarks.ncl"); + let bm_path = + ontoref_ontology::layout::resolve_section(&root, "reflection/search_bookmarks.ncl"); let _guard = state.ncl_write_lock.acquire(&bm_path).await; if !bm_path.exists() { return ( diff --git a/crates/ontoref-daemon/src/ui/handlers/session.rs b/crates/ontoref-daemon/src/ui/handlers/session.rs index a8ca5bc..96ae09c 100644 --- a/crates/ontoref-daemon/src/ui/handlers/session.rs +++ b/crates/ontoref-daemon/src/ui/handlers/session.rs @@ -4,9 +4,7 @@ use serde::{Deserialize, Serialize}; use tera::Context; use super::super::auth::AuthUser; -use super::common::{ - auth_role_str, epoch_secs, insert_brand_ctx, render, tera_ref, UiError, -}; +use super::common::{auth_role_str, epoch_secs, insert_brand_ctx, render, tera_ref, UiError}; use crate::api::AppState; #[derive(Debug, Serialize)] @@ -320,7 +318,8 @@ pub async fn notification_action_mp( .ok_or_else(|| { UiError::BadRequest("backlog_approve: missing proposed_status".into()) })?; - let backlog_path = ontoref_ontology::layout::resolve_section(&ctx_ref.root, "reflection/backlog.ncl"); + let backlog_path = + ontoref_ontology::layout::resolve_section(&ctx_ref.root, "reflection/backlog.ncl"); let _guard = state.ncl_write_lock.acquire(&backlog_path).await; super::super::backlog_ncl::update_status( &backlog_path, diff --git a/crates/ontoref-daemon/src/ui/watcher.rs b/crates/ontoref-daemon/src/ui/watcher.rs index 6ae8ccd..f8bac63 100644 --- a/crates/ontoref-daemon/src/ui/watcher.rs +++ b/crates/ontoref-daemon/src/ui/watcher.rs @@ -55,10 +55,7 @@ impl TemplateWatcher { } } -async fn reload_loop( - mut rx: mpsc::Receiver, - tera: Arc>, -) { +async fn reload_loop(mut rx: mpsc::Receiver, tera: Arc>) { let debounce = Duration::from_millis(150); loop { diff --git a/crates/ontoref-daemon/src/views.rs b/crates/ontoref-daemon/src/views.rs index 1223477..cb9393c 100644 --- a/crates/ontoref-daemon/src/views.rs +++ b/crates/ontoref-daemon/src/views.rs @@ -2,10 +2,10 @@ //! read path, shared by the HTTP API and the MCP surface so an interface IS a //! mounted view (not bespoke code). Mirrors the deterministic fold of the //! Nushell resolver (`reflection/modules/view.nu`): the declared prior ⊕ the -//! active adjustment chain. The Nushell path remains the source of truth and the -//! offline fallback (ADR-029). View files and the adjustment log are read as JSON -//! `Value` so the schema is not re-declared in Rust — only the fold is, pinned by -//! a unit test against the Nushell semantics. +//! active adjustment chain. The Nushell path remains the source of truth and +//! the offline fallback (ADR-029). View files and the adjustment log are read +//! as JSON `Value` so the schema is not re-declared in Rust — only the fold is, +//! pinned by a unit test against the Nushell semantics. use std::path::{Path, PathBuf}; use std::process::Command; @@ -26,8 +26,8 @@ fn adjustments_path(root: &Path) -> PathBuf { root.join(".coder").join("view-adjustments.jsonl") } -/// Export one view NCL file to a JSON `Value` (relative imports resolve from the -/// file's directory; the import path is supplied for any bare imports). +/// Export one view NCL file to a JSON `Value` (relative imports resolve from +/// the file's directory; the import path is supplied for any bare imports). fn export_ncl(path: &Path, import_path: Option<&str>) -> Option { let mut cmd = Command::new("nickel"); cmd.args(["export", "--format", "json"]).arg(path); @@ -104,7 +104,10 @@ fn compose(view: &Value, adjustments: &[Value]) -> Value { .iter() .map(|l| { let lref = l.get("ref").and_then(Value::as_str).unwrap_or(""); - let elig = l.get("eligibility").and_then(Value::as_str).unwrap_or("Always"); + let elig = l + .get("eligibility") + .and_then(Value::as_str) + .unwrap_or("Always"); let base = l.get("salience").and_then(Value::as_i64).unwrap_or(0); let d = if elig == "Learned" { delta_for(&active, lref) @@ -143,14 +146,16 @@ fn compose(view: &Value, adjustments: &[Value]) -> Value { }) } -/// Why a discipline could not be mounted — mapped to HTTP status / MCP error by callers. +/// Why a discipline could not be mounted — mapped to HTTP status / MCP error by +/// callers. pub enum MountError { NotFound(String), Multiple(Vec), } /// Declared substrate views (id, discipline, context, learns_from) as JSON. -/// Shared by the HTTP `views_list` handler and the MCP `ontoref_view_list` tool. +/// Shared by the HTTP `views_list` handler and the MCP `ontoref_view_list` +/// tool. pub fn list_views_json(root: &Path, import_path: Option<&str>) -> Value { let rows: Vec = load_views(root, import_path) .iter() @@ -213,10 +218,7 @@ fn resolve_root_for_slug<'a>( std::borrow::Cow::Borrowed(state.project_root.as_path()) } -fn resolve_import_path_for_slug( - state: &AppState, - slug: Option<&str>, -) -> Option { +fn resolve_import_path_for_slug(state: &AppState, slug: Option<&str>) -> Option { if let Some(s) = slug { if let Some(ctx) = state.registry.get(s) { return ctx.import_path.clone(); @@ -225,7 +227,8 @@ fn resolve_import_path_for_slug( state.nickel_import_path.clone() } -/// Declared substrate views. Accepts optional `?slug=` to query a registry project's views. +/// Declared substrate views. Accepts optional `?slug=` to query a registry +/// project's views. #[ontoref_derive::onto_api( method = "GET", path = "/api/views", @@ -233,10 +236,7 @@ fn resolve_import_path_for_slug( actors = "agent, developer, ci, admin", tags = "views, substrate" )] -pub async fn views_list( - State(state): State, - Query(q): Query, -) -> Json { +pub async fn views_list(State(state): State, Query(q): Query) -> Json { let root = resolve_root_for_slug(&state, q.slug.as_deref()); let import_path = resolve_import_path_for_slug(&state, q.slug.as_deref()); Json(list_views_json(&root, import_path.as_deref())) @@ -266,7 +266,9 @@ pub async fn views_mount( )), Err(MountError::Multiple(contexts)) => Err(( StatusCode::CONFLICT, - Json(json!({ "error": "multiple views for discipline; specify context", "contexts": contexts })), + Json( + json!({ "error": "multiple views for discipline; specify context", "contexts": contexts }), + ), )), } } diff --git a/crates/ontoref-daemon/src/watcher.rs b/crates/ontoref-daemon/src/watcher.rs index a8dece9..be47f8b 100644 --- a/crates/ontoref-daemon/src/watcher.rs +++ b/crates/ontoref-daemon/src/watcher.rs @@ -516,10 +516,7 @@ fn discover_pending_migrations(project_root: &Path) -> Vec { /// Run `nickel export --format json` against `config_path` with the given /// import path. Returns the parsed JSON or `None` on any failure (with a /// `warn!` log emitted by the caller's diagnostic context). -fn export_config_json( - config_path: &Path, - import_path: Option<&str>, -) -> Option { +fn export_config_json(config_path: &Path, import_path: Option<&str>) -> Option { let mut cmd = std::process::Command::new("nickel"); cmd.args(["export", "--format", "json"]).arg(config_path); if let Some(ip) = import_path { diff --git a/crates/ontoref-daemon/tests/dispatch_surfaces.rs b/crates/ontoref-daemon/tests/dispatch_surfaces.rs index 3a2013b..b0ac5d0 100644 --- a/crates/ontoref-daemon/tests/dispatch_surfaces.rs +++ b/crates/ontoref-daemon/tests/dispatch_surfaces.rs @@ -6,12 +6,12 @@ //! the resulting witnesses match on every cross-surface invariant //! (op_id, witness_shape, state_root, signature, effects). //! -//! - HTTP: in-process axum via `tower::ServiceExt::oneshot` — no -//! live socket required. +//! - HTTP: in-process axum via `tower::ServiceExt::oneshot` — no live socket +//! required. //! - CLI: spawns the compiled daemon binary via //! `env!("CARGO_BIN_EXE_ontoref-daemon")`. -//! - MCP: calls the pure-function projection that the MCP `tools/list` -//! response is built from. +//! - MCP: calls the pure-function projection that the MCP `tools/list` response +//! is built from. use std::process::Command; @@ -107,11 +107,13 @@ async fn http_surface_dispatches_propose_adr() { let body_bytes = axum::body::to_bytes(response.into_body(), 16 * 1024) .await .expect("collect body"); - let response: DispatchResponse = - serde_json::from_slice(&body_bytes).expect("response parses"); + let response: DispatchResponse = serde_json::from_slice(&body_bytes).expect("response parses"); assert_eq!(response.op_id, "propose_adr"); assert_eq!(response.witness_shape, "adr_proposed"); - assert!(tmp.path().join(".ontoref/adrs/adr-502-surface-equivalence.ncl").exists()); + assert!(tmp + .path() + .join(".ontoref/adrs/adr-502-surface-equivalence.ncl") + .exists()); } #[test] @@ -155,7 +157,10 @@ fn cli_surface_dispatches_propose_adr() { let response: DispatchResponse = serde_json::from_str(&stdout).expect("stdout parses as DispatchResponse"); assert_eq!(response.op_id, "propose_adr"); - assert!(tmp.path().join(".ontoref/adrs/adr-503-cli-test.ncl").exists()); + assert!(tmp + .path() + .join(".ontoref/adrs/adr-503-cli-test.ncl") + .exists()); } #[test] @@ -224,7 +229,10 @@ fn function_cli_and_http_produce_equivalent_witnesses() { assert_eq!(fn_response.state_root_hex, cli_response.state_root_hex); assert_eq!(fn_response.signature_hex, cli_response.signature_hex); assert_eq!(fn_response.effects_hash_hex, cli_response.effects_hash_hex); - assert_eq!(fn_response.actor_public_key_hex, cli_response.actor_public_key_hex); + assert_eq!( + fn_response.actor_public_key_hex, + cli_response.actor_public_key_hex + ); } /// Inline copies of the daemon's HTTP handlers — strip the AppState diff --git a/crates/ontoref-daemon/tests/save_reconciler.rs b/crates/ontoref-daemon/tests/save_reconciler.rs index 254dbdc..51e5dd8 100644 --- a/crates/ontoref-daemon/tests/save_reconciler.rs +++ b/crates/ontoref-daemon/tests/save_reconciler.rs @@ -98,10 +98,7 @@ fn apply_diffs_dispatches_via_runtime() { let result = &results[0]; assert_eq!(result.witness.op_id, "move_fsm_state"); result.witness.verify().expect("witness verifies"); - assert_eq!( - ctx.state.as_ref().unwrap().dimensions[0].current_state, - "b" - ); + assert_eq!(ctx.state.as_ref().unwrap().dimensions[0].current_state, "b"); } #[test] @@ -132,17 +129,17 @@ fn apply_diffs_rolls_back_on_uncatalogued_edit() { let err = apply_diffs(&diffs, &mut ctx).expect_err("uncatalogued edit must reject"); match err { ReconcileError::UncatalogedDiff(msg) => { - assert!(msg.contains("new-dim"), "diff summary must reference the new dim: {msg}"); + assert!( + msg.contains("new-dim"), + "diff summary must reference the new dim: {msg}" + ); } other => panic!("expected UncatalogedDiff, got {other:?}"), } // State left untouched — first-pass refusal preserves the // pre-edit substrate. - assert_eq!( - ctx.state.as_ref().unwrap().dimensions[0].current_state, - "a" - ); + assert_eq!(ctx.state.as_ref().unwrap().dimensions[0].current_state, "a"); } #[test] diff --git a/crates/ontoref-derive/src/lib.rs b/crates/ontoref-derive/src/lib.rs index f606a1d..c9d34b7 100644 --- a/crates/ontoref-derive/src/lib.rs +++ b/crates/ontoref-derive/src/lib.rs @@ -1094,7 +1094,8 @@ fn expand_onto_operation( return Err(syn::Error::new_spanned( &kv.value, format!( - "unknown validation_sla '{other}'; expected Synchronous | EventualWithin | Background" + "unknown validation_sla '{other}'; expected Synchronous | \ + EventualWithin | Background" ), )); } @@ -1124,7 +1125,8 @@ fn expand_onto_operation( let validation_sla = validation_sla.ok_or_else(|| { syn::Error::new( Span::call_site(), - "#[onto_operation] requires validation_sla = \"Synchronous\" | \"EventualWithin\" | \"Background\"", + "#[onto_operation] requires validation_sla = \"Synchronous\" | \"EventualWithin\" | \ + \"Background\"", ) })?; let desc = description.or(doc_desc).ok_or_else(|| { @@ -1152,7 +1154,8 @@ fn expand_onto_operation( let fn_name = fn_ident_name.ok_or_else(|| { syn::Error::new( Span::call_site(), - "#[onto_operation] must be applied to a function — the function pointer is used as the op's execute field", + "#[onto_operation] must be applied to a function — the function pointer is used as \ + the op's execute field", ) })?; Ok(emit_onto_operation(attr, &fn_name, input)) @@ -1299,7 +1302,9 @@ fn expand_onto_validator( let doc_desc: Option = parsed_fn .as_ref() .and_then(|fn_item| fn_item.attrs.iter().cloned().find_map(doc_attr_text)); - let fn_name: Option = parsed_fn.as_ref().map(|fn_item| fn_item.sig.ident.to_string()); + let fn_name: Option = parsed_fn + .as_ref() + .map(|fn_item| fn_item.sig.ident.to_string()); let kv_args = syn::parse::Parser::parse2( Punctuated::::parse_terminated, @@ -1330,9 +1335,7 @@ fn expand_onto_validator( other => { return Err(syn::Error::new_spanned( &kv.value, - format!( - "unknown category '{other}'; expected Structural | Contextual" - ), + format!("unknown category '{other}'; expected Structural | Contextual"), )); } }, @@ -1366,7 +1369,8 @@ fn expand_onto_validator( let predicate_ref = predicate_ref.or(fn_name).ok_or_else(|| { syn::Error::new( Span::call_site(), - "#[onto_validator] could not infer predicate_ref — supply it explicitly when applied to a non-fn item", + "#[onto_validator] could not infer predicate_ref — supply it explicitly when applied \ + to a non-fn item", ) })?; @@ -1427,9 +1431,10 @@ fn emit_onto_validator( mod tests { mod onto_operation { mod expand { - use crate::expand_onto_operation; use quote::quote; + use crate::expand_onto_operation; + fn must_expand( args: proc_macro2::TokenStream, input: proc_macro2::TokenStream, @@ -1461,10 +1466,7 @@ mod tests { ); assert!(s.contains("\"test_op\""), "must embed the op id"); assert!(s.contains("\"minimal test\""), "must embed description"); - assert!( - s.contains("\"Synchronous\""), - "must embed validation_sla" - ); + assert!(s.contains("\"Synchronous\""), "must embed validation_sla"); } #[test] @@ -1542,9 +1544,10 @@ mod tests { mod onto_validator { mod expand { - use crate::expand_onto_validator; use quote::quote; + use crate::expand_onto_validator; + fn must_expand( args: proc_macro2::TokenStream, input: proc_macro2::TokenStream, @@ -1622,7 +1625,8 @@ mod tests { description = "y", category = "Probabilistic" }; - let input = quote! { fn h(_: &Slice, _: &ValidationCtx) -> Verdict { Verdict::Accept } }; + let input = + quote! { fn h(_: &Slice, _: &ValidationCtx) -> Verdict { Verdict::Accept } }; let err = expand_onto_validator(args, input).expect_err("bad category rejected"); assert!(err.to_string().contains("unknown category")); } @@ -1635,7 +1639,8 @@ mod tests { category = "Structural", bogus = "z" }; - let input = quote! { fn h(_: &Slice, _: &ValidationCtx) -> Verdict { Verdict::Accept } }; + let input = + quote! { fn h(_: &Slice, _: &ValidationCtx) -> Verdict { Verdict::Accept } }; let err = expand_onto_validator(args, input).expect_err("unknown key rejected"); assert!(err.to_string().contains("unknown onto_validator key")); } @@ -1643,7 +1648,8 @@ mod tests { #[test] fn requires_id_and_category() { let args = quote! { description = "y" }; - let input = quote! { fn h(_: &Slice, _: &ValidationCtx) -> Verdict { Verdict::Accept } }; + let input = + quote! { fn h(_: &Slice, _: &ValidationCtx) -> Verdict { Verdict::Accept } }; let err = expand_onto_validator(args, input).expect_err("missing id"); assert!(err.to_string().contains("requires id")); } diff --git a/crates/ontoref-ontology-content/src/classification.rs b/crates/ontoref-ontology-content/src/classification.rs index 7973af9..aa4ea99 100644 --- a/crates/ontoref-ontology-content/src/classification.rs +++ b/crates/ontoref-ontology-content/src/classification.rs @@ -3,19 +3,18 @@ //! Four types govern how an ontology is shared and verified across //! actors. Each type carries different governance and sync expectations: //! -//! - **Type 1 (Protocol)** — mandatorily decentralized; ontoref-core is -//! the first extracted. Every actor MUST be able to operate without -//! relying on a central authority for the ontology's content. +//! - **Type 1 (Protocol)** — mandatorily decentralized; ontoref-core is the +//! first extracted. Every actor MUST be able to operate without relying on a +//! central authority for the ontology's content. //! - **Type 2a (Shared-Domain Federable)** — shared across projects but -//! federable: a maintainer publishes the canonical version while -//! forks may diverge with their own ids. -//! - **Type 2b (Shared-Domain Mandatorily Decentralized)** — shared -//! across projects and mandatorily decentralized: no canonical -//! maintainer; the address itself is the authority. +//! federable: a maintainer publishes the canonical version while forks may +//! diverge with their own ids. +//! - **Type 2b (Shared-Domain Mandatorily Decentralized)** — shared across +//! projects and mandatorily decentralized: no canonical maintainer; the +//! address itself is the authority. //! - **Type 3 (Project-Specific)** — opt-in per project; each project's //! self-description lives here. -//! - **Type 4 (Private)** — tier-0; never leaves the actor's local -//! environment. +//! - **Type 4 (Private)** — tier-0; never leaves the actor's local environment. use serde::{Deserialize, Serialize}; @@ -61,7 +60,10 @@ impl OntologyType { /// Whether this type forbids any centralised authority. pub fn must_be_decentralized(&self) -> bool { - matches!(self, Self::Type1Protocol | Self::Type2bMandatoryDecentralized) + matches!( + self, + Self::Type1Protocol | Self::Type2bMandatoryDecentralized + ) } /// Whether instances of this type are allowed to leave the actor. diff --git a/crates/ontoref-ontology-content/src/extract.rs b/crates/ontoref-ontology-content/src/extract.rs index f2931d8..7abe4c4 100644 --- a/crates/ontoref-ontology-content/src/extract.rs +++ b/crates/ontoref-ontology-content/src/extract.rs @@ -64,21 +64,12 @@ pub fn extract_core_to_oplog( if let Some(nodes) = core_json.get("nodes").and_then(|v| v.as_array()) { for node in nodes { - let entity_id = node - .get("id") - .and_then(|v| v.as_str()) - .unwrap_or_default(); + let entity_id = node.get("id").and_then(|v| v.as_str()).unwrap_or_default(); if entity_id.is_empty() { continue; } let attrs = build_attrs_from_object(node); - let op = build_signed_op( - signing_key, - &pubkey, - entity_id, - attrs, - Hlc::new(next_ts, 0), - ); + let op = build_signed_op(signing_key, &pubkey, entity_id, attrs, Hlc::new(next_ts, 0)); log.append(&op).map_err(Error::Append)?; next_ts += 1; nodes_extracted += 1; @@ -137,14 +128,16 @@ pub fn write_ontology_refs( } let entry = format!( - " {{\n name = \"{name}\",\n ontology_id = \"{}\",\n oplog_path = \"{}\",\n classification = '{},\n }},\n", + " {{\n name = \"{name}\",\n ontology_id = \"{}\",\n oplog_path = \ + \"{}\",\n classification = '{},\n }},\n", ontology_id.to_hex(), oplog_path.display(), classification.tag(), ); let content = format!( - "# Auto-generated by `ontoref-daemon --extract-ontology`.\n# Re-run extraction to update; do not edit by hand.\n\n{{\n ontology_refs = [\n{entry} ],\n}}\n" + "# Auto-generated by `ontoref-daemon --extract-ontology`.\n# Re-run extraction to update; \ + do not edit by hand.\n\n{{\n ontology_refs = [\n{entry} ],\n}}\n" ); std::fs::write(refs_path, content).map_err(|e| Error::Io { @@ -212,7 +205,9 @@ fn build_signed_op( #[cfg(test)] mod tests { use super::*; - use crate::{fetch_cell_witness, verify_cell, CellQuery, OntologyRegistry, OntologyType, RegistryEntry}; + use crate::{ + fetch_cell_witness, verify_cell, CellQuery, OntologyRegistry, OntologyType, RegistryEntry, + }; fn signing_key() -> SigningKey { SigningKey::from_bytes(&[7u8; 32]) @@ -233,15 +228,18 @@ mod tests { ], }); - let stats = extract_core_to_oplog(&core_json, &oplog_path, &signing_key()) - .expect("extract ok"); + let stats = + extract_core_to_oplog(&core_json, &oplog_path, &signing_key()).expect("extract ok"); assert_eq!(stats.nodes_extracted, 2); assert_eq!(stats.edges_extracted, 1); // Idempotent: second extraction adds nothing. let stats2 = extract_core_to_oplog(&core_json, &oplog_path, &signing_key()) .expect("second extract ok"); - assert_eq!(stats2.ontology_id, stats.ontology_id, "idempotent extraction must produce the same id"); + assert_eq!( + stats2.ontology_id, stats.ontology_id, + "idempotent extraction must produce the same id" + ); } #[test] @@ -256,8 +254,8 @@ mod tests { "edges": [], }); - let stats = extract_core_to_oplog(&core_json, &oplog_path, &signing_key()) - .expect("extract ok"); + let stats = + extract_core_to_oplog(&core_json, &oplog_path, &signing_key()).expect("extract ok"); let mut registry = OntologyRegistry::empty(); registry.register(RegistryEntry { @@ -269,8 +267,7 @@ mod tests { // Project A can fetch the cell witness for protocol-not-runtime/name. let query = CellQuery::new("protocol-not-runtime", "name"); - let response = - fetch_cell_witness(®istry, &stats.ontology_id, &query).expect("fetch ok"); + let response = fetch_cell_witness(®istry, &stats.ontology_id, &query).expect("fetch ok"); assert!(verify_cell(&response.witness, &response.state_root)); } @@ -281,8 +278,14 @@ mod tests { let oplog_path = tmp.path().join("ontoref-core.oplog"); let id = OntologyId([0xAB; 32]); - write_ontology_refs(&refs, "ontoref-core", &id, &oplog_path, OntologyType::Type1Protocol) - .expect("write ok"); + write_ontology_refs( + &refs, + "ontoref-core", + &id, + &oplog_path, + OntologyType::Type1Protocol, + ) + .expect("write ok"); let content = std::fs::read_to_string(&refs).expect("read ok"); assert!(content.contains("ontoref-core")); diff --git a/crates/ontoref-ontology-content/src/fetch.rs b/crates/ontoref-ontology-content/src/fetch.rs index f1f2a79..22e0b05 100644 --- a/crates/ontoref-ontology-content/src/fetch.rs +++ b/crates/ontoref-ontology-content/src/fetch.rs @@ -82,12 +82,9 @@ pub struct OntologyHandle { /// /// - [`Error::UnknownOntology`] when `id` is not registered locally. /// - [`Error::OpenOplog`] when the oplog file cannot be opened. -/// - [`Error::Append`] when the oplog returns an internal error while -/// replaying operations. -pub fn fetch_ontology( - registry: &OntologyRegistry, - id: &OntologyId, -) -> Result { +/// - [`Error::Append`] when the oplog returns an internal error while replaying +/// operations. +pub fn fetch_ontology(registry: &OntologyRegistry, id: &OntologyId) -> Result { let entry = resolve(registry, id)?; let state_root = compute_state_root(&entry.oplog_path)?; Ok(OntologyHandle { @@ -110,10 +107,10 @@ pub fn fetch_ontology( /// /// - [`Error::UnknownOntology`] when `id` is not registered locally. /// - [`Error::OpenOplog`] when the oplog file cannot be opened. -/// - [`Error::CellMissing`] when the cell `(entity, attr)` has never -/// been asserted under the ontology's commit layer. -/// - [`Error::Append`] when the oplog returns an internal error while -/// replaying operations. +/// - [`Error::CellMissing`] when the cell `(entity, attr)` has never been +/// asserted under the ontology's commit layer. +/// - [`Error::Append`] when the oplog returns an internal error while replaying +/// operations. pub fn fetch_cell_witness( registry: &OntologyRegistry, id: &OntologyId, @@ -135,10 +132,7 @@ pub fn fetch_cell_witness( }) } -fn resolve<'r>( - registry: &'r OntologyRegistry, - id: &OntologyId, -) -> Result<&'r RegistryEntry> { +fn resolve<'r>(registry: &'r OntologyRegistry, id: &OntologyId) -> Result<&'r RegistryEntry> { registry .lookup(id) .ok_or_else(|| Error::UnknownOntology(id.to_hex())) @@ -146,9 +140,7 @@ fn resolve<'r>( fn rebuild_commit_layer(oplog_path: &PathBuf) -> Result { let log = open_oplog(oplog_path)?; - let ops: Vec = log - .all_ops_in_hlc_order() - .map_err(Error::Append)?; + let ops: Vec = log.all_ops_in_hlc_order().map_err(Error::Append)?; let mut commit = BinaryMerkle::new(); for op in &ops { commit.apply(op); diff --git a/crates/ontoref-ontology-content/src/lib.rs b/crates/ontoref-ontology-content/src/lib.rs index bdf3afd..5ac9328 100644 --- a/crates/ontoref-ontology-content/src/lib.rs +++ b/crates/ontoref-ontology-content/src/lib.rs @@ -27,7 +27,9 @@ pub mod verify; pub use classification::OntologyType; pub use extract::{extract_core_to_oplog, write_ontology_refs, ExtractStats}; -pub use fetch::{fetch_cell_witness, fetch_ontology, CellQuery, CellWitnessResponse, OntologyHandle}; +pub use fetch::{ + fetch_cell_witness, fetch_ontology, CellQuery, CellWitnessResponse, OntologyHandle, +}; pub use ontology_id::{compute_ontology_id, OntologyId}; pub use publish::publish_op; pub use registry::{OntologyRegistry, RegistryEntry}; diff --git a/crates/ontoref-ontology-content/src/publish.rs b/crates/ontoref-ontology-content/src/publish.rs index 99b0d4f..5e767fa 100644 --- a/crates/ontoref-ontology-content/src/publish.rs +++ b/crates/ontoref-ontology-content/src/publish.rs @@ -19,8 +19,8 @@ use crate::{Error, Result}; /// # Errors /// /// - [`Error::OpenOplog`] when the oplog file cannot be opened. -/// - [`Error::Append`] when the oplog rejects the operation (bad -/// signature, unknown parent, non-monotonic timestamp, IO). +/// - [`Error::Append`] when the oplog rejects the operation (bad signature, +/// unknown parent, non-monotonic timestamp, IO). pub fn publish_op(oplog_path: &Path, op: &Operation) -> Result { let log = OpLog::open(oplog_path).map_err(|e| Error::OpenOplog { path: oplog_path.display().to_string(), diff --git a/crates/ontoref-ontology-content/src/registry.rs b/crates/ontoref-ontology-content/src/registry.rs index d2acead..c7797a6 100644 --- a/crates/ontoref-ontology-content/src/registry.rs +++ b/crates/ontoref-ontology-content/src/registry.rs @@ -50,17 +50,16 @@ impl OntologyRegistry { /// # Errors /// /// - [`Error::Io`] when the file exists but cannot be read. - /// - [`Error::RegistryParse`] when the file exists but does not - /// parse into the registry schema. + /// - [`Error::RegistryParse`] when the file exists but does not parse into + /// the registry schema. pub fn load(path: &Path) -> Result { match std::fs::read(path) { Ok(bytes) => { - let reg = serde_json::from_slice::(&bytes).map_err(|e| { - Error::RegistryParse { + let reg = + serde_json::from_slice::(&bytes).map_err(|e| Error::RegistryParse { path: path.display().to_string(), message: e.to_string(), - } - })?; + })?; Ok(reg) } Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(Self::default()), @@ -102,7 +101,11 @@ impl OntologyRegistry { /// Register a new entry, replacing any existing one with the same /// ontology id. pub fn register(&mut self, entry: RegistryEntry) { - if let Some(existing) = self.entries.iter_mut().find(|e| e.ontology_id == entry.ontology_id) { + if let Some(existing) = self + .entries + .iter_mut() + .find(|e| e.ontology_id == entry.ontology_id) + { *existing = entry; } else { self.entries.push(entry); diff --git a/crates/ontoref-ontology-content/tests/cross_project_witness.rs b/crates/ontoref-ontology-content/tests/cross_project_witness.rs index ee935f5..2f76def 100644 --- a/crates/ontoref-ontology-content/tests/cross_project_witness.rs +++ b/crates/ontoref-ontology-content/tests/cross_project_witness.rs @@ -13,17 +13,16 @@ use ed25519_dalek::SigningKey; use ontoref_commit::BinaryMerkle; +use ontoref_ontology_content::{ + compute_ontology_id, fetch_cell_witness, verify_cell, CellQuery, Error, OntologyRegistry, + OntologyType, RegistryEntry, +}; use ontoref_oplog::OpLog; use ontoref_types::{ operation::{OpBody, OpPayload, Operation}, AttrId, EntityId, Hlc, PublicKey, Value, }; -use ontoref_ontology_content::{ - compute_ontology_id, fetch_cell_witness, verify_cell, CellQuery, Error, OntologyRegistry, - OntologyType, RegistryEntry, -}; - fn signing_key(seed: u8) -> SigningKey { SigningKey::from_bytes(&[seed; 32]) } @@ -45,7 +44,10 @@ fn assert_op(entity: &str, attr: &str, value: Value, ts_logical: u64, seed: u8) /// Append `ops` to a fresh oplog at `path` and return the resulting /// [`OntologyId`] computed from the oplog's heads. -fn build_ontology(path: &std::path::Path, ops: &[Operation]) -> ontoref_ontology_content::OntologyId { +fn build_ontology( + path: &std::path::Path, + ops: &[Operation], +) -> ontoref_ontology_content::OntologyId { let log = OpLog::open(path).expect("open oplog"); for op in ops { log.append(op).expect("append op"); @@ -61,20 +63,8 @@ fn fetch_cell_witness_returns_only_witness_and_root() { // ── Foreign ontology X — carries an "x-entity" cell. ──────────────────── let x_oplog = tmp.path().join("x.oplog"); let x_ops = vec![ - assert_op( - "x-entity", - "x-attr", - Value::Str("x-value".into()), - 1, - 0xAA, - ), - assert_op( - "x-entity-other", - "x-attr-other", - Value::Int(42), - 2, - 0xAA, - ), + assert_op("x-entity", "x-attr", Value::Str("x-value".into()), 1, 0xAA), + assert_op("x-entity-other", "x-attr-other", Value::Int(42), 2, 0xAA), ]; let x_id = build_ontology(&x_oplog, &x_ops); @@ -116,8 +106,8 @@ fn fetch_cell_witness_returns_only_witness_and_root() { ); // The witness contains the canonical encoding of "x-value". - let expected = ontoref_types::canonical::encode(&Value::Str("x-value".into())) - .expect("encode value"); + let expected = + ontoref_types::canonical::encode(&Value::Str("x-value".into())).expect("encode value"); assert_eq!(response.witness.value, expected); // ── Negative: the witness must NOT verify under Y's state root. ──────── @@ -130,7 +120,10 @@ fn fetch_cell_witness_returns_only_witness_and_root() { y_commit.apply(op); } let y_root = y_commit.root(); - assert_ne!(response.state_root, y_root, "X and Y must have different roots"); + assert_ne!( + response.state_root, y_root, + "X and Y must have different roots" + ); assert!( !verify_cell(&response.witness, &y_root), "witness from X must not verify under Y's state root" @@ -144,7 +137,8 @@ fn fetch_cell_witness_returns_only_witness_and_root() { let full_oplog_size_bound = x_ops.len() * 1024; // pessimistic per-op size assert!( response_size < full_oplog_size_bound, - "response must be bounded — full oplog leakage would exceed {full_oplog_size_bound} bytes; got {response_size}" + "response must be bounded — full oplog leakage would exceed {full_oplog_size_bound} \ + bytes; got {response_size}" ); // Fetching a cell that does not exist in X returns CellMissing, @@ -159,10 +153,7 @@ fn fetch_cell_witness_returns_only_witness_and_root() { fn fetch_unknown_ontology_id_returns_error() { let tmp = tempfile::tempdir().expect("tempdir"); let oplog = tmp.path().join("x.oplog"); - let _ = build_ontology( - &oplog, - &[assert_op("e", "a", Value::Int(1), 1, 0xCC)], - ); + let _ = build_ontology(&oplog, &[assert_op("e", "a", Value::Int(1), 1, 0xCC)]); let registry = OntologyRegistry::empty(); let unknown_id = ontoref_ontology_content::OntologyId([0xFFu8; 32]); diff --git a/crates/ontoref-ontology/src/layout.rs b/crates/ontoref-ontology/src/layout.rs index 9e3d942..a55d973 100644 --- a/crates/ontoref-ontology/src/layout.rs +++ b/crates/ontoref-ontology/src/layout.rs @@ -291,10 +291,12 @@ pub fn is_legacy_layout(project_root: &Path) -> bool { #[cfg(test)] mod tests { - use super::*; use std::fs; + use tempfile::TempDir; + use super::*; + #[test] fn resolve_prefers_canonical_when_both_exist() { let dir = TempDir::new().expect("tempdir"); @@ -370,7 +372,10 @@ mod tests { #[test] fn relative_in_section_matches_both_prefixes() { - assert!(relative_in_section(".ontoref/ontology/core.ncl", "ontology")); + assert!(relative_in_section( + ".ontoref/ontology/core.ncl", + "ontology" + )); assert!(relative_in_section(".ontology/core.ncl", "ontology")); assert!(relative_in_section(".ontoref/adrs/adr-001.ncl", "adrs")); assert!(relative_in_section("adrs/adr-001.ncl", "adrs")); diff --git a/crates/ontoref-ontology/src/ontology.rs b/crates/ontoref-ontology/src/ontology.rs index 6eb7b7e..c350dd0 100644 --- a/crates/ontoref-ontology/src/ontology.rs +++ b/crates/ontoref-ontology/src/ontology.rs @@ -395,7 +395,8 @@ impl Gate { } } -// ── Positioning (ADR-035) ───────────────────────────────────────────────────── +// ── Positioning (ADR-035) +// ───────────────────────────────────────────────────── /// Runtime view of a project's positioning layer. /// @@ -611,19 +612,15 @@ impl Positioning { cfg.value_props = load_dir::(&positioning_dir.join("value-props"), "value-prop")?; cfg.campaigns = load_dir::(&positioning_dir.join("campaigns"), "campaign")?; - cfg.differentiators = load_dir::( - &positioning_dir.join("differentiators"), - "differentiator", - )?; + cfg.differentiators = + load_dir::(&positioning_dir.join("differentiators"), "differentiator")?; cfg.competitors = load_dir::(&positioning_dir.join("competitors"), "competitor")?; cfg.mechanisms = load_dir::(&positioning_dir.join("difusion"), "mechanism")?; cfg.proofs = load_dir::(&positioning_dir.join("proofs"), "proof")?; - cfg.viability_paths = load_dir::( - &positioning_dir.join("viability"), - "viability-path", - )?; + cfg.viability_paths = + load_dir::(&positioning_dir.join("viability"), "viability-path")?; Self::from_config(cfg) } @@ -673,9 +670,7 @@ impl Positioning { .map(|&i| &self.viability_paths[i]) } pub fn value_prop_by_id(&self, id: &str) -> Option<&ValueProp> { - self.value_prop_by_id - .get(id) - .map(|&i| &self.value_props[i]) + self.value_prop_by_id.get(id).map(|&i| &self.value_props[i]) } pub fn campaign_by_id(&self, id: &str) -> Option<&Campaign> { self.campaign_by_id.get(id).map(|&i| &self.campaigns[i]) @@ -698,10 +693,7 @@ impl Positioning { /// e.g. `_template.ncl`, `_smoke-test.ncl`). A missing `dir` yields an /// empty Vec — sub-directories are independently optional within the /// positioning layer (a project may have audiences but no campaigns yet). -fn load_dir( - dir: &Path, - section: &'static str, -) -> Result> { +fn load_dir(dir: &Path, section: &'static str) -> Result> { if !dir.exists() { return Ok(Vec::new()); } @@ -722,10 +714,8 @@ fn load_dir( continue; } let raw = nickel_export(&path, section)?; - let item: T = serde_json::from_slice(&raw).map_err(|e| OntologyError::Parse { - section, - source: e, - })?; + let item: T = serde_json::from_slice(&raw) + .map_err(|e| OntologyError::Parse { section, source: e })?; items.push(item); } Ok(items) @@ -1005,11 +995,7 @@ mod tests { let cfg = PositioningConfig { audiences: vec![mk_audience("aud-1")], value_props: vec![mk_value_prop("vp-1", vec!["aud-1"])], - campaigns: vec![mk_campaign( - "camp-1", - vec!["aud-missing"], - vec!["vp-1"], - )], + campaigns: vec![mk_campaign("camp-1", vec!["aud-missing"], vec!["vp-1"])], ..Default::default() }; let err = Positioning::from_config(cfg).expect_err("must reject"); diff --git a/crates/ontoref-ontology/src/render.rs b/crates/ontoref-ontology/src/render.rs index 365b1ba..e74c5de 100644 --- a/crates/ontoref-ontology/src/render.rs +++ b/crates/ontoref-ontology/src/render.rs @@ -1,4 +1,5 @@ -//! Byte-stable renderers from authoritative Rust state to NCL files (ADR-025 / D9). +//! Byte-stable renderers from authoritative Rust state to NCL files (ADR-025 / +//! D9). //! //! Phase 1 covers `state.ncl` fully — the file consumed by the //! `move_fsm_state` operation (O3). Other renderable paths diff --git a/crates/ontoref-ontology/src/render_pipeline.rs b/crates/ontoref-ontology/src/render_pipeline.rs index 8d36088..3b22882 100644 --- a/crates/ontoref-ontology/src/render_pipeline.rs +++ b/crates/ontoref-ontology/src/render_pipeline.rs @@ -45,12 +45,11 @@ pub enum PipelineError { /// /// # Errors /// -/// - [`PipelineError::Render`] — propagated from -/// [`crate::render::render`] for unsupported paths or render failure. -/// - [`PipelineError::TypecheckFailed`] — when `nickel typecheck` -/// reports a contract or syntax error on the staged file. -/// - [`PipelineError::NickelMissing`] — the `nickel` binary cannot be -/// spawned. +/// - [`PipelineError::Render`] — propagated from [`crate::render::render`] for +/// unsupported paths or render failure. +/// - [`PipelineError::TypecheckFailed`] — when `nickel typecheck` reports a +/// contract or syntax error on the staged file. +/// - [`PipelineError::NickelMissing`] — the `nickel` binary cannot be spawned. /// - [`PipelineError::Io`] — directory creation, write, or rename failed. pub fn render_and_write(state: &StateConfig, path: &Path) -> Result<(), PipelineError> { let bytes = render(state, path)?; diff --git a/crates/ontoref-ontology/src/types.rs b/crates/ontoref-ontology/src/types.rs index ccadd37..dcc0cf6 100644 --- a/crates/ontoref-ontology/src/types.rs +++ b/crates/ontoref-ontology/src/types.rs @@ -21,9 +21,10 @@ pub enum AbstractionLevel { Moment, } -/// Edge type between nodes. Reconciled 2026-07-09 (G-2) to the vocabulary actually -/// used in core.ncl, mirroring `code/ontology/schemas/core.ncl::edge_type`; the -/// prior enum listed four unused kinds and omitted four in active use. +/// Edge type between nodes. Reconciled 2026-07-09 (G-2) to the vocabulary +/// actually used in core.ncl, mirroring +/// `code/ontology/schemas/core.ncl::edge_type`; the prior enum listed four +/// unused kinds and omitted four in active use. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum EdgeType { Complements, @@ -275,7 +276,8 @@ pub enum ProofKind { Benchmark, } -/// Audience validation state (ADR-043) — Hypothesis until a Proof references it. +/// Audience validation state (ADR-043) — Hypothesis until a Proof references +/// it. #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] pub enum AudienceStatus { #[default] @@ -517,10 +519,11 @@ pub struct ViabilityConverges { /// ViabilityPath — the sustainability seam (ADR-055), a second costura parallel /// to [`Proof`]. Where a Proof answers "is the claim real?", a ViabilityPath -/// answers "does the claim sustain its maker?". It converges PARA QUIÉN × CÓMO × -/// (QUÉ×QUIÉN); nothing references it. `realized_outcome` is present only once a -/// real measured compensation promotes the path past `Draft` (the one-directional -/// realized⇒outcome gate, enforced by the Nickel `make_viability_path` contract). +/// answers "does the claim sustain its maker?". It converges PARA QUIÉN × CÓMO +/// × (QUÉ×QUIÉN); nothing references it. `realized_outcome` is present only +/// once a real measured compensation promotes the path past `Draft` (the +/// one-directional realized⇒outcome gate, enforced by the Nickel +/// `make_viability_path` contract). #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ViabilityPath { pub id: String, @@ -599,7 +602,11 @@ mod positioning_tests { other => panic!("expected FileExists, got {other:?}"), } match &vp.launch_criteria[1] { - LaunchCheck::Grep { pattern, paths, must_be_empty } => { + LaunchCheck::Grep { + pattern, + paths, + must_be_empty, + } => { assert_eq!(pattern, "p"); assert_eq!(paths, &vec!["a".to_string()]); assert!(!*must_be_empty); diff --git a/crates/ontoref-ontology/tests/render_deterministic.rs b/crates/ontoref-ontology/tests/render_deterministic.rs index 98d9160..b023f51 100644 --- a/crates/ontoref-ontology/tests/render_deterministic.rs +++ b/crates/ontoref-ontology/tests/render_deterministic.rs @@ -12,10 +12,11 @@ use ontoref_ontology::types::StateConfig; /// Project root that holds the `.ontoref/` spine. /// -/// Walks up from this crate until it finds the directory containing `.ontoref/`. -/// Post-ADR-048 (constellation) the spine sits at the constellation parent, -/// outside `code/`; pre-ADR-048 it was inside the workspace. Searching ancestors -/// resolves it under either layout instead of hard-coding a hop count. +/// Walks up from this crate until it finds the directory containing +/// `.ontoref/`. Post-ADR-048 (constellation) the spine sits at the +/// constellation parent, outside `code/`; pre-ADR-048 it was inside the +/// workspace. Searching ancestors resolves it under either layout instead of +/// hard-coding a hop count. fn workspace_root() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) .ancestors() @@ -46,6 +47,12 @@ fn repeated_render_produces_identical_bytes() { let second = render_state_ncl(&state); let third = render_state_ncl(&state); - assert_eq!(first, second, "render is not deterministic across two calls"); - assert_eq!(second, third, "render is not deterministic across three calls"); + assert_eq!( + first, second, + "render is not deterministic across two calls" + ); + assert_eq!( + second, third, + "render is not deterministic across three calls" + ); } diff --git a/crates/ontoref-ontology/tests/render_roundtrip.rs b/crates/ontoref-ontology/tests/render_roundtrip.rs index 94a0bd5..ccc1366 100644 --- a/crates/ontoref-ontology/tests/render_roundtrip.rs +++ b/crates/ontoref-ontology/tests/render_roundtrip.rs @@ -14,10 +14,11 @@ use ontoref_ontology::types::StateConfig; /// Project root that holds the `.ontoref/` spine. /// -/// Walks up from this crate until it finds the directory containing `.ontoref/`. -/// Post-ADR-048 (constellation) the spine sits at the constellation parent, -/// outside `code/`; pre-ADR-048 it was inside the workspace. Searching ancestors -/// resolves it under either layout instead of hard-coding a hop count. +/// Walks up from this crate until it finds the directory containing +/// `.ontoref/`. Post-ADR-048 (constellation) the spine sits at the +/// constellation parent, outside `code/`; pre-ADR-048 it was inside the +/// workspace. Searching ancestors resolves it under either layout instead of +/// hard-coding a hop count. fn workspace_root() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) .ancestors() diff --git a/crates/ontoref-ontology/tests/render_typecheck.rs b/crates/ontoref-ontology/tests/render_typecheck.rs index 1be5d93..000a292 100644 --- a/crates/ontoref-ontology/tests/render_typecheck.rs +++ b/crates/ontoref-ontology/tests/render_typecheck.rs @@ -1,11 +1,11 @@ //! Typecheck pipeline for the `state.ncl` renderer (O1.5 / D29). //! //! Verifies two contracts: -//! 1. Rendered bytes from any well-formed `StateConfig` pass -//! `nickel typecheck`. -//! 2. The transactional pipeline aborts and removes its staging file -//! when typecheck fails — proving the invariant "every NCL file on -//! disk parses against its schema". +//! 1. Rendered bytes from any well-formed `StateConfig` pass `nickel +//! typecheck`. +//! 2. The transactional pipeline aborts and removes its staging file when +//! typecheck fails — proving the invariant "every NCL file on disk parses +//! against its schema". use std::path::{Path, PathBuf}; use std::process::Command; @@ -16,10 +16,11 @@ use ontoref_ontology::types::StateConfig; /// Project root that holds the `.ontoref/` spine. /// -/// Walks up from this crate until it finds the directory containing `.ontoref/`. -/// Post-ADR-048 (constellation) the spine sits at the constellation parent, -/// outside `code/`; pre-ADR-048 it was inside the workspace. Searching ancestors -/// resolves it under either layout instead of hard-coding a hop count. +/// Walks up from this crate until it finds the directory containing +/// `.ontoref/`. Post-ADR-048 (constellation) the spine sits at the +/// constellation parent, outside `code/`; pre-ADR-048 it was inside the +/// workspace. Searching ancestors resolves it under either layout instead of +/// hard-coding a hop count. fn workspace_root() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) .ancestors() @@ -89,5 +90,8 @@ fn pipeline_writes_atomically_when_typecheck_passes() { let staging_present = entries .iter() .any(|n| n.to_string_lossy().ends_with(".staged")); - assert!(!staging_present, "staging file must be cleaned up on success"); + assert!( + !staging_present, + "staging file must be cleaned up on success" + ); } diff --git a/crates/ontoref-oplog/src/error.rs b/crates/ontoref-oplog/src/error.rs index bd6443e..07799c3 100644 --- a/crates/ontoref-oplog/src/error.rs +++ b/crates/ontoref-oplog/src/error.rs @@ -17,7 +17,8 @@ pub enum Error { /// An operation references a parent that is not in the log. #[error("unknown parent {0:?}")] UnknownParent(OpId), - /// An operation declares a timestamp not strictly greater than every parent's. + /// An operation declares a timestamp not strictly greater than every + /// parent's. #[error("non-monotonic timestamp on operation {0:?}")] NonMonotonicTimestamp(OpId), } diff --git a/crates/ontoref-oplog/src/oplog.rs b/crates/ontoref-oplog/src/oplog.rs index 5aafe07..8b3287e 100644 --- a/crates/ontoref-oplog/src/oplog.rs +++ b/crates/ontoref-oplog/src/oplog.rs @@ -17,7 +17,8 @@ pub struct OpLog { impl OpLog { /// Open or create the log at `path`. The redb file is created on first use; - /// tables are initialised inside a transaction so subsequent reads find them. + /// tables are initialised inside a transaction so subsequent reads find + /// them. pub fn open(path: impl AsRef) -> Result { let db = Database::create(path).map_err(Error::from)?; // Touch both tables in a write transaction so they exist for reads. diff --git a/crates/ontoref-oplog/tests/basic.rs b/crates/ontoref-oplog/tests/basic.rs index 01b9261..3240d4f 100644 --- a/crates/ontoref-oplog/tests/basic.rs +++ b/crates/ontoref-oplog/tests/basic.rs @@ -1,13 +1,12 @@ use std::collections::HashSet; use ed25519_dalek::SigningKey; +use ontoref_oplog::{Error, OpLog}; use ontoref_types::{ operation::{OpBody, OpPayload, Operation}, Hlc, OpId, PublicKey, }; -use ontoref_oplog::{Error, OpLog}; - fn signing_key() -> SigningKey { SigningKey::from_bytes(&[42u8; 32]) } @@ -47,7 +46,11 @@ fn replay_yields_same_heads_for_linear_chain() { // Drop the OpLog (closes the redb handle), reopen the file, re-read heads. let reopened_heads = OpLog::open(&path).unwrap().heads().unwrap(); assert_eq!(original_heads, reopened_heads); - assert_eq!(original_heads.len(), 1, "a linear chain has exactly one head"); + assert_eq!( + original_heads.len(), + 1, + "a linear chain has exactly one head" + ); } #[test] diff --git a/crates/ontoref-ops/src/capabilities.rs b/crates/ontoref-ops/src/capabilities.rs index ccf5a4d..a18fc06 100644 --- a/crates/ontoref-ops/src/capabilities.rs +++ b/crates/ontoref-ops/src/capabilities.rs @@ -28,7 +28,8 @@ static CAPABILITIES: &[TierCapability] = &[ id: "describe", label: "Describe / self-knowledge", min_tier: Tier::Tier0, - description: "describe project, capabilities, config, connections, state — full self-knowledge surface", + description: "describe project, capabilities, config, connections, state — full \ + self-knowledge surface", }, TierCapability { id: "adrs", @@ -58,7 +59,8 @@ static CAPABILITIES: &[TierCapability] = &[ id: "substrate", label: "Substrate (commit layer + state root)", min_tier: Tier::Tier1, - description: "Content-addressed ontology commits with state root — enables signed attestations and audit trails (ADR-023)", + description: "Content-addressed ontology commits with state root — enables signed \ + attestations and audit trails (ADR-023)", }, TierCapability { id: "signed-artifacts", @@ -70,25 +72,29 @@ static CAPABILITIES: &[TierCapability] = &[ id: "ops-dispatch", label: "Ops dispatch (typed mutations)", min_tier: Tier::Tier2, - description: "All state mutations route through declared domain operations — the agent action boundary (ADR-024)", + description: "All state mutations route through declared domain operations — the agent \ + action boundary (ADR-024)", }, TierCapability { id: "catalogued-ops", label: "Catalogued operations (ADR-026)", min_tier: Tier::Tier2, - description: "Ops registered in the artifact catalog with validators, preconditions, and SLA declarations", + description: "Ops registered in the artifact catalog with validators, preconditions, and \ + SLA declarations", }, TierCapability { id: "runtime-mutation", label: "Runtime-only state mutation", min_tier: Tier::Tier2, - description: "NCL files become render targets; all writes go through the ops runtime — human and AI paths unified", + description: "NCL files become render targets; all writes go through the ops runtime — \ + human and AI paths unified", }, TierCapability { id: "witness-verification", label: "Witness verification at boundary", min_tier: Tier::Tier2, - description: "Every op produces a verifiable witness; validators reject ill-typed state without a valid witness chain", + description: "Every op produces a verifiable witness; validators reject ill-typed state \ + without a valid witness chain", }, ]; @@ -121,9 +127,8 @@ impl From<&'static TierCapability> for CapabilityRef { /// Returns the capability split for the given tier. pub fn capability_view(current: Tier) -> CapabilityView { - let (available, locked): (Vec<_>, Vec<_>) = CAPABILITIES - .iter() - .partition(|c| c.min_tier <= current); + let (available, locked): (Vec<_>, Vec<_>) = + CAPABILITIES.iter().partition(|c| c.min_tier <= current); CapabilityView { available: available.into_iter().map(CapabilityRef::from).collect(), locked: locked.into_iter().map(CapabilityRef::from).collect(), @@ -160,9 +165,9 @@ pub fn tier_projection(current: Tier) -> TierProjection { .filter(|c| c.min_tier == Tier::Tier1) .map(CapabilityRef::from) .collect(), - "Tier-1 adopts the substrate layer (ADR-023): content-addressed ontology commits, \ - a state root, and signed artifacts. Every mutation becomes auditable and \ - reproducible. Prerequisite for ops-dispatch at Tier-2.", + "Tier-1 adopts the substrate layer (ADR-023): content-addressed ontology commits, a \ + state root, and signed artifacts. Every mutation becomes auditable and reproducible. \ + Prerequisite for ops-dispatch at Tier-2.", ), Some(Tier::Tier2) => ( CAPABILITIES @@ -171,14 +176,22 @@ pub fn tier_projection(current: Tier) -> TierProjection { .map(CapabilityRef::from) .collect(), "Tier-2 adopts the operations layer (ADR-024): all state mutations route through \ - declared domain operations. NCL files become render targets. The runtime is the \ - only mutation path; witnesses are cryptographically verifiable; validators reject \ + declared domain operations. NCL files become render targets. The runtime is the only \ + mutation path; witnesses are cryptographically verifiable; validators reject \ ill-typed state.", ), - None => (vec![], "Already at the highest tier. No further evolution is defined."), + None => ( + vec![], + "Already at the highest tier. No further evolution is defined.", + ), Some(_) => unreachable!(), }; - TierProjection { from: current, to, adds, narrative } + TierProjection { + from: current, + to, + adds, + narrative, + } } #[cfg(test)] diff --git a/crates/ontoref-ops/src/config_reload.rs b/crates/ontoref-ops/src/config_reload.rs index 799f03b..77740b3 100644 --- a/crates/ontoref-ops/src/config_reload.rs +++ b/crates/ontoref-ops/src/config_reload.rs @@ -13,10 +13,10 @@ //! consults the guard, and the function returns one of two outcomes: //! //! - `ReloadAction::Apply(new)` — caller swaps registry/state in. -//! - `ReloadAction::Block { .. }` — caller logs `Block`-severity -//! notification and retains the previous config; the file on disk is -//! NOT mutated (per ADR-033: the daemon only refuses to honour the -//! edit, never overwrites the user's NCL). +//! - `ReloadAction::Block { .. }` — caller logs `Block`-severity notification +//! and retains the previous config; the file on disk is NOT mutated (per +//! ADR-033: the daemon only refuses to honour the edit, never overwrites the +//! user's NCL). use crate::tier::OpsConfig; use crate::tier_guard::{enforce_tier_transition_guard, TierGuardError}; diff --git a/crates/ontoref-ops/src/lib.rs b/crates/ontoref-ops/src/lib.rs index c4b616c..77b5992 100644 --- a/crates/ontoref-ops/src/lib.rs +++ b/crates/ontoref-ops/src/lib.rs @@ -19,6 +19,7 @@ extern crate self as ontoref_ops; pub mod capabilities; +pub mod config_reload; pub mod context; pub mod dispatch; pub mod error; @@ -31,7 +32,6 @@ pub mod runtime; pub mod tier; pub mod tier_guard; pub mod tier_transition; -pub mod config_reload; pub mod validation; pub mod witness; @@ -39,6 +39,6 @@ pub use context::OpContext; pub use dispatch::{dispatch_op, registered_op_ids}; pub use error::OpError; pub use output::{EffectRecord, OpOutput}; -pub use registry::{OperationEntry, OpFn}; +pub use registry::{OpFn, OperationEntry}; pub use runtime::{execute_op, PipelineMetrics, PipelineResult}; pub use witness::{sign_witness, PipelineWitness}; diff --git a/crates/ontoref-ops/src/ops/close_dev_session.rs b/crates/ontoref-ops/src/ops/close_dev_session.rs index 79303be..3a49e43 100644 --- a/crates/ontoref-ops/src/ops/close_dev_session.rs +++ b/crates/ontoref-ops/src/ops/close_dev_session.rs @@ -26,10 +26,7 @@ use crate::output::{EffectRecord, OpOutput}; witness_shape = "dev_session_close", actor_policy = "developer,admin" )] -fn close_dev_session( - inputs: &serde_json::Value, - ctx: &mut OpContext, -) -> Result { +fn close_dev_session(inputs: &serde_json::Value, ctx: &mut OpContext) -> Result { let session_id = inputs .get("session_id") .and_then(|v| v.as_str()) @@ -65,8 +62,8 @@ fn close_dev_session( ); ctx.commit_layer.apply(&op); - let witness_payload = canonical::encode(&op) - .map_err(|e| OpError::body("close_dev_session", e.to_string()))?; + let witness_payload = + canonical::encode(&op).map_err(|e| OpError::body("close_dev_session", e.to_string()))?; Ok(OpOutput { effects: vec![EffectRecord { kind: "Update".to_owned(), diff --git a/crates/ontoref-ops/src/ops/evaluate_ondaod.rs b/crates/ontoref-ops/src/ops/evaluate_ondaod.rs index 45cca74..845cb23 100644 --- a/crates/ontoref-ops/src/ops/evaluate_ondaod.rs +++ b/crates/ontoref-ops/src/ops/evaluate_ondaod.rs @@ -15,10 +15,12 @@ use crate::context::OpContext; use crate::error::OpError; use crate::output::{EffectRecord, OpOutput}; -/// Inputs: `{ topic: String, engaged_tensions: [String], synthesis_state: String, motion_direction: String }`. +/// Inputs: `{ topic: String, engaged_tensions: [String], synthesis_state: +/// String, motion_direction: String }`. #[onto_operation( id = "evaluate_ondaod", - description = "Record an ondaod (Dao Discipline) evaluation as a meta-decision entity. No state mutation, no render.", + description = "Record an ondaod (Dao Discipline) evaluation as a meta-decision entity. No \ + state mutation, no render.", validation_sla = "Synchronous", precondition = "ondaod", pre_validators = "", @@ -27,10 +29,7 @@ use crate::output::{EffectRecord, OpOutput}; witness_shape = "ondaod_evaluation", actor_policy = "developer,admin,agent" )] -fn evaluate_ondaod( - inputs: &serde_json::Value, - ctx: &mut OpContext, -) -> Result { +fn evaluate_ondaod(inputs: &serde_json::Value, ctx: &mut OpContext) -> Result { let topic = inputs .get("topic") .and_then(|v| v.as_str()) diff --git a/crates/ontoref-ops/src/ops/manage_backlog_item.rs b/crates/ontoref-ops/src/ops/manage_backlog_item.rs index a51c974..858dd98 100644 --- a/crates/ontoref-ops/src/ops/manage_backlog_item.rs +++ b/crates/ontoref-ops/src/ops/manage_backlog_item.rs @@ -31,7 +31,8 @@ pub const WITNESS_SHAPE: &str = "backlog_item"; /// - `status` (String, optional): `"Open" | "InProgress" | "Closed"`. #[onto_operation( id = "manage_backlog_item", - description = "Add or update one backlog entry in reflection/backlog.ncl. Add asserts a fresh entry; update mutates an existing one in place.", + description = "Add or update one backlog entry in reflection/backlog.ncl. Add asserts a fresh \ + entry; update mutates an existing one in place.", validation_sla = "Synchronous", precondition = "backlog_open_items", pre_validators = "backlog_item_well_formed", @@ -62,10 +63,7 @@ fn manage_backlog_item( .get_mut("items") .and_then(|v| v.as_array_mut()) .ok_or_else(|| { - OpError::body( - "manage_backlog_item", - "ctx.backlog.items is not an array", - ) + OpError::body("manage_backlog_item", "ctx.backlog.items is not an array") })?; let id = inputs @@ -152,7 +150,8 @@ fn manage_backlog_item( ctx.commit_layer.apply(&op); if let Some(root) = ctx.project_root.as_ref() { - let backlog_path = ontoref_ontology::layout::resolve_section(root, "reflection/backlog.ncl"); + let backlog_path = + ontoref_ontology::layout::resolve_section(root, "reflection/backlog.ncl"); if let Some(parent) = backlog_path.parent() { std::fs::create_dir_all(parent).map_err(|e| OpError::Render { path: parent.display().to_string(), @@ -170,8 +169,8 @@ fn manage_backlog_item( })?; } - let witness_payload = canonical::encode(&op) - .map_err(|e| OpError::body("manage_backlog_item", e.to_string()))?; + let witness_payload = + canonical::encode(&op).map_err(|e| OpError::body("manage_backlog_item", e.to_string()))?; Ok(OpOutput { effects: vec![EffectRecord { @@ -195,7 +194,8 @@ fn render_backlog_stub(backlog: &serde_json::Value) -> String { /// Reject malformed backlog requests at the validator boundary. #[onto_validator( id = "backlog_item_well_formed", - description = "Reject manage_backlog_item when action is missing/invalid or required fields are absent.", + description = "Reject manage_backlog_item when action is missing/invalid or required fields \ + are absent.", category = "Contextual", slice_query = "backlog_open_items" )] diff --git a/crates/ontoref-ops/src/ops/move_fsm_state.rs b/crates/ontoref-ops/src/ops/move_fsm_state.rs index 00fcfa7..a7c5cdd 100644 --- a/crates/ontoref-ops/src/ops/move_fsm_state.rs +++ b/crates/ontoref-ops/src/ops/move_fsm_state.rs @@ -36,7 +36,8 @@ pub const WITNESS_SHAPE: &str = "state_transition"; /// can produce the corresponding `state.ncl` bytes. #[onto_operation( id = "move_fsm_state", - description = "Transition an FSM dimension's current_state. Pre-validates the transition exists and post-validates FSM invariants.", + description = "Transition an FSM dimension's current_state. Pre-validates the transition \ + exists and post-validates FSM invariants.", validation_sla = "Synchronous", precondition = "fsm_dimension_by_id", pre_validators = "fsm_transition_allowed", @@ -46,10 +47,7 @@ pub const WITNESS_SHAPE: &str = "state_transition"; witness_shape = "state_transition", actor_policy = "developer,admin,ci" )] -fn move_fsm_state( - inputs: &serde_json::Value, - ctx: &mut OpContext, -) -> Result { +fn move_fsm_state(inputs: &serde_json::Value, ctx: &mut OpContext) -> Result { let dim_id = inputs .get("dimension") .and_then(|v| v.as_str()) @@ -126,7 +124,8 @@ use crate::validation::verdict::Verdict; /// inputs: }` (see `runtime::load_fsm_dimension_slice`). #[onto_validator( id = "fsm_transition_allowed", - description = "Reject move_fsm_state when no transition with the requested `to` state exists in the dimension.", + description = "Reject move_fsm_state when no transition with the requested `to` state exists \ + in the dimension.", category = "Contextual", slice_query = "fsm_dimension_by_id" )] @@ -135,10 +134,7 @@ fn fsm_transition_allowed(slice: &Slice, _ctx: &ValidationCtx) -> Verdict { return Verdict::Unknown; }; let Some(new_state) = inputs.get("new_state").and_then(|v| v.as_str()) else { - return Verdict::reject( - "missing 'new_state' input", - "fsm_transition_allowed", - ); + return Verdict::reject("missing 'new_state' input", "fsm_transition_allowed"); }; let Some(dimension) = slice.get("dimension") else { return Verdict::Unknown; @@ -171,7 +167,8 @@ fn fsm_transition_allowed(slice: &Slice, _ctx: &ValidationCtx) -> Verdict { /// checks once those land.) #[onto_validator( id = "fsm_invariants_preserved", - description = "Reject if the dimension's current_state does not appear in any transition after move_fsm_state has run.", + description = "Reject if the dimension's current_state does not appear in any transition \ + after move_fsm_state has run.", category = "Contextual", slice_query = "fsm_dimension_by_id" )] diff --git a/crates/ontoref-ops/src/ops/propose_adr.rs b/crates/ontoref-ops/src/ops/propose_adr.rs index dfc020e..399ebbf 100644 --- a/crates/ontoref-ops/src/ops/propose_adr.rs +++ b/crates/ontoref-ops/src/ops/propose_adr.rs @@ -28,10 +28,12 @@ pub const WITNESS_SHAPE: &str = "adr_proposed"; /// - `slug` (String): kebab-case slug for the file name. /// - `title` (String). /// - `decision` (String): the decision text. -/// - `ondaod_evaluation` (Object): `{ engaged_tensions: [...], synthesis_state, motion_direction }`. +/// - `ondaod_evaluation` (Object): `{ engaged_tensions: [...], synthesis_state, +/// motion_direction }`. #[onto_operation( id = "propose_adr", - description = "Declare a new ADR in Proposed status. Requires ondaod evaluation as precondition (ADR-024 / dao-discipline).", + description = "Declare a new ADR in Proposed status. Requires ondaod evaluation as \ + precondition (ADR-024 / dao-discipline).", validation_sla = "Synchronous", precondition = "ondaod", pre_validators = "ondaod_engagement_declared", @@ -146,7 +148,9 @@ fn render_adr_stub(id: &str, title: &str, decision: &str) -> String { let title_escaped = title.replace('\\', "\\\\").replace('"', "\\\""); let decision_escaped = decision.replace('\\', "\\\\").replace('"', "\\\""); format!( - "# Generated by `propose_adr` op (O4). Edit via `transition_adr` once accepted.\n\n{{\n id = \"{id}\",\n title = \"{title_escaped}\",\n status = 'Proposed,\n decision = \"{decision_escaped}\",\n}}\n" + "# Generated by `propose_adr` op (O4). Edit via `transition_adr` once accepted.\n\n{{\n \ + id = \"{id}\",\n title = \"{title_escaped}\",\n status = 'Proposed,\n decision = \ + \"{decision_escaped}\",\n}}\n" ) } @@ -155,7 +159,8 @@ fn render_adr_stub(id: &str, title: &str, decision: &str) -> String { /// other. Slice shape: see `runtime::load_ondaod_slice`. #[onto_validator( id = "ondaod_engagement_declared", - description = "Reject propose_adr when the ondaod evaluation declares neither engaged tensions nor an empty-set rationale.", + description = "Reject propose_adr when the ondaod evaluation declares neither engaged \ + tensions nor an empty-set rationale.", category = "Contextual", slice_query = "ondaod" )] diff --git a/crates/ontoref-ops/src/ops/start_dev_session.rs b/crates/ontoref-ops/src/ops/start_dev_session.rs index e7d853d..e6ffbdd 100644 --- a/crates/ontoref-ops/src/ops/start_dev_session.rs +++ b/crates/ontoref-ops/src/ops/start_dev_session.rs @@ -16,7 +16,8 @@ use crate::output::{EffectRecord, OpOutput}; #[onto_operation( id = "start_dev_session", - description = "Open a code dev session with declared scope and target paths. Records session id, scope, and applicable ADRs.", + description = "Open a code dev session with declared scope and target paths. Records session \ + id, scope, and applicable ADRs.", validation_sla = "Synchronous", precondition = "", pre_validators = "", @@ -25,10 +26,7 @@ use crate::output::{EffectRecord, OpOutput}; witness_shape = "dev_session_start", actor_policy = "developer,admin" )] -fn start_dev_session( - inputs: &serde_json::Value, - ctx: &mut OpContext, -) -> Result { +fn start_dev_session(inputs: &serde_json::Value, ctx: &mut OpContext) -> Result { let scope = inputs .get("scope") .and_then(|v| v.as_str()) @@ -66,8 +64,8 @@ fn start_dev_session( ); ctx.commit_layer.apply(&op); - let witness_payload = canonical::encode(&op) - .map_err(|e| OpError::body("start_dev_session", e.to_string()))?; + let witness_payload = + canonical::encode(&op).map_err(|e| OpError::body("start_dev_session", e.to_string()))?; Ok(OpOutput { effects: vec![EffectRecord { kind: "Insert".to_owned(), diff --git a/crates/ontoref-ops/src/ops/sync_apply.rs b/crates/ontoref-ops/src/ops/sync_apply.rs index 72ddb06..b020926 100644 --- a/crates/ontoref-ops/src/ops/sync_apply.rs +++ b/crates/ontoref-ops/src/ops/sync_apply.rs @@ -17,7 +17,8 @@ use crate::output::{EffectRecord, OpOutput}; #[onto_operation( id = "sync_apply", - description = "Apply a batch of reconciled diffs as a single sync event. Phase 1 records the batch attempt; recursive dispatch arrives later.", + description = "Apply a batch of reconciled diffs as a single sync event. Phase 1 records the \ + batch attempt; recursive dispatch arrives later.", validation_sla = "Synchronous", precondition = "", pre_validators = "", @@ -38,10 +39,7 @@ fn sync_apply(inputs: &serde_json::Value, ctx: &mut OpContext) -> Result Result Result OpsConfig { #[onto_operation( id = "transition_tier", - description = "Mutate the project's `ops.tier` declaration. Enforces ADR-029 / ADR-033 guards: refuses when migrations are pending, atomically rewrites .ontoref/config.ncl, emits a tier-transition witness covering the change.", + description = "Mutate the project's `ops.tier` declaration. Enforces ADR-029 / ADR-033 \ + guards: refuses when migrations are pending, atomically rewrites \ + .ontoref/config.ncl, emits a tier-transition witness covering the change.", validation_sla = "Synchronous", precondition = "", pre_validators = "", @@ -76,10 +78,7 @@ fn current_ops_from_disk(project_root: &Path) -> OpsConfig { witness_shape = "tier_transition", actor_policy = "developer,admin" )] -fn transition_tier( - inputs: &serde_json::Value, - ctx: &mut OpContext, -) -> Result { +fn transition_tier(inputs: &serde_json::Value, ctx: &mut OpContext) -> Result { let target_str = inputs .get("target_tier") .and_then(|v| v.as_str()) @@ -106,9 +105,8 @@ fn transition_tier( descent_signing_key: Some(ctx.signing_key.clone()), }; - let outcome = execute(target, transition_ctx).map_err(|e| { - OpError::body("transition_tier", format!("tier transition failed: {e}")) - })?; + let outcome = execute(target, transition_ctx) + .map_err(|e| OpError::body("transition_tier", format!("tier transition failed: {e}")))?; let entity_id = "project_ops:state".to_owned(); let signing_key = ctx.signing_key.clone(); @@ -142,8 +140,8 @@ fn transition_tier( ); ctx.commit_layer.apply(&op); - let witness_payload = canonical::encode(&op) - .map_err(|e| OpError::body("transition_tier", e.to_string()))?; + let witness_payload = + canonical::encode(&op).map_err(|e| OpError::body("transition_tier", e.to_string()))?; Ok(OpOutput { effects: vec![EffectRecord { diff --git a/crates/ontoref-ops/src/ops/update_ontology_edge.rs b/crates/ontoref-ops/src/ops/update_ontology_edge.rs index cf0e6e1..910539a 100644 --- a/crates/ontoref-ops/src/ops/update_ontology_edge.rs +++ b/crates/ontoref-ops/src/ops/update_ontology_edge.rs @@ -18,7 +18,8 @@ use crate::validation::verdict::Verdict; #[onto_operation( id = "update_ontology_edge", - description = "Insert one edge into core.ncl edges array. Pre-validator rejects direct self-loops and 1-step cycles.", + description = "Insert one edge into core.ncl edges array. Pre-validator rejects direct \ + self-loops and 1-step cycles.", validation_sla = "Synchronous", precondition = "ontology_edge_dag_check", pre_validators = "ontology_edge_no_cycle", @@ -105,10 +106,7 @@ fn ontology_edge_no_cycle(slice: &Slice, _ctx: &ValidationCtx) -> Verdict { ); } if from == to { - return Verdict::reject( - "self-loop rejected (from == to)", - "ontology_edge_no_cycle", - ); + return Verdict::reject("self-loop rejected (from == to)", "ontology_edge_no_cycle"); } let reverse_exists = slice .get("existing_reverse") diff --git a/crates/ontoref-ops/src/ops/update_ontology_node.rs b/crates/ontoref-ops/src/ops/update_ontology_node.rs index af5885c..1351a6b 100644 --- a/crates/ontoref-ops/src/ops/update_ontology_node.rs +++ b/crates/ontoref-ops/src/ops/update_ontology_node.rs @@ -32,7 +32,8 @@ pub const WITNESS_SHAPE: &str = "node_upsert"; /// - `invariant` (Bool, optional). #[onto_operation( id = "update_ontology_node", - description = "Insert or update one node in .ontology/core.ncl. Mutates ctx.core in place and writes the file back via Phase 1 inline render.", + description = "Insert or update one node in .ontology/core.ncl. Mutates ctx.core in place and \ + writes the file back via Phase 1 inline render.", validation_sla = "Synchronous", precondition = "ontology_node_by_id", pre_validators = "ontology_node_well_formed", @@ -150,10 +151,14 @@ fn update_ontology_node( })?; } - let witness_payload = canonical::encode(&op) - .map_err(|e| OpError::body("update_ontology_node", e.to_string()))?; + let witness_payload = + canonical::encode(&op).map_err(|e| OpError::body("update_ontology_node", e.to_string()))?; - let effect_kind = if existing_idx.is_some() { "Update" } else { "Insert" }; + let effect_kind = if existing_idx.is_some() { + "Update" + } else { + "Insert" + }; Ok(OpOutput { effects: vec![EffectRecord { kind: effect_kind.to_owned(), @@ -180,7 +185,8 @@ fn render_core_stub(core: &serde_json::Value) -> String { /// Reject when the proposed node is missing required identity fields. #[onto_validator( id = "ontology_node_well_formed", - description = "Reject update_ontology_node when the proposed node lacks id, or when both name and description are missing on an insert.", + description = "Reject update_ontology_node when the proposed node lacks id, or when both name \ + and description are missing on an insert.", category = "Contextual", slice_query = "ontology_node_by_id" )] @@ -190,10 +196,7 @@ fn ontology_node_well_formed(slice: &Slice, _ctx: &ValidationCtx) -> Verdict { }; let node_id = inputs.get("node_id").and_then(|v| v.as_str()).unwrap_or(""); if node_id.is_empty() { - return Verdict::reject( - "missing 'node_id' input", - "ontology_node_well_formed", - ); + return Verdict::reject("missing 'node_id' input", "ontology_node_well_formed"); } let is_insert = slice .get("existing_node") diff --git a/crates/ontoref-ops/src/precondition/ondaod.rs b/crates/ontoref-ops/src/precondition/ondaod.rs index 546a390..5bef7ed 100644 --- a/crates/ontoref-ops/src/precondition/ondaod.rs +++ b/crates/ontoref-ops/src/precondition/ondaod.rs @@ -14,11 +14,12 @@ use serde::{Deserialize, Serialize}; -// NOTE: this loader intentionally does NOT use `ontoref_ontology::Core::from_value` -// because that constructor parses the `edges` array, and the existing -// `Edge.weight: f64` Rust type does not match the real `.ontology/core.ncl` -// edges (which carry `weight = 'High'` enum tags). Fixing the Edge weight type -// is tracked separately; O0 reads only the `nodes` array which is unaffected. +// NOTE: this loader intentionally does NOT use +// `ontoref_ontology::Core::from_value` because that constructor parses the +// `edges` array, and the existing `Edge.weight: f64` Rust type does not match +// the real `.ontology/core.ncl` edges (which carry `weight = 'High'` enum +// tags). Fixing the Edge weight type is tracked separately; O0 reads only the +// `nodes` array which is unaffected. /// Canonical Q&A entry id for the protocol baseline ondaod procedure. pub const ONDAOD_QA_ID: &str = "ontoref-dao-discipline"; @@ -157,8 +158,8 @@ pub fn load( }) .collect(); - let qa: QaStore = serde_json::from_value(qa_json.clone()) - .map_err(|e| OndaodError::Qa(anyhow::anyhow!(e)))?; + let qa: QaStore = + serde_json::from_value(qa_json.clone()).map_err(|e| OndaodError::Qa(anyhow::anyhow!(e)))?; let entry = qa .entries @@ -182,10 +183,10 @@ pub fn load( /// /// # Errors /// -/// - [`OndaodError::UnknownTension`] when any `tension_id` in `engaged` is -/// not present in `context.tensions`. The evaluation rejects entirely; -/// partial application is not supported because the discipline requires -/// the engagement set to be coherent with the loaded tensions snapshot. +/// - [`OndaodError::UnknownTension`] when any `tension_id` in `engaged` is not +/// present in `context.tensions`. The evaluation rejects entirely; partial +/// application is not supported because the discipline requires the +/// engagement set to be coherent with the loaded tensions snapshot. /// /// # Examples /// @@ -263,10 +264,11 @@ struct NodeRow { #[cfg(test)] mod tests { - use super::*; use std::path::{Path, PathBuf}; use std::process::Command; + use super::*; + fn workspace_root() -> PathBuf { // CARGO_MANIFEST_DIR = crates/ontoref-ops; parent twice = workspace root. PathBuf::from(env!("CARGO_MANIFEST_DIR")) @@ -296,8 +298,14 @@ mod tests { #[test] fn load_discipline_and_evaluate() { let root = workspace_root(); - let core_json = nickel_export(&ontoref_ontology::layout::resolve_section(&root, "ontology/core.ncl")); - let qa_json = nickel_export(&ontoref_ontology::layout::resolve_section(&root, "reflection/qa.ncl")); + let core_json = nickel_export(&ontoref_ontology::layout::resolve_section( + &root, + "ontology/core.ncl", + )); + let qa_json = nickel_export(&ontoref_ontology::layout::resolve_section( + &root, + "reflection/qa.ncl", + )); let context = load(&core_json, &qa_json).expect("load ondaod context"); @@ -339,8 +347,14 @@ mod tests { #[test] fn empty_engagement_is_explicit() { let root = workspace_root(); - let core_json = nickel_export(&ontoref_ontology::layout::resolve_section(&root, "ontology/core.ncl")); - let qa_json = nickel_export(&ontoref_ontology::layout::resolve_section(&root, "reflection/qa.ncl")); + let core_json = nickel_export(&ontoref_ontology::layout::resolve_section( + &root, + "ontology/core.ncl", + )); + let qa_json = nickel_export(&ontoref_ontology::layout::resolve_section( + &root, + "reflection/qa.ncl", + )); let context = load(&core_json, &qa_json).expect("load ondaod context"); let evaluation = evaluate(context, vec![], MotionDirection::Static) @@ -353,8 +367,14 @@ mod tests { #[test] fn unknown_tension_rejected() { let root = workspace_root(); - let core_json = nickel_export(&ontoref_ontology::layout::resolve_section(&root, "ontology/core.ncl")); - let qa_json = nickel_export(&ontoref_ontology::layout::resolve_section(&root, "reflection/qa.ncl")); + let core_json = nickel_export(&ontoref_ontology::layout::resolve_section( + &root, + "ontology/core.ncl", + )); + let qa_json = nickel_export(&ontoref_ontology::layout::resolve_section( + &root, + "reflection/qa.ncl", + )); let context = load(&core_json, &qa_json).expect("load ondaod context"); let err = evaluate( diff --git a/crates/ontoref-ops/src/reconcile.rs b/crates/ontoref-ops/src/reconcile.rs index 6dfc73b..ac57419 100644 --- a/crates/ontoref-ops/src/reconcile.rs +++ b/crates/ontoref-ops/src/reconcile.rs @@ -62,7 +62,10 @@ pub fn diff_state(old: &StateConfig, new: &StateConfig) -> Vec { diffs.push(StateDiff { op_id: String::new(), inputs: serde_json::json!({ "dimension_added": new_dim.id }), - summary: format!("dimension '{}' added — no catalogued op covers this", new_dim.id), + summary: format!( + "dimension '{}' added — no catalogued op covers this", + new_dim.id + ), }); continue; }; @@ -88,7 +91,8 @@ pub fn diff_state(old: &StateConfig, new: &StateConfig) -> Vec { "new_desired": new_dim.desired_state, }), summary: format!( - "dimension '{}' desired_state: '{}' → '{}' — no catalogued op covers this in Phase 1", + "dimension '{}' desired_state: '{}' → '{}' — no catalogued op covers this in \ + Phase 1", new_dim.id, old_dim.desired_state, new_dim.desired_state ), }); @@ -117,10 +121,10 @@ pub fn diff_state(old: &StateConfig, new: &StateConfig) -> Vec { /// /// # Errors /// -/// - [`ReconcileError::UncatalogedDiff`] when any diff carries an empty -/// `op_id` (no registered op covers the edit). -/// - [`ReconcileError::Dispatch`] for runtime errors propagated from -/// the underlying [`dispatch_op`]. +/// - [`ReconcileError::UncatalogedDiff`] when any diff carries an empty `op_id` +/// (no registered op covers the edit). +/// - [`ReconcileError::Dispatch`] for runtime errors propagated from the +/// underlying [`dispatch_op`]. pub fn apply_diffs( diffs: &[StateDiff], ctx: &mut OpContext, @@ -156,11 +160,12 @@ pub fn apply_diffs( #[cfg(test)] mod tests { - use super::*; use ontoref_ontology::types::{ Dimension, DimensionState, Horizon, StateConfig, TensionLevel, Transition, }; + use super::*; + fn dim(id: &str, current: &str) -> Dimension { Dimension { id: id.to_owned(), @@ -247,8 +252,12 @@ mod tests { a.desired_state = "z".to_owned(); let mut b = dim("d1", "a"); b.desired_state = "q".to_owned(); - let s1 = StateConfig { dimensions: vec![a] }; - let s2 = StateConfig { dimensions: vec![b] }; + let s1 = StateConfig { + dimensions: vec![a], + }; + let s2 = StateConfig { + dimensions: vec![b], + }; let diffs = diff_state(&s1, &s2); assert_eq!(diffs.len(), 1); assert!(diffs[0].op_id.is_empty()); diff --git a/crates/ontoref-ops/src/runtime.rs b/crates/ontoref-ops/src/runtime.rs index c78e6f3..6ab6d72 100644 --- a/crates/ontoref-ops/src/runtime.rs +++ b/crates/ontoref-ops/src/runtime.rs @@ -1,4 +1,5 @@ -//! Pipeline executor — `load → pre_validate → exec → apply → render → post_validate → emit_witness`. +//! Pipeline executor — `load → pre_validate → exec → apply → render → +//! post_validate → emit_witness`. //! //! Phase 1 wires the skeleton: every step is instrumented with //! microsecond-resolution metrics (ADR-026 SLA observability). Concrete @@ -74,9 +75,9 @@ pub fn execute_op( // reference it without an extra parameter. ctx.op_id = entry.id.to_owned(); - // 1. Precondition load — dispatched by entry.precondition tag. - // Bundles the loaded slice with the op's inputs so validators - // see both without an extra parameter. + // 1. Precondition load — dispatched by entry.precondition tag. Bundles the + // loaded slice with the op's inputs so validators see both without an extra + // parameter. let t0 = Instant::now(); let slice = load_precondition(entry, inputs, ctx)?; metrics.precondition_load_us = elapsed_us(t0); @@ -98,8 +99,8 @@ pub fn execute_op( apply_effects(entry, &output, ctx); metrics.apply_us = elapsed_us(t0); - // 5. Render NCL paths — Phase 1 supports state.ncl when - // ctx.state + ctx.project_root are set. + // 5. Render NCL paths — Phase 1 supports state.ncl when ctx.state + + // ctx.project_root are set. let t0 = Instant::now(); render_paths(entry, ctx)?; metrics.render_us = elapsed_us(t0); @@ -107,7 +108,12 @@ pub fn execute_op( // 6. Post-validators — re-load the slice (state has mutated) and run. let t0 = Instant::now(); let post_slice = load_precondition(entry, inputs, ctx)?; - run_validators(entry.id, entry.post_validators, &post_slice, &validation_ctx)?; + run_validators( + entry.id, + entry.post_validators, + &post_slice, + &validation_ctx, + )?; metrics.post_validate_us = elapsed_us(t0); // 7. Emit witness. @@ -122,12 +128,11 @@ pub fn execute_op( ); metrics.witness_us = elapsed_us(t0); - // 8. Persist the witness to the oplog as the final stage (substrate - // feature). Append-first semantics: the oplog is the source of - // truth, the rendered NCL is a projection. Done here, after the - // in-memory render, the only divergence window is render-ok / - // append-fail, surfaced as OpError::WitnessPersist for the caller - // to reconcile. + // 8. Persist the witness to the oplog as the final stage (substrate feature). + // Append-first semantics: the oplog is the source of truth, the rendered NCL + // is a projection. Done here, after the in-memory render, the only + // divergence window is render-ok / append-fail, surfaced as + // OpError::WitnessPersist for the caller to reconcile. #[cfg(feature = "substrate")] let oplog_op_id = persist_witness(&witness, ctx)?; @@ -256,9 +261,10 @@ fn load_ondaod_slice( // crate::precondition::ondaod::load arrives when the runtime takes // ownership of the qa.ncl + core.ncl JSON snapshots. let mut slice = serde_json::Map::new(); - let ondaod = inputs.get("ondaod_evaluation").cloned().unwrap_or_else(|| { - serde_json::json!({ "engaged_tensions": [], "synthesis_state": "" }) - }); + let ondaod = inputs + .get("ondaod_evaluation") + .cloned() + .unwrap_or_else(|| serde_json::json!({ "engaged_tensions": [], "synthesis_state": "" })); slice.insert("ondaod".to_owned(), ondaod); slice.insert("inputs".to_owned(), inputs.clone()); slice.insert( @@ -318,20 +324,22 @@ fn load_ontology_edge_dag_check_slice( inputs: &serde_json::Value, ctx: &OpContext, ) -> Result { - let from = inputs - .get("from") - .and_then(|v| v.as_str()) - .ok_or_else(|| OpError::PreconditionLoad { - op_id: entry.id.to_owned(), - reason: "missing 'from' input".to_owned(), - })?; - let to = inputs - .get("to") - .and_then(|v| v.as_str()) - .ok_or_else(|| OpError::PreconditionLoad { - op_id: entry.id.to_owned(), - reason: "missing 'to' input".to_owned(), - })?; + let from = + inputs + .get("from") + .and_then(|v| v.as_str()) + .ok_or_else(|| OpError::PreconditionLoad { + op_id: entry.id.to_owned(), + reason: "missing 'from' input".to_owned(), + })?; + let to = + inputs + .get("to") + .and_then(|v| v.as_str()) + .ok_or_else(|| OpError::PreconditionLoad { + op_id: entry.id.to_owned(), + reason: "missing 'to' input".to_owned(), + })?; // The reverse edge (to → from) is what would close a 1-step cycle. let existing_reverse = ctx .core @@ -370,7 +378,8 @@ fn load_fsm_dimension_slice( .as_ref() .ok_or_else(|| OpError::PreconditionLoad { op_id: entry.id.to_owned(), - reason: "ctx.state is None — fsm_dimension_by_id requires a loaded StateConfig".to_owned(), + reason: "ctx.state is None — fsm_dimension_by_id requires a loaded StateConfig" + .to_owned(), })?; let dimension = state .dimensions @@ -483,7 +492,8 @@ fn pipeline() { // paths. Exercises every pipeline step in skeleton form. #[onto_operation( id = "noop_pipeline_smoke", - description = "Noop op used by the O2 runtime smoke test — no precondition, no validators, no effects, no render paths.", + description = "Noop op used by the O2 runtime smoke test — no precondition, no \ + validators, no effects, no render paths.", validation_sla = "Synchronous", witness_shape = "noop" )] @@ -543,7 +553,8 @@ fn pipeline_rejects_disallowed_actor() { /// Restricted op for actor-policy negative coverage. #[onto_operation( id = "restricted_op_smoke", - description = "Restricted to the admin role — used by O2 to verify actor policy enforcement.", + description = "Restricted to the admin role — used by O2 to verify actor policy \ + enforcement.", validation_sla = "Synchronous", actor_policy = "admin", witness_shape = "noop" @@ -579,7 +590,8 @@ fn witness_persists_to_oplog_and_verifies_in_fresh_process() { /// Noop op exercising the full pipeline including witness persistence. #[onto_operation( id = "substrate_persist_smoke", - description = "Noop op used by the substrate witness-persistence test — dispatches, signs, and appends to the oplog.", + description = "Noop op used by the substrate witness-persistence test — dispatches, \ + signs, and appends to the oplog.", validation_sla = "Synchronous", witness_shape = "noop" )] diff --git a/crates/ontoref-ops/src/tier_guard.rs b/crates/ontoref-ops/src/tier_guard.rs index 9f4ed1b..bbbe6d7 100644 --- a/crates/ontoref-ops/src/tier_guard.rs +++ b/crates/ontoref-ops/src/tier_guard.rs @@ -24,7 +24,8 @@ pub enum TierGuardError { /// Callers MUST refuse to apply the new config until the migration /// baseline is clean. #[error( - "tier transition {old_tier:?} -> {new_tier:?} blocked: {pending} migration(s) pending: {ids:?}" + "tier transition {old_tier:?} -> {new_tier:?} blocked: {pending} migration(s) pending: \ + {ids:?}" )] MigrationsPending { /// Tier value declared by the OLD config (or default if absent). @@ -54,8 +55,8 @@ pub enum TierGuardError { /// Note: the function does NOT check `ops.phase` — Phase transitions /// (`Progressive` → `Pure` within tier-2) are governed by a separate /// constraint (future ADR-033). This guard is scoped to tier transitions -/// only, matching the ADR-029 / `tier-transition-requires-clean-migration-state` -/// constraint precisely. +/// only, matching the ADR-029 / +/// `tier-transition-requires-clean-migration-state` constraint precisely. pub fn enforce_tier_transition_guard( old: &OpsConfig, new: &OpsConfig, diff --git a/crates/ontoref-ops/src/tier_transition.rs b/crates/ontoref-ops/src/tier_transition.rs index 1a66058..bd1e3a4 100644 --- a/crates/ontoref-ops/src/tier_transition.rs +++ b/crates/ontoref-ops/src/tier_transition.rs @@ -23,17 +23,17 @@ //! - `transition-tier-uses-existing-guard` — [`execute`] invokes //! [`crate::tier_guard::enforce_tier_transition_guard`] as its first //! validator before any effect runs. -//! - `transition-tier-single-rust-function` — this is the single entry -//! that both the CLI surface and the future catalogued op must call. +//! - `transition-tier-single-rust-function` — this is the single entry that +//! both the CLI surface and the future catalogued op must call. //! - `transition-tier-transactional-or-noop` — file replace happens via //! tempfile + atomic rename. On any pre-rewrite failure the original -//! `.ontoref/config.ncl` is untouched. The atomicity tests in this -//! module pin the property. +//! `.ontoref/config.ncl` is untouched. The atomicity tests in this module pin +//! the property. //! //! Hard constraints DEFERRED to substrate integration: //! -//! - `downgrade-preserves-substrate-immutable` — no substrate to preserve -//! yet in this module. Will be verified when oplog/keys effects land. +//! - `downgrade-preserves-substrate-immutable` — no substrate to preserve yet +//! in this module. Will be verified when oplog/keys effects land. //! - `tier-2-descent-emits-final-witness` — witness emission requires the //! keypair surface and the oplog writer; deferred. @@ -42,10 +42,9 @@ use std::io::Write; use std::path::{Path, PathBuf}; use std::time::{SystemTime, UNIX_EPOCH}; -use tempfile::NamedTempFile; - #[cfg(feature = "substrate")] use ed25519_dalek::SigningKey; +use tempfile::NamedTempFile; use crate::tier::{OpsConfig, Phase, Tier}; use crate::tier_guard::{enforce_tier_transition_guard, TierGuardError}; @@ -155,14 +154,15 @@ fn rewrite_config_tier(config_path: &Path, target: Tier) -> Result<(), Transitio })?; let target_atom = format!("'{target:?}"); - let rewritten = - replace_tier_value(&original, &target_atom).ok_or_else(|| TransitionError::OpsBlockAbsent { + let rewritten = replace_tier_value(&original, &target_atom).ok_or_else(|| { + TransitionError::OpsBlockAbsent { path: config_path.to_path_buf(), - })?; - - let parent = config_path.parent().ok_or_else(|| { - TransitionError::Io(std::io::Error::other("config path has no parent")) + } })?; + + let parent = config_path + .parent() + .ok_or_else(|| TransitionError::Io(std::io::Error::other("config path has no parent")))?; let mut tmp = NamedTempFile::new_in(parent)?; tmp.write_all(rewritten.as_bytes())?; tmp.flush()?; @@ -252,20 +252,19 @@ fn now_unix_ms() -> u128 { /// keys, no secret values). Only the protocol-level fields named by the /// constraint: requested target tier, original tier, failure source, /// timestamp. -fn log_failed_transition( - project_root: &Path, - requested: Tier, - original: Tier, - source: &str, -) { - let log_dir = project_root.join(".ontoref").join("artifacts").join("transition-log"); +fn log_failed_transition(project_root: &Path, requested: Tier, original: Tier, source: &str) { + let log_dir = project_root + .join(".ontoref") + .join("artifacts") + .join("transition-log"); if fs::create_dir_all(&log_dir).is_err() { return; } let ts = now_unix_ms(); let log_path = log_dir.join(format!("{ts}-failed.ncl")); let body = format!( - "{{\n timestamp = {ts},\n requested_tier = \"{requested:?}\",\n original_tier = \"{original:?}\",\n source = \"{source}\",\n}}\n" + "{{\n timestamp = {ts},\n requested_tier = \"{requested:?}\",\n original_tier = \ + \"{original:?}\",\n source = \"{source}\",\n}}\n" ); let _ = fs::write(log_path, body); } @@ -280,7 +279,8 @@ enum Compensation { /// Remove an oplog file this transition created, and its parent /// `substrate/` directory when this transition created that too. Only /// scheduled for substrate we created — pre-existing substrate is - /// immutable on rollback (ADR-033 `downgrade-preserves-substrate-immutable`). + /// immutable on rollback (ADR-033 + /// `downgrade-preserves-substrate-immutable`). RemoveOplog { /// Path of the oplog file to remove. oplog_path: PathBuf, @@ -488,11 +488,9 @@ pub fn execute( tier: target_tier, phase: ctx.current_ops.phase, }; - if let Err(e) = enforce_tier_transition_guard( - &ctx.current_ops, - &target_ops, - &ctx.pending_migrations, - ) { + if let Err(e) = + enforce_tier_transition_guard(&ctx.current_ops, &target_ops, &ctx.pending_migrations) + { log_failed_transition( &ctx.project_root, target_tier, @@ -539,7 +537,9 @@ pub fn execute( comp.run(); } let e = TransitionError::TierDescentWitness { - reason: "Tier2 descent requires the actor signing key (supply it via the ops surface or the keypair store)".to_owned(), + reason: "Tier2 descent requires the actor signing key (supply it via the ops \ + surface or the keypair store)" + .to_owned(), }; log_failed_transition( &ctx.project_root, @@ -764,7 +764,10 @@ mod tests { } fn transition_log_entries(project_root: &Path) -> Vec { - let dir = project_root.join(".ontoref").join("artifacts").join("transition-log"); + let dir = project_root + .join(".ontoref") + .join("artifacts") + .join("transition-log"); if !dir.exists() { return Vec::new(); } @@ -891,10 +894,12 @@ mod tests { let between_quotes = body .split("source = \"") .nth(1) - .and_then(|s| s.split("\"") - .next()) + .and_then(|s| s.split("\"").next()) .unwrap_or(""); - assert!(!between_quotes.contains('"'), "source value must not contain bare double-quotes"); + assert!( + !between_quotes.contains('"'), + "source value must not contain bare double-quotes" + ); } #[cfg(feature = "substrate")] diff --git a/crates/ontoref-ops/src/validation/criteria_validators.rs b/crates/ontoref-ops/src/validation/criteria_validators.rs index e672e43..754c980 100644 --- a/crates/ontoref-ops/src/validation/criteria_validators.rs +++ b/crates/ontoref-ops/src/validation/criteria_validators.rs @@ -15,7 +15,8 @@ use crate::validation::verdict::Verdict; /// /// Slice envelope: `{ "adrs": [{"id": "…", "status": "…"}], /// "proofs": [{"evidence_adr": "…"}] }`. -/// Returns `Unknown` when the slice was not loaded (slice loader not yet wired). +/// Returns `Unknown` when the slice was not loaded (slice loader not yet +/// wired). #[onto_validator( id = "adr_accepted_has_proof", description = "Every Accepted ADR is cited by at least one positioning Proof.", @@ -91,9 +92,10 @@ fn adr_proposal_requires_description(slice: &Slice, _ctx: &ValidationCtx) -> Ver #[cfg(test)] mod tests { - use super::*; use serde_json::json; + use super::*; + fn ctx() -> ValidationCtx { ValidationCtx::synthetic("test", "op:adr-propose") } @@ -218,8 +220,8 @@ mod tests { "adrs": [{"id": "adr-001", "status": "Accepted"}], "proofs": [{"evidence_adr": "adr-001"}] }); - let verdict = - dispatch::dispatch("adr_accepted_has_proof", &slice, &ctx()).expect("must be registered"); + let verdict = dispatch::dispatch("adr_accepted_has_proof", &slice, &ctx()) + .expect("must be registered"); assert_eq!(verdict, Verdict::Accept); } @@ -240,10 +242,11 @@ mod tests { /// in NCL but its Rust fn was never registered via `#[onto_validator]`. #[test] fn ncl_predicate_refs_all_registered() { - use crate::validation::dispatch; use std::path::PathBuf; use std::process::Command; + use crate::validation::dispatch; + // `CARGO_MANIFEST_DIR` = code/crates/ontoref-ops // Three parents up → constellation root (ontoref/) let catalog = PathBuf::from(env!("CARGO_MANIFEST_DIR")) @@ -293,8 +296,8 @@ mod tests { assert!( registered.contains(&predicate_ref), - "NCL predicate_ref '{}' (from {}) is not registered in the Rust inventory.\n\ - Registered ids: {:?}", + "NCL predicate_ref '{}' (from {}) is not registered in the Rust \ + inventory.\nRegistered ids: {:?}", predicate_ref, path.display(), registered diff --git a/crates/ontoref-ops/src/validation/traits.rs b/crates/ontoref-ops/src/validation/traits.rs index dae129e..4781fa5 100644 --- a/crates/ontoref-ops/src/validation/traits.rs +++ b/crates/ontoref-ops/src/validation/traits.rs @@ -78,15 +78,17 @@ where #[cfg(test)] mod tests { + use ontoref_derive::onto_validator; + use super::*; use crate::validation::registry::ValidatorEntry; - use ontoref_derive::onto_validator; /// Dummy validator registered via the macro so the inventory iter /// has at least one entry to discover during the gate test. #[onto_validator( id = "dummy_always_accept", - description = "Reference validator that accepts every input — exists to verify the registration path.", + description = "Reference validator that accepts every input — exists to verify the \ + registration path.", category = "Structural", predicate_ref = "dummy_always_accept" )] diff --git a/crates/ontoref-ops/src/witness.rs b/crates/ontoref-ops/src/witness.rs index fd08b99..da1945a 100644 --- a/crates/ontoref-ops/src/witness.rs +++ b/crates/ontoref-ops/src/witness.rs @@ -40,7 +40,11 @@ impl PipelineWitness { /// reconstruct this from the same inputs to verify the signature. pub fn canonical_payload(&self) -> Vec { let mut buf = Vec::with_capacity( - self.op_id.len() + self.state_root.len() + self.effects_hash.len() + self.extra_payload.len() + 16, + self.op_id.len() + + self.state_root.len() + + self.effects_hash.len() + + self.extra_payload.len() + + 16, ); buf.extend_from_slice(b"ontoref-witness-v1"); buf.extend_from_slice(self.op_id.as_bytes()); @@ -54,10 +58,9 @@ impl PipelineWitness { /// /// # Errors /// - /// - [`SignatureError::InvalidPublicKey`] if the actor's key is - /// malformed. - /// - [`SignatureError::BadSignature`] if the signature does not - /// verify the canonical payload. + /// - [`SignatureError::InvalidPublicKey`] if the actor's key is malformed. + /// - [`SignatureError::BadSignature`] if the signature does not verify the + /// canonical payload. pub fn verify(&self) -> Result<(), SignatureError> { let payload = self.canonical_payload(); self.actor.verify(&payload, &self.signature) diff --git a/crates/ontoref-ops/tests/e2e/e2e/cross_domain.rs b/crates/ontoref-ops/tests/e2e/e2e/cross_domain.rs index eb62f1d..9823f3a 100644 --- a/crates/ontoref-ops/tests/e2e/e2e/cross_domain.rs +++ b/crates/ontoref-ops/tests/e2e/e2e/cross_domain.rs @@ -62,7 +62,9 @@ fn propose_adr_writes_adr_file_and_signs_witness() { assert_eq!(result.witness.witness_shape, "adr_proposed"); result.witness.verify().expect("witness verifies"); - let adr_path = tmp.path().join(".ontoref/adrs/adr-099-cross-domain-pilot.ncl"); + let adr_path = tmp + .path() + .join(".ontoref/adrs/adr-099-cross-domain-pilot.ncl"); assert!(adr_path.exists(), "ADR file must be written"); let body = std::fs::read_to_string(&adr_path).expect("read adr"); assert!(body.contains("status = 'Proposed")); @@ -89,8 +91,7 @@ fn propose_adr_rejects_empty_ondaod_with_no_rationale() { "synthesis_state": "" } }); - let err = dispatch_op("propose_adr", &inputs, &mut ctx) - .expect_err("empty ondaod must reject"); + let err = dispatch_op("propose_adr", &inputs, &mut ctx).expect_err("empty ondaod must reject"); assert!(matches!(err, OpError::ValidatorRejected { .. })); } @@ -106,8 +107,8 @@ fn propose_adr_rejects_malformed_id() { "decision": "x", "ondaod_evaluation": { "engaged_tensions": ["t"], "synthesis_state": "x" } }); - let err = dispatch_op("propose_adr", &inputs, &mut ctx) - .expect_err("malformed adr id must reject"); + let err = + dispatch_op("propose_adr", &inputs, &mut ctx).expect_err("malformed adr id must reject"); assert!(matches!(err, OpError::OpBody { .. })); } @@ -153,8 +154,8 @@ fn update_ontology_node_updates_existing_node() { }); let result = dispatch_op("update_ontology_node", &inputs, &mut ctx).expect("dispatch ok"); assert_eq!(result.effects[0].kind, "Update"); - let body = std::fs::read_to_string(tmp.path().join(".ontoref/ontology/core.ncl")) - .expect("read core"); + let body = + std::fs::read_to_string(tmp.path().join(".ontoref/ontology/core.ncl")).expect("read core"); assert!(body.contains("Updated description for the protocol invariant")); } @@ -189,8 +190,8 @@ fn manage_backlog_item_add_creates_entry() { result.witness.verify().expect("witness verifies"); assert_eq!(result.effects[0].kind, "Insert"); - let body = - std::fs::read_to_string(tmp.path().join(".ontoref/reflection/backlog.ncl")).expect("read backlog"); + let body = std::fs::read_to_string(tmp.path().join(".ontoref/reflection/backlog.ncl")) + .expect("read backlog"); assert!(body.contains("Wire MCP surface to dispatch")); assert!(body.contains("Feature")); } @@ -213,11 +214,10 @@ fn manage_backlog_item_update_mutates_existing() { "id": "bl-9001", "status": "Closed" }); - let result = - dispatch_op("manage_backlog_item", &update_inputs, &mut ctx).expect("update ok"); + let result = dispatch_op("manage_backlog_item", &update_inputs, &mut ctx).expect("update ok"); assert_eq!(result.effects[0].kind, "Update"); - let body = - std::fs::read_to_string(tmp.path().join(".ontoref/reflection/backlog.ncl")).expect("read backlog"); + let body = std::fs::read_to_string(tmp.path().join(".ontoref/reflection/backlog.ncl")) + .expect("read backlog"); assert!(body.contains("Closed")); } diff --git a/crates/ontoref-ops/tests/e2e/e2e/extra_ops.rs b/crates/ontoref-ops/tests/e2e/e2e/extra_ops.rs index de0e38f..8a19904 100644 --- a/crates/ontoref-ops/tests/e2e/e2e/extra_ops.rs +++ b/crates/ontoref-ops/tests/e2e/e2e/extra_ops.rs @@ -48,7 +48,10 @@ fn transition_adr_accept_requires_ondaod() { }); let err = dispatch_op("transition_adr", &inputs, &mut c) .expect_err("accept without ondaod must reject"); - assert!(matches!(err, ontoref_ops::OpError::ValidatorRejected { .. })); + assert!(matches!( + err, + ontoref_ops::OpError::ValidatorRejected { .. } + )); } #[test] @@ -75,9 +78,12 @@ fn transition_adr_unsupported_target_rejects() { "new_status": "Withdrawn", "ondaod_evaluation": { "engaged_tensions": ["x"], "synthesis_state": "x" } }); - let err = dispatch_op("transition_adr", &inputs, &mut c) - .expect_err("unsupported target must reject"); - assert!(matches!(err, ontoref_ops::OpError::ValidatorRejected { .. })); + let err = + dispatch_op("transition_adr", &inputs, &mut c).expect_err("unsupported target must reject"); + assert!(matches!( + err, + ontoref_ops::OpError::ValidatorRejected { .. } + )); } // ── update_ontology_edge ────────────────────────────────────────────────── @@ -105,9 +111,12 @@ fn update_ontology_edge_rejects_self_loop() { "to": "x", "kind": "DependsOn", }); - let err = dispatch_op("update_ontology_edge", &inputs, &mut c) - .expect_err("self-loop must reject"); - assert!(matches!(err, ontoref_ops::OpError::ValidatorRejected { .. })); + let err = + dispatch_op("update_ontology_edge", &inputs, &mut c).expect_err("self-loop must reject"); + assert!(matches!( + err, + ontoref_ops::OpError::ValidatorRejected { .. } + )); } #[test] @@ -124,9 +133,12 @@ fn update_ontology_edge_rejects_1_step_cycle() { "to": "b", "kind": "DependsOn", }); - let err = dispatch_op("update_ontology_edge", &inputs, &mut c) - .expect_err("1-step cycle must reject"); - assert!(matches!(err, ontoref_ops::OpError::ValidatorRejected { .. })); + let err = + dispatch_op("update_ontology_edge", &inputs, &mut c).expect_err("1-step cycle must reject"); + assert!(matches!( + err, + ontoref_ops::OpError::ValidatorRejected { .. } + )); } // ── sync_apply ──────────────────────────────────────────────────────────── diff --git a/crates/ontoref-ops/tests/e2e/e2e/move_fsm_state.rs b/crates/ontoref-ops/tests/e2e/e2e/move_fsm_state.rs index db1df91..03aabda 100644 --- a/crates/ontoref-ops/tests/e2e/e2e/move_fsm_state.rs +++ b/crates/ontoref-ops/tests/e2e/e2e/move_fsm_state.rs @@ -79,7 +79,11 @@ fn build_ctx(project_root: std::path::PathBuf) -> OpContext { fn happy_path_full_pipeline() { let tmp = tempfile::tempdir().expect("tempdir"); let project_root = tmp.path().to_path_buf(); - std::fs::create_dir_all(ontoref_ontology::layout::resolve_section(&project_root, "ontology")).expect("mkdir .ontology"); + std::fs::create_dir_all(ontoref_ontology::layout::resolve_section( + &project_root, + "ontology", + )) + .expect("mkdir .ontology"); let mut ctx = build_ctx(project_root.clone()); let initial_root = ctx.commit_layer.root(); @@ -97,8 +101,14 @@ fn happy_path_full_pipeline() { // ── substrate ─────────────────────────────────────────────────────────── let new_root = ctx.commit_layer.root(); - assert_eq!(new_root, result.witness.state_root, "witness binds to current state_root"); - assert_ne!(new_root, initial_root, "state_root must advance after the op"); + assert_eq!( + new_root, result.witness.state_root, + "witness binds to current state_root" + ); + assert_ne!( + new_root, initial_root, + "state_root must advance after the op" + ); // ── typed state ──────────────────────────────────────────────────────── let dim = ctx.state.as_ref().unwrap().dimensions.first().unwrap(); diff --git a/crates/ontoref-ops/tests/e2e/e2e/witness_seam_full_cycle.rs b/crates/ontoref-ops/tests/e2e/e2e/witness_seam_full_cycle.rs index a77f510..f6b5e16 100644 --- a/crates/ontoref-ops/tests/e2e/e2e/witness_seam_full_cycle.rs +++ b/crates/ontoref-ops/tests/e2e/e2e/witness_seam_full_cycle.rs @@ -7,12 +7,12 @@ //! log from disk and, trusting nothing but the actor's public key: //! //! 1. verifies every persisted operation's signature (the oplog body); -//! 2. reconstructs each `PipelineWitness` from its payload and verifies -//! its own Ed25519 signature; +//! 2. reconstructs each `PipelineWitness` from its payload and verifies its +//! own Ed25519 signature; //! 3. replays the witness chain — applying each witness's embedded //! state-changing op to a fresh commit layer — and confirms the -//! reconstructed `state_root` reproduces the one the witness attests, -//! at every step. +//! reconstructed `state_root` reproduces the one the witness attests, at +//! every step. //! //! This is the executable proof of the `vp-004` provenance claim. @@ -41,9 +41,8 @@ fn full_cycle_dispatch_persist_replay_verify() { // verifier reopens the file — a genuine fresh-process boundary. let attested_roots: Vec<[u8; 32]> = { let oplog = Arc::new(OpLog::open(&oplog_path).expect("open oplog")); - let mut ctx = - OpContext::new_in_memory("developer", "", SigningKey::from_bytes(&SEED)) - .with_oplog(Arc::clone(&oplog)); + let mut ctx = OpContext::new_in_memory("developer", "", SigningKey::from_bytes(&SEED)) + .with_oplog(Arc::clone(&oplog)); let mut roots = Vec::new(); for topic in ["structure-that-remembers", "witness-as-axis-seam"] { diff --git a/crates/ontoref-query/src/engine.rs b/crates/ontoref-query/src/engine.rs index 21d7b05..2ce32e2 100644 --- a/crates/ontoref-query/src/engine.rs +++ b/crates/ontoref-query/src/engine.rs @@ -76,9 +76,7 @@ impl QueryEngine { if !visited.insert(node.clone()) { continue; } - let next = self - .triples - .entity_attr(&node, ref_attr, as_of, valid_at)?; + let next = self.triples.entity_attr(&node, ref_attr, as_of, valid_at)?; if let Some(Value::Ref(target)) = next { frontier.push(target); } diff --git a/crates/ontoref-query/tests/basic.rs b/crates/ontoref-query/tests/basic.rs index 8c9b6c1..ffc0f90 100644 --- a/crates/ontoref-query/tests/basic.rs +++ b/crates/ontoref-query/tests/basic.rs @@ -2,14 +2,13 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; use ed25519_dalek::SigningKey; +use ontoref_query::QueryEngine; use ontoref_triples::TripleStore; use ontoref_types::{ operation::{OpBody, OpPayload, Operation}, AttrId, EntityId, Hlc, PublicKey, Value, }; -use ontoref_query::QueryEngine; - fn signing_key() -> SigningKey { SigningKey::from_bytes(&[13u8; 32]) } @@ -147,10 +146,8 @@ fn transitive_closure_respects_as_of_horizon() { Hlc::new(30, 0), ) .unwrap(); - let expected_late: std::collections::HashSet = ["a", "b", "c"] - .iter() - .map(|n| EntityId::new(*n)) - .collect(); + let expected_late: std::collections::HashSet = + ["a", "b", "c"].iter().map(|n| EntityId::new(*n)).collect(); assert_eq!(late, expected_late); } @@ -268,8 +265,5 @@ fn subscription_delivers_op_payload_to_handler() { .unwrap(); let seen = captured.lock().unwrap(); - assert_eq!( - *seen, - vec![EntityId::new("alpha"), EntityId::new("beta")] - ); + assert_eq!(*seen, vec![EntityId::new("alpha"), EntityId::new("beta")]); } diff --git a/crates/ontoref-sync/src/sync_backend.rs b/crates/ontoref-sync/src/sync_backend.rs index a9b5b22..d4e7692 100644 --- a/crates/ontoref-sync/src/sync_backend.rs +++ b/crates/ontoref-sync/src/sync_backend.rs @@ -87,7 +87,10 @@ impl FilesystemSync { /// # Errors /// /// IO error when the local peer directory cannot be created. - pub fn new(base_dir: impl Into, peer_id: impl Into) -> Result { + pub fn new( + base_dir: impl Into, + peer_id: impl Into, + ) -> Result { let base_dir = base_dir.into(); let peer_id = peer_id.into(); let local = base_dir.join(&peer_id); @@ -120,7 +123,10 @@ impl FilesystemSync { /// /// IO error on write. pub fn publish_body(&self, op_id: &str, body: &[u8]) -> Result<(), SyncError> { - let path = self.base_dir.join(&self.peer_id).join(format!("{op_id}.body")); + let path = self + .base_dir + .join(&self.peer_id) + .join(format!("{op_id}.body")); std::fs::write(&path, body).map_err(|e| SyncError::Io { context: format!("writing body to {}", path.display()), source: e, @@ -254,6 +260,8 @@ fn filesystem_default() { assert!(missing.is_none()); // Subscribing to a non-existent peer fails clearly. - let err = bob.subscribe_peer("nobody").expect_err("subscribe must fail"); + let err = bob + .subscribe_peer("nobody") + .expect_err("subscribe must fail"); assert!(matches!(err, SyncError::Io { .. })); } diff --git a/crates/ontoref-triples/src/error.rs b/crates/ontoref-triples/src/error.rs index 590bb1a..200bc5b 100644 --- a/crates/ontoref-triples/src/error.rs +++ b/crates/ontoref-triples/src/error.rs @@ -12,7 +12,8 @@ pub enum Error { /// An identifier exceeded the 65535-byte key encoding limit. #[error("identifier too long ({0} bytes)")] IdentifierTooLong(usize), - /// A stored EAV row's key bytes did not decode to a valid (attr, hlc) tuple. + /// A stored EAV row's key bytes did not decode to a valid (attr, hlc) + /// tuple. #[error("corrupt key in eav table")] CorruptKey, } diff --git a/crates/ontoref-triples/src/key.rs b/crates/ontoref-triples/src/key.rs index 6abb436..f405c97 100644 --- a/crates/ontoref-triples/src/key.rs +++ b/crates/ontoref-triples/src/key.rs @@ -24,8 +24,7 @@ pub(crate) fn hlc_from_be(bytes: &[u8; 12]) -> Hlc { } pub(crate) fn value_hash(value: &Value) -> [u8; 32] { - let bytes = - canonical::encode(value).expect("canonical encoding never fails for owned types"); + let bytes = canonical::encode(value).expect("canonical encoding never fails for owned types"); *blake3::hash(&bytes).as_bytes() } @@ -57,12 +56,7 @@ pub(crate) fn eav_prefix_entity_attr(entity: &str, attr: &str) -> Result Ok(key) } -pub(crate) fn vae_key( - attr: &str, - value_h: [u8; 32], - entity: &str, - tx: Hlc, -) -> Result> { +pub(crate) fn vae_key(attr: &str, value_h: [u8; 32], entity: &str, tx: Hlc) -> Result> { let mut key = Vec::with_capacity(2 + attr.len() + 32 + 2 + entity.len() + 12); push_len_str(&mut key, attr)?; key.extend_from_slice(&value_h); @@ -78,7 +72,8 @@ pub(crate) fn vae_prefix_attr_value(attr: &str, value_h: [u8; 32]) -> Result Result<(String, Hlc)> { let entity_prefix_len = 2 + entity.len(); if key.len() < entity_prefix_len + 2 + 12 { @@ -98,7 +93,8 @@ pub(crate) fn parse_eav_key_attr_tx(key: &[u8], entity: &str) -> Result<(String, Ok((attr, hlc_from_be(&tx_bytes))) } -/// Parse the (entity, tx_time) suffix of a VAE key whose (attr, value_hash) prefix is fixed. +/// Parse the (entity, tx_time) suffix of a VAE key whose (attr, value_hash) +/// prefix is fixed. pub(crate) fn parse_vae_key_entity_tx(key: &[u8], prefix_len: usize) -> Result<(String, Hlc)> { if key.len() < prefix_len + 2 + 12 { return Err(Error::CorruptKey); @@ -117,7 +113,8 @@ pub(crate) fn parse_vae_key_entity_tx(key: &[u8], prefix_len: usize) -> Result<( Ok((entity, hlc_from_be(&tx_bytes))) } -/// Extract the tx_time from an EAV key whose `(entity, attr)` prefix is `entity_attr_prefix`. +/// Extract the tx_time from an EAV key whose `(entity, attr)` prefix is +/// `entity_attr_prefix`. pub(crate) fn parse_eav_key_tx_only(key: &[u8], entity_attr_prefix_len: usize) -> Result { if key.len() != entity_attr_prefix_len + 12 { return Err(Error::CorruptKey); diff --git a/crates/ontoref-triples/src/store.rs b/crates/ontoref-triples/src/store.rs index 6ec5528..9ae6503 100644 --- a/crates/ontoref-triples/src/store.rs +++ b/crates/ontoref-triples/src/store.rs @@ -174,7 +174,12 @@ impl TripleStore { continue; } - let candidate = (eav_row.valid_from, tx_time, eav_row.value, eav_row.retracted); + let candidate = ( + eav_row.valid_from, + tx_time, + eav_row.value, + eav_row.retracted, + ); let better = match winners.get(&attr) { None => true, Some((current_vf, current_tx, _, _)) => { diff --git a/crates/ontoref-triples/tests/basic.rs b/crates/ontoref-triples/tests/basic.rs index 898b7ee..1a0e395 100644 --- a/crates/ontoref-triples/tests/basic.rs +++ b/crates/ontoref-triples/tests/basic.rs @@ -1,13 +1,12 @@ use std::collections::HashMap; use ed25519_dalek::SigningKey; +use ontoref_triples::TripleStore; use ontoref_types::{ operation::{OpBody, OpPayload, Operation}, AttrId, EntityId, Hlc, PublicKey, Value, }; -use ontoref_triples::TripleStore; - fn signing_key() -> SigningKey { SigningKey::from_bytes(&[9u8; 32]) } @@ -53,9 +52,7 @@ fn forward_index_returns_attrs_for_entity() { )) .unwrap(); - let attrs = store - .entity_attrs(&EntityId::new("alice"), tx, tx) - .unwrap(); + let attrs = store.entity_attrs(&EntityId::new("alice"), tx, tx).unwrap(); let map: HashMap = attrs .into_iter() .map(|(attr, value)| (attr.as_str().to_string(), value)) @@ -205,14 +202,10 @@ fn as_of_filters_to_tx_time_horizon() { .apply(&assert_op("e", vec![("v", Value::Int(2))], tx2, None)) .unwrap(); - let at_tx1 = store - .entity_attrs(&EntityId::new("e"), tx1, tx1) - .unwrap(); + let at_tx1 = store.entity_attrs(&EntityId::new("e"), tx1, tx1).unwrap(); assert_eq!(at_tx1[0].1, Value::Int(1)); - let at_tx2 = store - .entity_attrs(&EntityId::new("e"), tx2, tx2) - .unwrap(); + let at_tx2 = store.entity_attrs(&EntityId::new("e"), tx2, tx2).unwrap(); assert_eq!(at_tx2[0].1, Value::Int(2)); } @@ -340,7 +333,12 @@ fn retract_hides_attribute_in_query_view() { // Assert e.v = 1 at tx=10, then retract e.v at tx=20. store - .apply(&assert_op("e", vec![("v", Value::Int(1))], Hlc::new(10, 0), None)) + .apply(&assert_op( + "e", + vec![("v", Value::Int(1))], + Hlc::new(10, 0), + None, + )) .unwrap(); store .apply(&retract_op("e", &["v"], Hlc::new(20, 0))) @@ -364,15 +362,31 @@ fn reassert_after_retract_restores_attribute() { let dir = tempfile::tempdir().unwrap(); let store = TripleStore::open(dir.path().join("triples.redb")).unwrap(); store - .apply(&assert_op("e", vec![("v", Value::Int(1))], Hlc::new(10, 0), None)) + .apply(&assert_op( + "e", + vec![("v", Value::Int(1))], + Hlc::new(10, 0), + None, + )) .unwrap(); - store.apply(&retract_op("e", &["v"], Hlc::new(20, 0))).unwrap(); store - .apply(&assert_op("e", vec![("v", Value::Int(2))], Hlc::new(30, 0), None)) + .apply(&retract_op("e", &["v"], Hlc::new(20, 0))) + .unwrap(); + store + .apply(&assert_op( + "e", + vec![("v", Value::Int(2))], + Hlc::new(30, 0), + None, + )) .unwrap(); let now = store .entity_attrs(&EntityId::new("e"), Hlc::new(30, 0), Hlc::new(30, 0)) .unwrap(); - assert_eq!(now, vec![(AttrId::new("v"), Value::Int(2))], "re-assert after retract wins by tx_time"); + assert_eq!( + now, + vec![(AttrId::new("v"), Value::Int(2))], + "re-assert after retract wins by tx_time" + ); } diff --git a/crates/ontoref-types/src/canonical.rs b/crates/ontoref-types/src/canonical.rs index 5a9efc7..a6e70dd 100644 --- a/crates/ontoref-types/src/canonical.rs +++ b/crates/ontoref-types/src/canonical.rs @@ -3,8 +3,8 @@ //! Determinism depends on two properties: //! 1. The encoder MUST NOT emit indefinite-length items. //! 2. Map keys MUST appear in lexicographic byte order (CBOR canonical form, -//! RFC 8949 §4.2.1). `ciborium` respects struct field declaration order; -//! the type-level invariant is: every serialized struct declares fields in +//! RFC 8949 §4.2.1). `ciborium` respects struct field declaration order; the +//! type-level invariant is: every serialized struct declares fields in //! lexicographic order. Each new struct added must respect this. use serde::{de::DeserializeOwned, Serialize}; diff --git a/crates/ontoref-types/src/op_id.rs b/crates/ontoref-types/src/op_id.rs index 55aed61..e0f0150 100644 --- a/crates/ontoref-types/src/op_id.rs +++ b/crates/ontoref-types/src/op_id.rs @@ -1,8 +1,9 @@ //! Content-addressed identifier for operations. -use serde::{Deserialize, Serialize}; use std::fmt; +use serde::{Deserialize, Serialize}; + /// 32-byte blake3 content address of a canonically-encoded operation. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct OpId(pub [u8; 32]); diff --git a/crates/ontoref-types/src/operation.rs b/crates/ontoref-types/src/operation.rs index d9247b6..f1a91ff 100644 --- a/crates/ontoref-types/src/operation.rs +++ b/crates/ontoref-types/src/operation.rs @@ -46,7 +46,8 @@ pub enum OpPayload { /// witness to its DAG position (bl-022): reordering the log mutates /// the signed body and fails `verify_signature`. Witness { - /// Blake3 hash over the canonical encoding of the attested op's effects. + /// Blake3 hash over the canonical encoding of the attested op's + /// effects. effects_hash: [u8; 32], /// Domain payload the op body emitted (canonical bytes for replay). extra_payload: Vec, diff --git a/crates/ontoref-types/src/signature.rs b/crates/ontoref-types/src/signature.rs index 33f96a9..efb4749 100644 --- a/crates/ontoref-types/src/signature.rs +++ b/crates/ontoref-types/src/signature.rs @@ -35,7 +35,8 @@ impl PublicKey { pub fn verify(&self, msg: &[u8], sig: &Sig) -> Result<(), SignatureError> { let vk = VerifyingKey::from_bytes(&self.0).map_err(|_| SignatureError::InvalidPublicKey)?; let ed = EdSignature::from_bytes(&sig.0); - vk.verify(msg, &ed).map_err(|_| SignatureError::BadSignature) + vk.verify(msg, &ed) + .map_err(|_| SignatureError::BadSignature) } } @@ -51,9 +52,9 @@ impl fmt::Debug for PublicKey { /// 64-byte Ed25519 signature. /// -/// Serde default array impls cap at length 32; this newtype provides hand-rolled -/// impls that round-trip through CBOR as a byte string (major type 2) for -/// canonical, compact encoding. +/// Serde default array impls cap at length 32; this newtype provides +/// hand-rolled impls that round-trip through CBOR as a byte string (major type +/// 2) for canonical, compact encoding. #[derive(Clone, Copy, PartialEq, Eq)] pub struct Sig(pub [u8; 64]); diff --git a/crates/ontoref-types/src/succession.rs b/crates/ontoref-types/src/succession.rs index f11a4c8..a297c7e 100644 --- a/crates/ontoref-types/src/succession.rs +++ b/crates/ontoref-types/src/succession.rs @@ -146,15 +146,16 @@ mod tests { let forged = Operation::sign(body, &forger); let valid = valid_keys_for_actor(&[forged], "agent", pk(1)); - assert_eq!(valid, HashSet::from([pk(1)]), "forged successor is excluded"); + assert_eq!( + valid, + HashSet::from([pk(1)]), + "forged successor is excluded" + ); // And a forged record cannot strand a real key: from current pk(9) // (the forger's target) it resolves only itself, never pk(1). - let valid_from_9 = valid_keys_for_actor( - &[build_forged_old_signed_by_wrong_key()], - "agent", - pk(9), - ); + let valid_from_9 = + valid_keys_for_actor(&[build_forged_old_signed_by_wrong_key()], "agent", pk(9)); assert_eq!(valid_from_9, HashSet::from([pk(9)])); } diff --git a/crates/ontoref-types/tests/determinism.rs b/crates/ontoref-types/tests/determinism.rs index 1661422..2bf4fc0 100644 --- a/crates/ontoref-types/tests/determinism.rs +++ b/crates/ontoref-types/tests/determinism.rs @@ -1,5 +1,4 @@ use ed25519_dalek::SigningKey; - use ontoref_types::{ canonical, hlc::Hlc, @@ -30,7 +29,10 @@ fn canonical_encoding_is_deterministic() { let op = sample(); let a = canonical::encode(&op).unwrap(); let b = canonical::encode(&op).unwrap(); - assert_eq!(a, b, "two encodes of the same op must produce identical bytes"); + assert_eq!( + a, b, + "two encodes of the same op must produce identical bytes" + ); assert_eq!(op.id(), OpId::from_bytes(&a)); } @@ -46,7 +48,8 @@ fn round_trip_preserves_id() { #[test] fn signature_verifies() { let op = sample(); - op.verify_signature().expect("signature must verify under the actor's public key"); + op.verify_signature() + .expect("signature must verify under the actor's public key"); } #[test] diff --git a/domains/rustelo/domain.ncl b/domains/rustelo/domain.ncl index 296a200..9ec93c4 100644 --- a/domains/rustelo/domain.ncl +++ b/domains/rustelo/domain.ncl @@ -2,7 +2,7 @@ let s = import "../schema.ncl" in # Rustelo domain — full-stack Rust framework (Leptos + Axum). # Authority: rustelo project. Schemas queried via schema_cmd. -# Consumers declare .rustelo.ontoref/ for their implementation. +# Consumers declare the bond carrier .domains-ontoref/rustelo/ (bond model, ADR-073). s.Domain & { id = "rustelo", diff --git a/install/install.nu b/install/install.nu index 1e37dba..6b5f95b 100755 --- a/install/install.nu +++ b/install/install.nu @@ -305,6 +305,26 @@ def main [] { print $"✓ ontology ($ontology_dest)/ updated=($ont_updated) unchanged=($ont_skipped)" } + # ── 3e-cleanup. Remove pre-ADR-032 legacy schema/defaults at the data-dir root ─ + # A pre-ADR-032 installer copied ontology schemas/defaults to the BARE data-dir + # root ($data_dir/schemas, $data_dir/defaults). ADR-032 nested them under + # ontology/, and this installer writes ONLY $data_dir/ontology/{schemas,defaults}/ + # (§3e above) — it never touches the bare dirs, so a stale copy lingered + # indefinitely. That stale copy won import resolution over the current ontology/ + # copy for the bare forms `import "defaults/manifest.ncl"` / "schemas/manifest.ncl", + # binding checks to a schema missing later fields (kind, registry_provides) and + # failing consumer manifests with Nickel `extra field` errors (see migration 0046). + # Guarded on the canonical ontology/ copy being present so a partial install never + # deletes the only copy; idempotent. + if ($"($data_dir)/ontology/schemas" | path exists) { + for legacy in [$"($data_dir)/schemas" $"($data_dir)/defaults"] { + if ($legacy | path exists) { + rm -rf $legacy + print $"✓ removed legacy pre-ADR-032 ($legacy)" + } + } + } + # ── 3f. ADR protocol schema (data dir) ──────────────────────────────────── # Consumer projects' ADRs do `import "adr-defaults.ncl"` (→ adr-schema.ncl → # adr-constraints.ncl). Bundle just the protocol SCHEMA (not the ontoref diff --git a/install/install.sh b/install/install.sh index 8fe3e1f..eb171c7 100755 --- a/install/install.sh +++ b/install/install.sh @@ -222,6 +222,15 @@ for sub in reflection ontology domains templates public install; do cp -a "${STAGE}/data/${sub}" "${DATA_DIR}/${sub}" fi done +# Remove pre-ADR-032 legacy schema/defaults at the data-dir root. An older +# installer placed ontology schemas/defaults here; ADR-032 nested them under +# ontology/ (replaced wholesale above), and they are not in the subtree list, so +# without this they linger and win import resolution over the current ontology/ +# copy (see install.nu §3e-cleanup and migration 0046). Guarded on the canonical +# copy being present so a partial install never deletes the only copy. +if [ -d "${DATA_DIR}/ontology/schemas" ]; then + rm -rf "${DATA_DIR:?}/schemas" "${DATA_DIR:?}/defaults" +fi ok "data → ${DATA_DIR}" # ── Seed the config skeleton (idempotent — never clobber existing config) ─────