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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ members = [
"crates/spar-verify",
"crates/spar-wasm",
"crates/spar-insight",
"crates/spar-trace-topology",
]

[workspace.package]
Expand All @@ -45,6 +46,7 @@ spar-transform = { path = "crates/spar-transform" }
spar-variants = { path = "crates/spar-variants" }
spar-codegen = { path = "crates/spar-codegen" }
spar-insight = { path = "crates/spar-insight" }
spar-trace-topology = { path = "crates/spar-trace-topology" }
rowan = "0.16"
salsa = "0.26"
la-arena = "0.3"
Expand Down
48 changes: 48 additions & 0 deletions artifacts/requirements.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1731,4 +1731,52 @@ artifacts:
status: implemented
tags: [mcp, migration, track-e, v090, integration]

# ── Track G — v0.10.0 trace-topology foundation ──────────────────────

- id: REQ-TRACE-TOPOLOGY-001
type: requirement
title: Spar_Identity property surface
description: >
System shall expose a Spar_Identity property set that lets
AADL declare runtime-observable identities for the v0.10.0+
`spar trace topology` reconciler. The set carries six
properties: MAC_Address (aadlstring; device, processor),
VLAN_ID (aadlinteger 0 .. 4094; connection, bus),
Stream_Handle (aadlinteger; connection), Multicast_Group
(aadlstring; connection), LLDP_Chassis_Id (aadlstring;
device, processor, bus), and LLDP_Port_Id (aadlstring; bus
access feature). The set is registered as a predefined
(non-AS5506) property set so AADL annotations resolve without
explicit `with` imports, mirroring Spar_TSN, Spar_Network,
Spar_Migration, and Spar_Power. Per the v0.10.0 trace-topology
foundation design (docs/designs/v0.10.0-trace-topology.md §3).
status: planned
tags: [trace-topology, track-g, v0100, properties]

- id: REQ-TRACE-TOPOLOGY-002
type: requirement
title: spar-trace-topology crate skeleton
description: >
System shall expose a spar-trace-topology crate carrying the
foundation surface for runtime/declared topology
reconciliation. v0.10.0 ships four modules: `identity` (typed
accessors for the six Spar_Identity properties, typed-first
/ string-fallback per the existing Spar_TSN pattern),
`ingest` (placeholder traits FrameSource / TopologySource /
SwitchConfigSource / PtpTimeSource for the v0.10.x parsers),
`reconcile` (ReconcileFinding enum with the five
deterministic check kinds — IdentityUnknown,
TopologyMissingWiring, ConfigDrift, GptpOutOfBudget,
BinaryMismatch), and `report` (TopologyReport struct
aggregating findings). v0.10.0 ships no real parsing, no
reconciliation logic, no SARIF emission, and no in-toto
attestation; those land in v0.10.x sibling commits and
v0.11.0 / v1.0 respectively per the design doc's
§"Implementation phasing". The in-toto attestation predicate
URL is `https://pulseengine.eu/spar-trace-topology/v1`. Per
docs/designs/v0.10.0-trace-topology.md and the companion
external-integrator contract docs/contracts/spar-trace-topology-v1.md.
status: planned
tags: [trace-topology, track-g, v0100, crate]

# Research findings tracked separately in research/findings.yaml
35 changes: 35 additions & 0 deletions artifacts/verification.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2275,3 +2275,38 @@ artifacts:
target: REQ-MIGRATION-006
- type: satisfies
target: REQ-MIGRATION-008

# ── Track G — v0.10.0 trace-topology foundation ─────────────────────

- id: TEST-TRACE-TOPOLOGY-IDENTITY
type: feature
title: Spar_Identity property surface + spar-trace-topology accessors
description: >
Verifies that the v0.10.0 Spar_Identity property surface and
the spar-trace-topology crate's identity-accessor surface are
registered and reachable. spar-hir-def standard_properties
tests assert that Spar_Identity appears in
STANDARD_PROPERTY_SET_NAMES with the expected six properties
(MAC_Address, VLAN_ID, Stream_Handle, Multicast_Group,
LLDP_Chassis_Id, LLDP_Port_Id), that each property resolves
via GlobalScope without explicit `with` imports, and that the
total predeclared property count is 137 (was 131 before this
commit). spar-trace-topology integration tests
(tests/identity_accessors.rs) verify the six typed accessors
(get_mac_address, get_vlan_id, get_stream_handle,
get_multicast_group, get_lldp_chassis_id, get_lldp_port_id)
read both typed PropertyExpr values and string-fallback
values from a PropertyMap, and that VLAN_ID rejects values
outside 0..=4094 (including the reserved 4095) on both paths.
fields:
method: automated-test
steps:
- run: cargo test -p spar-hir-def --lib -- standard_properties
- run: cargo test -p spar-trace-topology
status: passing
tags: [v0.10.0, trace-topology, track-g, properties]
links:
- type: satisfies
target: REQ-TRACE-TOPOLOGY-001
- type: satisfies
target: REQ-TRACE-TOPOLOGY-002
155 changes: 152 additions & 3 deletions crates/spar-hir-def/src/standard_properties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub const STANDARD_PROPERTY_SET_NAMES: &[&str] = &[
"Spar_Migration",
"Spar_Power",
"Spar_TSN",
"Spar_Identity",
];

// ── Timing_Properties ───────────────────────────────────────────────
Expand Down Expand Up @@ -529,6 +530,51 @@ const SPAR_TSN: &[(&str, &str)] = &[
("Lo_Credit", "aadlinteger units Size_Units"),
];

// ── Spar_Identity ───────────────────────────────────────────────────
//
// Non-standard property set defined by spar itself (not AS5506); used
// for runtime/declared topology reconciliation (v0.10.0 trace-topology
// foundation, Track G). Provides the AADL vocabulary that lets spar
// match an OEM's runtime artefacts (PCAPNG captures, LLDP topology
// snapshots, Qcc YANG configs, gPTP logs) against the AADL declaration
// of "what should be on the wire" so the v0.11.0 reconciliation engine
// can emit the five deterministic checks
// (IdentityUnknown, TopologyMissingWiring, ConfigDrift,
// GptpOutOfBudget, BinaryMismatch) and the v1.0 SARIF + signed
// in-toto attestation artefact.
//
// v0.10.0 ships the property surface only; the parsers and the
// reconciliation engine land in subsequent commits. See
// `docs/designs/v0.10.0-trace-topology.md` (the v1 design doc) and
// `docs/contracts/spar-trace-topology-v1.md` (the external-integrator
// contract).

const SPAR_IDENTITY: &[(&str, &str)] = &[
// L2 MAC address of a device or processor — the canonical identity
// a PCAPNG capture and LLDP snapshot will report. Applies to
// device, processor.
("MAC_Address", "aadlstring"),
// 802.1Q VLAN ID carried by frames on a connection or bus
// (`0..4094` per 802.1Q-2022, with 0 meaning "priority-tagged, no
// VLAN" and 4095 reserved). Applies to connection, bus.
("VLAN_ID", "aadlinteger 0 .. 4094"),
// 802.1Qcc CB stream-handle for a reserved stream. The Qcc YANG
// config declares the same handle; reconciliation matches against
// this value. Applies to connection.
("Stream_Handle", "aadlinteger"),
// L2 destination multicast group MAC — for multicast streams the
// Qcc reservation declares the destination MAC; PCAPNG captures
// observe it. Applies to connection.
("Multicast_Group", "aadlstring"),
// LLDP chassis-id of a device, processor, or bus endpoint as
// reported by the runtime LLDP topology snapshot (typically a MAC
// or interface-name). Applies to device, processor, bus.
("LLDP_Chassis_Id", "aadlstring"),
// LLDP port-id of a bus access feature as reported by the runtime
// LLDP snapshot. Applies to bus access (feature-level).
("LLDP_Port_Id", "aadlstring"),
];

/// Helper: collect properties from a table into the result vector.
fn collect_properties(
table: &[(&'static str, &'static str)],
Expand Down Expand Up @@ -570,6 +616,7 @@ pub fn all_standard_properties() -> Vec<StandardProperty> {
collect_properties(SPAR_MIGRATION, "Spar_Migration", &mut result);
collect_properties(SPAR_POWER, "Spar_Power", &mut result);
collect_properties(SPAR_TSN, "Spar_TSN", &mut result);
collect_properties(SPAR_IDENTITY, "Spar_Identity", &mut result);

result
}
Expand Down Expand Up @@ -601,6 +648,7 @@ fn lookup_table(set_lower: &str) -> Option<&'static [(&'static str, &'static str
"spar_migration" => Some(SPAR_MIGRATION),
"spar_power" => Some(SPAR_POWER),
"spar_tsn" => Some(SPAR_TSN),
"spar_identity" => Some(SPAR_IDENTITY),
_ => None,
}
}
Expand Down Expand Up @@ -651,6 +699,7 @@ mod tests {
assert!(is_standard_property_set("Spar_Migration"));
assert!(is_standard_property_set("Spar_Power"));
assert!(is_standard_property_set("Spar_TSN"));
assert!(is_standard_property_set("Spar_Identity"));

// Case-insensitive
assert!(is_standard_property_set("timing_properties"));
Expand Down Expand Up @@ -1206,6 +1255,103 @@ mod tests {
);
}

#[test]
fn test_standard_properties_in_spar_identity() {
// Spar_Identity is a known property set (v0.10.0 trace-topology
// foundation).
assert!(is_standard_property_set("Spar_Identity"));

let props = standard_properties_in_set("Spar_Identity");
assert_eq!(props.len(), 6);
assert!(props.contains(&"MAC_Address"));
assert!(props.contains(&"VLAN_ID"));
assert!(props.contains(&"Stream_Handle"));
assert!(props.contains(&"Multicast_Group"));
assert!(props.contains(&"LLDP_Chassis_Id"));
assert!(props.contains(&"LLDP_Port_Id"));

// Each property resolves to its expected type.
assert_eq!(
standard_property_type("Spar_Identity", "MAC_Address"),
Some("aadlstring")
);
assert_eq!(
standard_property_type("Spar_Identity", "VLAN_ID"),
Some("aadlinteger 0 .. 4094")
);
assert_eq!(
standard_property_type("Spar_Identity", "Stream_Handle"),
Some("aadlinteger")
);
assert_eq!(
standard_property_type("Spar_Identity", "Multicast_Group"),
Some("aadlstring")
);
assert_eq!(
standard_property_type("Spar_Identity", "LLDP_Chassis_Id"),
Some("aadlstring")
);
assert_eq!(
standard_property_type("Spar_Identity", "LLDP_Port_Id"),
Some("aadlstring")
);

// Deliberately-wrong name returns None.
assert_eq!(standard_property_type("Spar_Identity", "Nonexistent"), None);

// Case-insensitive.
assert_eq!(
standard_property_type("spar_identity", "mac_address"),
Some("aadlstring")
);
assert_eq!(
standard_property_type("SPAR_IDENTITY", "VLAN_ID"),
Some("aadlinteger 0 .. 4094")
);
}

#[test]
fn test_spar_identity_property_set_resolved_via_global_scope() {
use crate::name::Name;
use crate::resolver::{GlobalScope, ResolvedProperty};

let scope = GlobalScope::from_trees(vec![]);

// Each Spar_Identity property is resolvable without explicit `with`.
for prop_name in [
"MAC_Address",
"VLAN_ID",
"Stream_Handle",
"Multicast_Group",
"LLDP_Chassis_Id",
"LLDP_Port_Id",
] {
let result = scope.resolve_property(&Name::new("Spar_Identity"), &Name::new(prop_name));
assert!(
matches!(result, ResolvedProperty::PropertyDef { .. }),
"expected PropertyDef for Spar_Identity::{}, got {:?}",
prop_name,
result
);
}

// Deliberately-wrong name inside a known spar set is Unresolved.
let result = scope.resolve_property(&Name::new("Spar_Identity"), &Name::new("Nonexistent"));
assert!(
matches!(result, ResolvedProperty::Unresolved),
"expected Unresolved for Spar_Identity::Nonexistent, got {:?}",
result
);

// Case-insensitive resolution.
let result = scope.resolve_property(&Name::new("spar_identity"), &Name::new("mac_address"));
assert!(
matches!(result, ResolvedProperty::PropertyDef { .. }),
"expected case-insensitive match for Spar_Identity::MAC_Address, got {:?}",
result
);
}

#[test]
fn test_standard_properties_unknown_set() {
let props = standard_properties_in_set("Nonexistent_Properties");
Expand All @@ -1222,10 +1368,10 @@ mod tests {
#[test]
fn test_all_standard_properties_total_count() {
let all = all_standard_properties();
// 12 + 13 + 14 + 14 + 8 + 25 + 4 + 13 + 5 + 4 + 5 + 4 + 1 + 9 = 131
// 12 + 13 + 14 + 14 + 8 + 25 + 4 + 13 + 5 + 4 + 5 + 4 + 1 + 9 + 6 = 137
// (Timing + Communication + Memory + Deployment + Thread + Programming
// + Modeling + AADL_Project + Spar_Timing + Spar_Trace + Spar_Network
// + Spar_Migration + Spar_Power + Spar_TSN)
// + Spar_Migration + Spar_Power + Spar_TSN + Spar_Identity)
// Thread_Properties: +1 for Locking_Protocol (v0.7.1 PIP/PCP).
// Spar_Timing: +1 for Critical_Section_Blocking (v0.7.1 PIP/PCP).
// Spar_Network: +1 for WCTT_Budget (Track D commit 4).
Expand All @@ -1234,7 +1380,10 @@ mod tests {
// Max_Frame_Size, Frame_Preemption (Track D Phase 2 v0.8.1 c1)
// +1 for Bandwidth_Reservation (Track D Phase 2 v0.8.1 c3, CBS).
// +1 for Sync_Error (v0.9.1 NC soundness, gPTP ε budget).
assert_eq!(all.len(), 131);
// Spar_Identity: +6 for MAC_Address, VLAN_ID, Stream_Handle,
// Multicast_Group, LLDP_Chassis_Id, LLDP_Port_Id (v0.10.0
// trace-topology foundation, Track G).
assert_eq!(all.len(), 137);
}

#[test]
Expand Down
15 changes: 15 additions & 0 deletions crates/spar-trace-topology/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "spar-trace-topology"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
description = "Runtime/declared topology reconciliation for spar (PCAPNG + LLDP + Qcc + AADL)"

[dependencies]
spar-hir-def.workspace = true
spar-base-db.workspace = true
spar-syntax.workspace = true

[dev-dependencies]
tempfile = "3"
Loading
Loading