From c7c5073f8afbea51b2d04591edfe87a26b465aaf Mon Sep 17 00:00:00 2001 From: elasticdotventures Date: Sat, 22 Aug 2026 11:55:52 +0000 Subject: [PATCH 1/2] feat(iso): HasVisualization + viz_manifest wiring for Requirement/Decision/Cost MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds dedicated SemanticType::{Requirement,Decision,Cost} variants (iso.rs) and HasVisualization impls for arc_kit_au::node::{Requirement, Decision,Cost} (iso_objects.rs, gated behind the arc-kit-au feature, matching ontology.rs's arc_kit_bridge precedent), all routed to ZLayer::SystemsModel (added in ledgrrr#185). Wires the 3 new types into xtask's export_viz_manifest (now 31 domain types, up from 28) and regenerates the checked-in viz-manifest.json. Updates pipeline_e2e.rs's EXPECTED_ENTRY_COUNT and representative-type assertions to match. Verified: cargo test -p ledger-core --lib (185 passed), cargo test -p ledgerr-mcp --test pipeline_e2e (manifest count test passes), cargo check --workspace --all-features clean. This closes out the one remaining gap from ledgrrr#185 (task 4 in docs/systems-modeling-registry-rescope.md's §6), left explicitly unfinished there pending this larger change. --- Cargo.lock | 1 + crates/ledger-core/src/iso.rs | 15 ++ crates/ledger-core/src/iso_objects.rs | 64 ++++- crates/ledgerr-mcp/tests/pipeline_e2e.rs | 5 +- ui/docs/public/viz-manifest.json | 289 ++++++++++++++++++++++- xtask/Cargo.toml | 1 + xtask/src/viz_manifest.rs | 6 +- 7 files changed, 377 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b0eacf8e..bef3f2fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11546,6 +11546,7 @@ dependencies = [ name = "xtask-mcpb" version = "1.10.0" dependencies = [ + "arc-kit-au", "clap", "hex", "ledger-core", diff --git a/crates/ledger-core/src/iso.rs b/crates/ledger-core/src/iso.rs index 0b16966e..cb64638a 100644 --- a/crates/ledger-core/src/iso.rs +++ b/crates/ledger-core/src/iso.rs @@ -137,6 +137,12 @@ pub enum SemanticType { Issue, Proof, Attestation, + /// SysML-v2 systems-modeling requirement record — see `ZLayer::SystemsModel`. + Requirement, + /// SysML-v2 systems-modeling decision record — see `ZLayer::SystemsModel`. + Decision, + /// SysML-v2 systems-modeling cost record — see `ZLayer::SystemsModel`. + Cost, Unknown, } @@ -155,6 +161,9 @@ impl SemanticType { SemanticType::Issue => "issue", SemanticType::Proof => "proof", SemanticType::Attestation => "attestation", + SemanticType::Requirement => "requirement", + SemanticType::Decision => "decision", + SemanticType::Cost => "cost", SemanticType::Unknown => "unknown", } } @@ -690,6 +699,9 @@ mod tests { SemanticType::Issue, SemanticType::Proof, SemanticType::Attestation, + SemanticType::Requirement, + SemanticType::Decision, + SemanticType::Cost, SemanticType::Unknown, ]; for st in all { @@ -816,6 +828,9 @@ mod tests { SemanticType::Issue, SemanticType::Proof, SemanticType::Attestation, + SemanticType::Requirement, + SemanticType::Decision, + SemanticType::Cost, SemanticType::Unknown, ]; for st in all { diff --git a/crates/ledger-core/src/iso_objects.rs b/crates/ledger-core/src/iso_objects.rs index 0b0fbcfd..9c724e37 100644 --- a/crates/ledger-core/src/iso_objects.rs +++ b/crates/ledger-core/src/iso_objects.rs @@ -1,4 +1,4 @@ -//! `HasVisualization` implementations for the 28 domain types that participate +//! `HasVisualization` implementations for the 31 domain types that participate //! in the isometric pipeline view. //! //! Every `impl HasVisualization` added here must also be registered in @@ -469,9 +469,64 @@ let lots = wallet.cost_basis_lots(method); // FIFO | HIFO | ACB"#, } } +// ============================================================================ +// SYSTEMS MODEL — Requirement/Decision/Cost (z=6, SystemsModel layer) +// ============================================================================ + +#[cfg(feature = "arc-kit-au")] +mod systems_model { + use arc_kit_au::node::{Cost, Decision, Requirement}; + + use crate::iso::{HasVisualization, RhaiDsl, SemanticType, VisualizationSpec, ZLayer}; + + impl HasVisualization for Requirement { + fn viz_spec() -> VisualizationSpec { + VisualizationSpec { + semantic_type: SemanticType::Requirement, + z_layer: ZLayer::SystemsModel, + rhai_dsl: RhaiDsl::new( + r#"let req = load_requirement(source); +if req.status == "active" { link_decisions(req.related_decisions) }"#, + ), + description: "SysML v2 systems-modeling requirement — traceable spec record, linked to the decisions it constrains", + } + } + } + + impl HasVisualization for Decision { + fn viz_spec() -> VisualizationSpec { + VisualizationSpec { + semantic_type: SemanticType::Decision, + z_layer: ZLayer::SystemsModel, + rhai_dsl: RhaiDsl::new( + r#"let decision = record_decision(subject, rationale, decided_by); +link_requirements(decision.related_requirements);"#, + ), + description: "Version-controlled decision record — rationale + decider, linked back to the requirements it satisfies", + } + } + } + + impl HasVisualization for Cost { + fn viz_spec() -> VisualizationSpec { + VisualizationSpec { + semantic_type: SemanticType::Cost, + z_layer: ZLayer::SystemsModel, + rhai_dsl: RhaiDsl::new( + r#"let cost = record_cost(subject, amount, currency); +if cost.related_decision.is_some() { attribute_to_decision(cost) }"#, + ), + description: "Version-controlled cost record — monetary amount attributed to a decision for systems-modeling cost traceability", + } + } + } +} + #[cfg(test)] mod tests { use super::*; + #[cfg(feature = "arc-kit-au")] + use arc_kit_au::node::{Cost, Decision, Requirement}; #[test] fn all_viz_spec_rhai_dsl_has_valid_syntax() { @@ -517,6 +572,13 @@ mod tests { check!(UsRdcCredit); check!(CryptoTx); check!(CryptoWallet); + // Systems-modeling domain + #[cfg(feature = "arc-kit-au")] + { + check!(Requirement); + check!(Decision); + check!(Cost); + } } } diff --git a/crates/ledgerr-mcp/tests/pipeline_e2e.rs b/crates/ledgerr-mcp/tests/pipeline_e2e.rs index a7aa0173..333f92d0 100644 --- a/crates/ledgerr-mcp/tests/pipeline_e2e.rs +++ b/crates/ledgerr-mcp/tests/pipeline_e2e.rs @@ -180,7 +180,7 @@ fn pipe_viz_manifest_entry_count_matches_registered_types() { // `impl HasVisualization` blocks in `ledger_core::iso_objects`. It must be // regenerated (and this count updated) whenever an impl is added, removed, // or registered/deregistered in xtask/src/viz_manifest.rs. - const EXPECTED_ENTRY_COUNT: usize = 28; + const EXPECTED_ENTRY_COUNT: usize = 31; let manifest_path = Path::new(env!("CARGO_MANIFEST_DIR")) .parent() @@ -218,6 +218,9 @@ fn pipe_viz_manifest_entry_count_matches_registered_types() { "AuRdActivity", "UsRdcCredit", "CryptoWallet", + "Requirement", + "Decision", + "Cost", ] { assert!(names.contains(&expected), "missing {expected} entry"); } diff --git a/ui/docs/public/viz-manifest.json b/ui/docs/public/viz-manifest.json index 11291912..212ac67a 100644 --- a/ui/docs/public/viz-manifest.json +++ b/ui/docs/public/viz-manifest.json @@ -1,5 +1,5 @@ { - "version": "1.9.0", + "version": "1.10.0", "objects": [ { "type_name": "PipelineState", @@ -3188,6 +3188,293 @@ }, "description": "Crypto wallet — tracks asset lots, cost basis methods, and balance history for tax event attribution" } + }, + { + "type_name": "Requirement", + "spec": { + "semantic_type": "requirement", + "z_layer": "SystemsModel", + "rhai_dsl": { + "source": "let req = load_requirement(source);\nif req.status == \"active\" { link_decisions(req.related_decisions) }", + "symbols": [ + { + "kind": "Keyword", + "name": "let", + "span": { + "line": 1, + "col": 1 + } + }, + { + "kind": "Variable", + "name": "req", + "span": { + "line": 1, + "col": 5 + } + }, + { + "kind": "FunctionCall", + "name": "load_requirement", + "span": { + "line": 1, + "col": 11 + } + }, + { + "kind": "Variable", + "name": "source", + "span": { + "line": 1, + "col": 28 + } + }, + { + "kind": "Keyword", + "name": "if", + "span": { + "line": 2, + "col": 1 + } + }, + { + "kind": "Variable", + "name": "req", + "span": { + "line": 2, + "col": 4 + } + }, + { + "kind": "Variable", + "name": "status", + "span": { + "line": 2, + "col": 8 + } + }, + { + "kind": "FunctionCall", + "name": "link_decisions", + "span": { + "line": 2, + "col": 29 + } + }, + { + "kind": "Variable", + "name": "req", + "span": { + "line": 2, + "col": 44 + } + }, + { + "kind": "Variable", + "name": "related_decisions", + "span": { + "line": 2, + "col": 48 + } + } + ] + }, + "description": "SysML v2 systems-modeling requirement — traceable spec record, linked to the decisions it constrains" + } + }, + { + "type_name": "Decision", + "spec": { + "semantic_type": "decision", + "z_layer": "SystemsModel", + "rhai_dsl": { + "source": "let decision = record_decision(subject, rationale, decided_by);\nlink_requirements(decision.related_requirements);", + "symbols": [ + { + "kind": "Keyword", + "name": "let", + "span": { + "line": 1, + "col": 1 + } + }, + { + "kind": "Variable", + "name": "decision", + "span": { + "line": 1, + "col": 5 + } + }, + { + "kind": "FunctionCall", + "name": "record_decision", + "span": { + "line": 1, + "col": 16 + } + }, + { + "kind": "Variable", + "name": "subject", + "span": { + "line": 1, + "col": 32 + } + }, + { + "kind": "Variable", + "name": "rationale", + "span": { + "line": 1, + "col": 41 + } + }, + { + "kind": "Variable", + "name": "decided_by", + "span": { + "line": 1, + "col": 52 + } + }, + { + "kind": "FunctionCall", + "name": "link_requirements", + "span": { + "line": 2, + "col": 1 + } + }, + { + "kind": "Variable", + "name": "decision", + "span": { + "line": 2, + "col": 19 + } + }, + { + "kind": "Variable", + "name": "related_requirements", + "span": { + "line": 2, + "col": 28 + } + } + ] + }, + "description": "Version-controlled decision record — rationale + decider, linked back to the requirements it satisfies" + } + }, + { + "type_name": "Cost", + "spec": { + "semantic_type": "cost", + "z_layer": "SystemsModel", + "rhai_dsl": { + "source": "let cost = record_cost(subject, amount, currency);\nif cost.related_decision.is_some() { attribute_to_decision(cost) }", + "symbols": [ + { + "kind": "Keyword", + "name": "let", + "span": { + "line": 1, + "col": 1 + } + }, + { + "kind": "Variable", + "name": "cost", + "span": { + "line": 1, + "col": 5 + } + }, + { + "kind": "FunctionCall", + "name": "record_cost", + "span": { + "line": 1, + "col": 12 + } + }, + { + "kind": "Variable", + "name": "subject", + "span": { + "line": 1, + "col": 24 + } + }, + { + "kind": "Variable", + "name": "amount", + "span": { + "line": 1, + "col": 33 + } + }, + { + "kind": "Variable", + "name": "currency", + "span": { + "line": 1, + "col": 41 + } + }, + { + "kind": "Keyword", + "name": "if", + "span": { + "line": 2, + "col": 1 + } + }, + { + "kind": "Variable", + "name": "cost", + "span": { + "line": 2, + "col": 4 + } + }, + { + "kind": "Variable", + "name": "related_decision", + "span": { + "line": 2, + "col": 9 + } + }, + { + "kind": "FunctionCall", + "name": "is_some", + "span": { + "line": 2, + "col": 26 + } + }, + { + "kind": "FunctionCall", + "name": "attribute_to_decision", + "span": { + "line": 2, + "col": 38 + } + }, + { + "kind": "Variable", + "name": "cost", + "span": { + "line": 2, + "col": 60 + } + } + ] + }, + "description": "Version-controlled cost record — monetary amount attributed to a decision for systems-modeling cost traceability" + } } ] } \ No newline at end of file diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 500f1587..5b4960a1 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -15,6 +15,7 @@ name = "xtask_mcpb" path = "src/lib.rs" [dependencies] +arc-kit-au = { path = "../crates/arc-kit-au" } clap = { version = "4", features = ["derive"] } ledger-core = { path = "../crates/ledger-core" } hex = "0.4" diff --git a/xtask/src/viz_manifest.rs b/xtask/src/viz_manifest.rs index 43e27519..aa0750c3 100644 --- a/xtask/src/viz_manifest.rs +++ b/xtask/src/viz_manifest.rs @@ -1,12 +1,13 @@ //! Export the VisualizationSpec JSON manifest for the docs UI. //! -//! Collects `HasVisualization::viz_spec()` from all 28 domain types (every +//! Collects `HasVisualization::viz_spec()` from all 31 domain types (every //! `impl HasVisualization` in `ledger_core::iso_objects`, with the generic //! `StageResult` represented once via `StageResult<()>`) and writes a //! `VizManifest` JSON file to the specified output path. use std::path::Path; +use arc_kit_au::node::{Cost, Decision, Requirement}; use ledger_core::{ au_rd::{AuRdActivity, AuRdOffset}, constraints::{ @@ -74,6 +75,9 @@ pub fn export_viz_manifest(output: &Path) -> Result<(), Box Date: Sun, 23 Aug 2026 16:32:52 +1000 Subject: [PATCH 2/2] feat(arc-kit-au): retrofit #[derive(SysmlBlock)] onto existing node types (#193) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Applies the sysml-derive macro (feat/sysml-derive-spike, #183) to the 8 pre-existing arc-kit-au node structs that predate the systems-modeling epic: SourceDoc, ExtractedRow, Transaction, Classification, ModelProposal, OperatorApproval, ValidationIssue, WorkbookRow. This was the second of two explicitly-deferred follow-ons from task 3 (#184) — the first (HasVisualization/viz_manifest wiring for Requirement/Decision/Cost) landed as part of this same stack. The derive is purely syntactic (walks named fields via syn, stringifies each field's type via quote!) so it applies uniformly regardless of field type — no per-struct special-casing needed, confirmed by re-reading crates/sysml-derive/src/lib.rs before applying. Verified: cargo test -p sysml-derive (2 tests), cargo test -p arc-kit-au (46 tests, unchanged from pre-retrofit baseline), cargo check --workspace --all-features (clean, one pre-existing unrelated warning in ledgerr-mcp/src/fbar.rs), cargo clippy -p arc-kit-au --all-features (clean). --- crates/arc-kit-au/src/node.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/arc-kit-au/src/node.rs b/crates/arc-kit-au/src/node.rs index dc930261..81059d27 100644 --- a/crates/arc-kit-au/src/node.rs +++ b/crates/arc-kit-au/src/node.rs @@ -124,7 +124,7 @@ pub fn content_hash(parts: &[&str]) -> String { } /// Source document evidence. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, SysmlBlock)] pub struct SourceDoc { /// Original filename (must follow VENDOR--ACCOUNT--YYYY-MM--DOCTYPE.ext) pub filename: String, @@ -151,7 +151,7 @@ impl SourceDoc { } /// Extracted row from document parsing. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, SysmlBlock)] pub struct ExtractedRow { pub account_id: String, pub date: String, @@ -176,7 +176,7 @@ impl ExtractedRow { } /// Deterministic transaction record. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, SysmlBlock)] pub struct Transaction { /// Blake3 hash of account/date/amount/description pub tx_id: String, @@ -194,7 +194,7 @@ impl Transaction { } /// Classification applied to transaction. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, SysmlBlock)] pub struct Classification { pub tx_id: String, pub category: String, @@ -221,7 +221,7 @@ impl Classification { } /// Model-generated classification proposal. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, SysmlBlock)] pub struct ModelProposal { pub tx_id: String, pub model_name: String, @@ -247,7 +247,7 @@ impl ModelProposal { } /// Operator approval/rejection of model proposal. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, SysmlBlock)] pub struct OperatorApproval { pub tx_id: String, pub operator_id: String, @@ -275,7 +275,7 @@ impl OperatorApproval { /// Distinct from Classification — validation artifacts represent rule/constraint /// failures, not categorization decisions. PRD-4 Phase 2 requires /// classification_artifact → validation_artifact as a separate chain step. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, SysmlBlock)] pub struct ValidationIssue { pub tx_id: String, pub rule: String, @@ -301,7 +301,7 @@ impl ValidationIssue { } /// Final workbook row in CPA export. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, SysmlBlock)] pub struct WorkbookRow { pub tx_id: String, pub sheet_name: String,