Skip to content

feat: widen ArtifactKind/NodeType/OperationKind with Requirement/Decision/Cost - #184

Merged
elasticdotventures merged 13 commits into
feat/sysml-derive-spikefrom
feat/requirement-decision-cost-kinds
Aug 23, 2026
Merged

feat: widen ArtifactKind/NodeType/OperationKind with Requirement/Decision/Cost#184
elasticdotventures merged 13 commits into
feat/sysml-derive-spikefrom
feat/requirement-decision-cost-kinds

Conversation

@elasticdotventures

Copy link
Copy Markdown
Member

Summary

Task 3 from docs/systems-modeling-registry-rescope.md (epic part 2, #181), backed by sysml-derive (#183) per the resolved LinkML comparison (§3a). Stacked on #183 — base branch is feat/sysml-derive-spike, not main.

Changes

arc-kit-au:

  • NodeType gains Requirement/Decision/Cost variants (prefixes req/dec/cost)
  • New Requirement/Decision/Cost structs, following the existing Transaction/Classification pattern (content-hashed node_id()), each annotated with #[derive(SysmlBlock)] from sysml-derive
  • EvidenceNode gains matching variants; node_id()/node_type()/tx_id() updated (tx_id()None for all three, same as SourceDoc/ExtractedRow)
  • Now depends on sysml-derive

ledger-core:

  • ArtifactKind gains matching variants + canonical_name() arms
  • arc_kit_bridge's From<ArtifactKind> for NodeType maps them explicitly (previously would've fallen through the _ => Unknown wildcard)
  • OperationKind gains RecordDecision/RecordCost/ImportRequirement, each with a corresponding LedgerOperation impl (content-hashed, idempotent, same shape as CheckTaxDeadlineOp) and wired into OperationDispatcher::from_scheduled_events

ledgerr-mcp:

  • evidence_node_type_label() (behind the legacy feature) gets the 3 new NodeType arms it was missing — this was an exhaustive match that would not have compiled otherwise

Verification

  • cargo test -p arc-kit-au — 46 tests pass
  • cargo test -p ledger-core --lib — 184 tests pass
  • cargo build -p ledgerr-mcp --features legacy — compiles clean
  • cargo check --workspace --all-features — passes
  • clippy clean on all touched files (pre-existing warnings elsewhere untouched)

Scope

Retrofitting existing variants (Transaction/TaxCategory/etc.) onto sysml-derive is a tracked follow-on, not done here, per the doc's own scoping. Wiring these into crates/ledgerr-mcp/src/contract.rs (the MCP capability surface) is a separate later task (§6 task 6 in #181).

…sion/Cost

Task 3 from docs/systems-modeling-registry-rescope.md (epic part 2,
ledgrrr#181), backed by sysml-derive (#183) per the resolved LinkML
comparison (#3a).

arc-kit-au:
- NodeType gains Requirement/Decision/Cost variants (prefixes req/dec/cost)
- New Requirement/Decision/Cost structs, following the existing
  Transaction/Classification pattern (content-hashed node_id()), each
  annotated with #[derive(SysmlBlock)] from sysml-derive
- EvidenceNode gains matching variants; node_id()/node_type()/tx_id()
  updated (tx_id() -> None for all three, same as SourceDoc/ExtractedRow)
- arc-kit-au now depends on sysml-derive

ledger-core:
- ArtifactKind gains matching variants + canonical_name() arms
- arc_kit_bridge's From<ArtifactKind> for NodeType maps them explicitly
  (was previously falling through the _ => Unknown wildcard)
- OperationKind gains RecordDecision/RecordCost/ImportRequirement, each
  with a corresponding LedgerOperation impl (content-hashed, idempotent,
  same shape as CheckTaxDeadlineOp) and wired into
  OperationDispatcher::from_scheduled_events

ledgerr-mcp:
- evidence_node_type_label() (behind the legacy feature) gets the 3
  new NodeType arms it was missing

Verified: cargo test -p arc-kit-au (46 tests), cargo test -p ledger-core
--lib (184 tests), cargo build -p ledgerr-mcp --features legacy, and
cargo check --workspace --all-features all pass. clippy clean on the
touched files.

Retrofitting existing variants (Transaction/TaxCategory/etc.) onto
sysml-derive is a tracked follow-on, not done here, per the doc's own
scoping.
@elasticdotventures
elasticdotventures marked this pull request as ready for review August 22, 2026 13:09
…lues

DateTime<Utc> previously emitted the literal, invalid text
`DateTime<Utc>` as a SysML v2 attribute type -- SysML v2's grammar has
no angle-bracket generic-parameter syntax, so this would fail to parse
under any conformant SysML v2 tool. bool/usize/etc. also passed through
as bare Rust keywords with no corresponding SysML type.

Map DateTime<_> -> ScalarValues::String, bool -> ScalarValues::Boolean,
unsigned ints -> ScalarValues::Natural, signed ints ->
ScalarValues::Integer, floats -> ScalarValues::Rational. Any other
single-type-argument generic is now a compile error instead of a
silent invalid-syntax emission. Opaque domain types (NodeId,
Confidence, Decimal) still pass through as bare names -- documented as
an intentional modeling assumption, not a bug.

Adds regression tests for the field types introduced by #184/#193
(Decimal, Confidence, bool, usize, DateTime<Utc>) that basic.rs never
exercised. Closes ledgrrr#195.
…ing' into feat/requirement-decision-cost-kinds
…eal parser validation into ufo-types

Ran SysmlV2Emitter's own output through the real sysml-v2-parser crate
(docs/sysml-v2-parser-spike.md, an existing unmerged spike) and confirmed
two bugs make it non-parseable:

- The closing '}' was on the same line as a trailing '//' comment, so the
  comment swallowed it -- the block was never syntactically closed.
- The emitter used SysML v1's 'block def' keyword. SysML v2 renamed this
  construct to 'part def'; 'block' is not a SysML v2 keyword at all.

Fixed both. Added ufo_types::sysml -- a shared Constraint/Satisfies-based
SysML v2 syntax validator wired to sysml-v2-parser (pinned to =0.54.0 per
the spike's crate-health findings; not wasm32-compatible, so this must
stay out of holon-viz's runtime dependency graph -- added to holon-viz
only as a dev-dependency, used in a new round-trip test that feeds the
emitter's own output through the real parser instead of just asserting on
substrings.

This is the concrete round-trip-closed signal the existing spike (PR #187)
called out as the next step.
…yntax' into fix/sysml-block-scalar-type-mapping

# Conflicts:
#	Cargo.lock
…al SysML v2 grammar

Same bug holon-viz's SysmlV2Emitter had (ledgrrr#197): SysML v1 called
this construct Block/'block def'; SysML v2 renamed it to 'part def', and
'block' is not a SysML v2 keyword at all. Confirmed via the newly-wired
ufo_types::sysml::validate_sysml_v2 (real sysml-v2-parser crate, not a
hand-rolled heuristic) that the fixed output actually parses.

Adds tests/real_grammar_validation.rs: runs the actual generated
sysml_block_def() text for Transaction/Requirement/ExtractedRow/
ModelProposal/WorkbookRow (mirroring the real production structs from
#184/#193) through the real parser. This replaces the 'no angle brackets'
manual check used to validate the earlier DateTime/bool/usize scalar
mapping fix with genuine grammar validation, closing out ledgrrr#195.
…ing' into feat/requirement-decision-cost-kinds
…yntax' into fix/sysml-block-scalar-type-mapping
…ing' into feat/requirement-decision-cost-kinds

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clippy found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

elasticdotventures added a commit that referenced this pull request Aug 23, 2026
…ypes (#193)

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).
elasticdotventures added a commit that referenced this pull request Aug 23, 2026
…ision/Cost (#190)

* feat(iso): HasVisualization + viz_manifest wiring for Requirement/Decision/Cost

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.

* feat(arc-kit-au): retrofit #[derive(SysmlBlock)] onto existing node types (#193)

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).
elasticdotventures added a commit that referenced this pull request Aug 23, 2026
* feat(reqif-mcp-spike): Rust MCP client for reqif-opa-mcp + Requirement converter

Spike per decision 6 (docs/systems-modeling-registry-rescope.md §5/§6
task 5): reqif-opa-mcp is wrapped over MCP, not ported to Rust;
arc-kit-au stays the canonical decision+cost ledger.

New crate reqif-mcp-spike:
- McpHttpClient: minimal blocking client for reqif-opa-mcp's Streamable-
  HTTP MCP server (FastMCP 3.0.0b1, protocol 2024-11-05). Handles the
  initialize handshake + mcp-session-id header, the mandatory
  notifications/initialized follow-up, and tools/call, extracting the
  JSON-RPC result out of the single-frame SSE response body.
- RequirementRecord: mirrors reqif-opa-mcp's requirement-record.schema.json
  exactly (uid/key/subtypes/status/policy_baseline/rubrics/text/attrs).
- requirement_record_to_node(): converts a RequirementRecord into the
  arc-kit-au Requirement struct from ledgrrr#184 (ArtifactKind::Requirement /
  NodeType::Requirement). requirement_id<-uid, title<-key, rationale<-text,
  source<-attrs.source_standard(+source_url) falling back to the policy
  baseline id, status passed through, related_decisions always empty
  (reqif-opa-mcp carries no decision links; those are created later in
  arc-kit-au itself).

Verified live end-to-end against a real reqif-opa-mcp checkout
(uv sync --extra ingest-lite; uv run python -m reqif_mcp --http --port
8123) parsing both its own sample derived baselines,
samples/standards/derived/{nist_ssdf_dogfood,owasp_asvs_cwe}.reqif (4
and 7 requirements respectively), through reqif_parse -> reqif_query ->
requirement_record_to_node -> Requirement::node_id(), all producing
correct, deterministic req: NodeIds. That live run is captured as an
#[ignore]'d integration test (tests/live_server.rs) since it needs an
external repo checkout + Python/uv, not something CI or a fresh clone
has; re-run manually with REQIF_MCP_URL set to a running server.

cargo test -p reqif-mcp-spike: 5 unit tests pass, 1 ignored (live).
cargo check --workspace --all-features: clean. cargo clippy -p
reqif-mcp-spike --all-targets: clean.

* feat(ledgerr-mcp): wire Requirement/Decision/Cost into contract.rs (task 6)

- EvidenceArgs gains import_requirement/record_decision/record_cost
  actions, each constructing the corresponding arc-kit-au node
  (Requirement/Decision/Cost from ledgrrr#184) and inserting it into the
  evidence graph via EvidenceGraph::add_node. DuplicateNode is treated as
  idempotent success (content-hash dedup), matching the idempotent
  semantics already established for EvidenceBuilder's ensure_* methods.
- parse_evidence_node_type gains requirement/req, decision/dec, cost
  (plus previously-missing rnd_activity/tax_offset, same class of gap).
- Summary's node_counts and ListNodes' invalid-type error message
  extended to include the 3 new types.
- EVIDENCE_TOOL's contract.rs action list and purpose string updated.

Verified: new tests/evidence_requirement_decision_cost.rs (3 tests,
covering import+list+detail+summary, record_decision+record_cost, and
idempotent re-import) all pass; cargo build -p ledgerr-mcp --features
legacy clean (1 pre-existing unrelated warning).

* docs: regenerate mcp-capability-contract.md (drift from task 6)

Task 6 (ledgrrr#186's second commit) added import_requirement/
record_decision/record_cost to ledgerr_evidence's EVIDENCE_TOOL actions
in contract.rs but never re-ran regen-docs, so the checked-in
mcp-capability-contract.md drifted — caught by CI's check-drift step
on downstream stacked PRs (#190, #193).

Ran: cargo run -p ledgerr-mcp --bin regen-docs

* docs: regenerate viz-manifest.json (stale version field, 1.9.0 -> 1.10.0)

Pre-existing drift, unrelated to Requirement/Decision/Cost content (28
objects, unchanged) -- the workspace version was bumped to 1.10.0 at
some point after this artifact was last regenerated. Only surfaced now
because check-drift's earlier mcp-capability-contract.md failure (fixed
in dd168f7) was masking this second, independent drift.

Ran: cargo run -p xtask-mcpb -- export-viz-manifest

* feat(iso): HasVisualization + viz_manifest wiring for Requirement/Decision/Cost (#190)

* feat(iso): HasVisualization + viz_manifest wiring for Requirement/Decision/Cost

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.

* feat(arc-kit-au): retrofit #[derive(SysmlBlock)] onto existing node types (#193)

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).
* feat(iso): add dedicated ZLayer::SystemsModel variant

Requirement/Decision/Cost content (ledgrrr#184) gets its own isometric
layer rather than folding into the existing 6 or the still-unimplemented
proposed Domain layer (docs/ontological-implementation-spec.md §6.1),
per decision 2 in docs/systems-modeling-registry-rescope.md: independent
toggle/color in the renderer over reusing an ontological-concepts layer.

index=6, base_z=816.0 (continuing the existing 136.0 spacing), color
#be185d (distinct from all 6 existing hexes). No HasVisualization impls
wired yet for Requirement/Decision/Cost themselves — that requires also
touching xtask's viz_manifest export + the checked-in viz-manifest.json
per iso_objects.rs's own convention, tracked as a follow-on alongside
task 6 (ledgerr-mcp/contract.rs wiring), not done here.

* feat: spike reqif-opa-mcp over MCP + Requirement converter (#186)

* feat(reqif-mcp-spike): Rust MCP client for reqif-opa-mcp + Requirement converter

Spike per decision 6 (docs/systems-modeling-registry-rescope.md §5/§6
task 5): reqif-opa-mcp is wrapped over MCP, not ported to Rust;
arc-kit-au stays the canonical decision+cost ledger.

New crate reqif-mcp-spike:
- McpHttpClient: minimal blocking client for reqif-opa-mcp's Streamable-
  HTTP MCP server (FastMCP 3.0.0b1, protocol 2024-11-05). Handles the
  initialize handshake + mcp-session-id header, the mandatory
  notifications/initialized follow-up, and tools/call, extracting the
  JSON-RPC result out of the single-frame SSE response body.
- RequirementRecord: mirrors reqif-opa-mcp's requirement-record.schema.json
  exactly (uid/key/subtypes/status/policy_baseline/rubrics/text/attrs).
- requirement_record_to_node(): converts a RequirementRecord into the
  arc-kit-au Requirement struct from ledgrrr#184 (ArtifactKind::Requirement /
  NodeType::Requirement). requirement_id<-uid, title<-key, rationale<-text,
  source<-attrs.source_standard(+source_url) falling back to the policy
  baseline id, status passed through, related_decisions always empty
  (reqif-opa-mcp carries no decision links; those are created later in
  arc-kit-au itself).

Verified live end-to-end against a real reqif-opa-mcp checkout
(uv sync --extra ingest-lite; uv run python -m reqif_mcp --http --port
8123) parsing both its own sample derived baselines,
samples/standards/derived/{nist_ssdf_dogfood,owasp_asvs_cwe}.reqif (4
and 7 requirements respectively), through reqif_parse -> reqif_query ->
requirement_record_to_node -> Requirement::node_id(), all producing
correct, deterministic req: NodeIds. That live run is captured as an
#[ignore]'d integration test (tests/live_server.rs) since it needs an
external repo checkout + Python/uv, not something CI or a fresh clone
has; re-run manually with REQIF_MCP_URL set to a running server.

cargo test -p reqif-mcp-spike: 5 unit tests pass, 1 ignored (live).
cargo check --workspace --all-features: clean. cargo clippy -p
reqif-mcp-spike --all-targets: clean.

* feat(ledgerr-mcp): wire Requirement/Decision/Cost into contract.rs (task 6)

- EvidenceArgs gains import_requirement/record_decision/record_cost
  actions, each constructing the corresponding arc-kit-au node
  (Requirement/Decision/Cost from ledgrrr#184) and inserting it into the
  evidence graph via EvidenceGraph::add_node. DuplicateNode is treated as
  idempotent success (content-hash dedup), matching the idempotent
  semantics already established for EvidenceBuilder's ensure_* methods.
- parse_evidence_node_type gains requirement/req, decision/dec, cost
  (plus previously-missing rnd_activity/tax_offset, same class of gap).
- Summary's node_counts and ListNodes' invalid-type error message
  extended to include the 3 new types.
- EVIDENCE_TOOL's contract.rs action list and purpose string updated.

Verified: new tests/evidence_requirement_decision_cost.rs (3 tests,
covering import+list+detail+summary, record_decision+record_cost, and
idempotent re-import) all pass; cargo build -p ledgerr-mcp --features
legacy clean (1 pre-existing unrelated warning).

* docs: regenerate mcp-capability-contract.md (drift from task 6)

Task 6 (ledgrrr#186's second commit) added import_requirement/
record_decision/record_cost to ledgerr_evidence's EVIDENCE_TOOL actions
in contract.rs but never re-ran regen-docs, so the checked-in
mcp-capability-contract.md drifted — caught by CI's check-drift step
on downstream stacked PRs (#190, #193).

Ran: cargo run -p ledgerr-mcp --bin regen-docs

* docs: regenerate viz-manifest.json (stale version field, 1.9.0 -> 1.10.0)

Pre-existing drift, unrelated to Requirement/Decision/Cost content (28
objects, unchanged) -- the workspace version was bumped to 1.10.0 at
some point after this artifact was last regenerated. Only surfaced now
because check-drift's earlier mcp-capability-contract.md failure (fixed
in dd168f7) was masking this second, independent drift.

Ran: cargo run -p xtask-mcpb -- export-viz-manifest

* feat(iso): HasVisualization + viz_manifest wiring for Requirement/Decision/Cost (#190)

* feat(iso): HasVisualization + viz_manifest wiring for Requirement/Decision/Cost

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.

* feat(arc-kit-au): retrofit #[derive(SysmlBlock)] onto existing node types (#193)

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).
@elasticdotventures
elasticdotventures merged commit 8a4830c into feat/sysml-derive-spike Aug 23, 2026
10 checks passed
elasticdotventures added a commit that referenced this pull request Aug 23, 2026
* feat(sysml-derive): spike a Rust-AST-to-SysML-v2 derive macro

New crate: #[derive(SysmlBlock)] walks a struct's named fields via syn
and generates a sysml_block_def() associated fn returning the
equivalent SysML-v2 'block def' text at compile time (matching
holon-viz's SysmlV2Emitter 'block def' terminology).

Field-type mapping: Vec<T> -> T[*], Option<T> -> T[0..1], everything
else -> plain T. Not yet checked against a real SysML-v2 grammar/parser
(Part 1's Tier 0 candidates) -- this is the spike from
docs/systems-modeling-registry-rescope.md #2a/#6 task 1, proving the
AST-walk direction before comparing it against the LinkML spike
(task 2) and wiring either into the real ArtifactKind/NodeType widening
(task 3).

Tests mirror arc-kit-au::Transaction's and ::Classification's field
shapes (name+type only, no dependency on arc-kit-au itself -- wiring
the derive onto the real production structs is task 3, not this spike)
and assert on the emitted block def text for both the Vec and Option
multiplicity branches.

Same technique this codebase already approved for a different target:
AGENTS.md (PM-3, 2026-05-13) sanctions #[derive(specta::Type)] for
Rust-to-TypeScript; this is Rust-to-SysML-v2/KerML instead.

* chore: regenerate viz-manifest.json (version drift 1.9.0 -> 1.10.0)

* feat: widen ArtifactKind/NodeType/OperationKind with Requirement/Decision/Cost (#184)

* feat: widen ArtifactKind/NodeType/OperationKind with Requirement/Decision/Cost

Task 3 from docs/systems-modeling-registry-rescope.md (epic part 2,
ledgrrr#181), backed by sysml-derive (#183) per the resolved LinkML
comparison (#3a).

arc-kit-au:
- NodeType gains Requirement/Decision/Cost variants (prefixes req/dec/cost)
- New Requirement/Decision/Cost structs, following the existing
  Transaction/Classification pattern (content-hashed node_id()), each
  annotated with #[derive(SysmlBlock)] from sysml-derive
- EvidenceNode gains matching variants; node_id()/node_type()/tx_id()
  updated (tx_id() -> None for all three, same as SourceDoc/ExtractedRow)
- arc-kit-au now depends on sysml-derive

ledger-core:
- ArtifactKind gains matching variants + canonical_name() arms
- arc_kit_bridge's From<ArtifactKind> for NodeType maps them explicitly
  (was previously falling through the _ => Unknown wildcard)
- OperationKind gains RecordDecision/RecordCost/ImportRequirement, each
  with a corresponding LedgerOperation impl (content-hashed, idempotent,
  same shape as CheckTaxDeadlineOp) and wired into
  OperationDispatcher::from_scheduled_events

ledgerr-mcp:
- evidence_node_type_label() (behind the legacy feature) gets the 3
  new NodeType arms it was missing

Verified: cargo test -p arc-kit-au (46 tests), cargo test -p ledger-core
--lib (184 tests), cargo build -p ledgerr-mcp --features legacy, and
cargo check --workspace --all-features all pass. clippy clean on the
touched files.

Retrofitting existing variants (Transaction/TaxCategory/etc.) onto
sysml-derive is a tracked follow-on, not done here, per the doc's own
scoping.

* fix(sysml-derive): map primitive scalars + DateTime to SysML ScalarValues

DateTime<Utc> previously emitted the literal, invalid text
`DateTime<Utc>` as a SysML v2 attribute type -- SysML v2's grammar has
no angle-bracket generic-parameter syntax, so this would fail to parse
under any conformant SysML v2 tool. bool/usize/etc. also passed through
as bare Rust keywords with no corresponding SysML type.

Map DateTime<_> -> ScalarValues::String, bool -> ScalarValues::Boolean,
unsigned ints -> ScalarValues::Natural, signed ints ->
ScalarValues::Integer, floats -> ScalarValues::Rational. Any other
single-type-argument generic is now a compile error instead of a
silent invalid-syntax emission. Opaque domain types (NodeId,
Confidence, Decimal) still pass through as bare names -- documented as
an intentional modeling assumption, not a bug.

Adds regression tests for the field types introduced by #184/#193
(Decimal, Confidence, bool, usize, DateTime<Utc>) that basic.rs never
exercised. Closes ledgrrr#195.

* fix(holon-viz): emit valid SysML v2 (part def, not block def); wire real parser validation into ufo-types

Ran SysmlV2Emitter's own output through the real sysml-v2-parser crate
(docs/sysml-v2-parser-spike.md, an existing unmerged spike) and confirmed
two bugs make it non-parseable:

- The closing '}' was on the same line as a trailing '//' comment, so the
  comment swallowed it -- the block was never syntactically closed.
- The emitter used SysML v1's 'block def' keyword. SysML v2 renamed this
  construct to 'part def'; 'block' is not a SysML v2 keyword at all.

Fixed both. Added ufo_types::sysml -- a shared Constraint/Satisfies-based
SysML v2 syntax validator wired to sysml-v2-parser (pinned to =0.54.0 per
the spike's crate-health findings; not wasm32-compatible, so this must
stay out of holon-viz's runtime dependency graph -- added to holon-viz
only as a dev-dependency, used in a new round-trip test that feeds the
emitter's own output through the real parser instead of just asserting on
substrings.

This is the concrete round-trip-closed signal the existing spike (PR #187)
called out as the next step.

* fix(sysml-derive): emit part def (not block def); validate against real SysML v2 grammar

Same bug holon-viz's SysmlV2Emitter had (ledgrrr#197): SysML v1 called
this construct Block/'block def'; SysML v2 renamed it to 'part def', and
'block' is not a SysML v2 keyword at all. Confirmed via the newly-wired
ufo_types::sysml::validate_sysml_v2 (real sysml-v2-parser crate, not a
hand-rolled heuristic) that the fixed output actually parses.

Adds tests/real_grammar_validation.rs: runs the actual generated
sysml_block_def() text for Transaction/Requirement/ExtractedRow/
ModelProposal/WorkbookRow (mirroring the real production structs from
#184/#193) through the real parser. This replaces the 'no angle brackets'
manual check used to validate the earlier DateTime/bool/usize scalar
mapping fix with genuine grammar validation, closing out ledgrrr#195.

* test(arc-kit-au): update block def -> part def assertion (sysml-derive fix)

* chore: regenerate viz-manifest.json (version drift, ledgrrr#194)

* feat(iso): dedicated ZLayer::SystemsModel variant (#185)

* feat(iso): add dedicated ZLayer::SystemsModel variant

Requirement/Decision/Cost content (ledgrrr#184) gets its own isometric
layer rather than folding into the existing 6 or the still-unimplemented
proposed Domain layer (docs/ontological-implementation-spec.md §6.1),
per decision 2 in docs/systems-modeling-registry-rescope.md: independent
toggle/color in the renderer over reusing an ontological-concepts layer.

index=6, base_z=816.0 (continuing the existing 136.0 spacing), color
#be185d (distinct from all 6 existing hexes). No HasVisualization impls
wired yet for Requirement/Decision/Cost themselves — that requires also
touching xtask's viz_manifest export + the checked-in viz-manifest.json
per iso_objects.rs's own convention, tracked as a follow-on alongside
task 6 (ledgerr-mcp/contract.rs wiring), not done here.

* feat: spike reqif-opa-mcp over MCP + Requirement converter (#186)

* feat(reqif-mcp-spike): Rust MCP client for reqif-opa-mcp + Requirement converter

Spike per decision 6 (docs/systems-modeling-registry-rescope.md §5/§6
task 5): reqif-opa-mcp is wrapped over MCP, not ported to Rust;
arc-kit-au stays the canonical decision+cost ledger.

New crate reqif-mcp-spike:
- McpHttpClient: minimal blocking client for reqif-opa-mcp's Streamable-
  HTTP MCP server (FastMCP 3.0.0b1, protocol 2024-11-05). Handles the
  initialize handshake + mcp-session-id header, the mandatory
  notifications/initialized follow-up, and tools/call, extracting the
  JSON-RPC result out of the single-frame SSE response body.
- RequirementRecord: mirrors reqif-opa-mcp's requirement-record.schema.json
  exactly (uid/key/subtypes/status/policy_baseline/rubrics/text/attrs).
- requirement_record_to_node(): converts a RequirementRecord into the
  arc-kit-au Requirement struct from ledgrrr#184 (ArtifactKind::Requirement /
  NodeType::Requirement). requirement_id<-uid, title<-key, rationale<-text,
  source<-attrs.source_standard(+source_url) falling back to the policy
  baseline id, status passed through, related_decisions always empty
  (reqif-opa-mcp carries no decision links; those are created later in
  arc-kit-au itself).

Verified live end-to-end against a real reqif-opa-mcp checkout
(uv sync --extra ingest-lite; uv run python -m reqif_mcp --http --port
8123) parsing both its own sample derived baselines,
samples/standards/derived/{nist_ssdf_dogfood,owasp_asvs_cwe}.reqif (4
and 7 requirements respectively), through reqif_parse -> reqif_query ->
requirement_record_to_node -> Requirement::node_id(), all producing
correct, deterministic req: NodeIds. That live run is captured as an
#[ignore]'d integration test (tests/live_server.rs) since it needs an
external repo checkout + Python/uv, not something CI or a fresh clone
has; re-run manually with REQIF_MCP_URL set to a running server.

cargo test -p reqif-mcp-spike: 5 unit tests pass, 1 ignored (live).
cargo check --workspace --all-features: clean. cargo clippy -p
reqif-mcp-spike --all-targets: clean.

* feat(ledgerr-mcp): wire Requirement/Decision/Cost into contract.rs (task 6)

- EvidenceArgs gains import_requirement/record_decision/record_cost
  actions, each constructing the corresponding arc-kit-au node
  (Requirement/Decision/Cost from ledgrrr#184) and inserting it into the
  evidence graph via EvidenceGraph::add_node. DuplicateNode is treated as
  idempotent success (content-hash dedup), matching the idempotent
  semantics already established for EvidenceBuilder's ensure_* methods.
- parse_evidence_node_type gains requirement/req, decision/dec, cost
  (plus previously-missing rnd_activity/tax_offset, same class of gap).
- Summary's node_counts and ListNodes' invalid-type error message
  extended to include the 3 new types.
- EVIDENCE_TOOL's contract.rs action list and purpose string updated.

Verified: new tests/evidence_requirement_decision_cost.rs (3 tests,
covering import+list+detail+summary, record_decision+record_cost, and
idempotent re-import) all pass; cargo build -p ledgerr-mcp --features
legacy clean (1 pre-existing unrelated warning).

* docs: regenerate mcp-capability-contract.md (drift from task 6)

Task 6 (ledgrrr#186's second commit) added import_requirement/
record_decision/record_cost to ledgerr_evidence's EVIDENCE_TOOL actions
in contract.rs but never re-ran regen-docs, so the checked-in
mcp-capability-contract.md drifted — caught by CI's check-drift step
on downstream stacked PRs (#190, #193).

Ran: cargo run -p ledgerr-mcp --bin regen-docs

* docs: regenerate viz-manifest.json (stale version field, 1.9.0 -> 1.10.0)

Pre-existing drift, unrelated to Requirement/Decision/Cost content (28
objects, unchanged) -- the workspace version was bumped to 1.10.0 at
some point after this artifact was last regenerated. Only surfaced now
because check-drift's earlier mcp-capability-contract.md failure (fixed
in dd168f7) was masking this second, independent drift.

Ran: cargo run -p xtask-mcpb -- export-viz-manifest

* feat(iso): HasVisualization + viz_manifest wiring for Requirement/Decision/Cost (#190)

* feat(iso): HasVisualization + viz_manifest wiring for Requirement/Decision/Cost

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.

* feat(arc-kit-au): retrofit #[derive(SysmlBlock)] onto existing node types (#193)

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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants