Source-system model validation¶
Validated with documented limitations.
1. The component¶
rfgen.scene.source_system is the immutable version-1 record boundary for a
source system: named devices and their permitted directed links. It is not a
population generator, event planner, trajectory resolver, or waveform
generator. A topology is the graph of which device can communicate with which
other device. The model records that graph before later components assign
movement, emissions, or samples.
def namespace_hash(namespace: str, identifier: str) -> str: ...
class SourceDevice(BaseModel):
device_id: str
role: Literal["aircraft", "controller", "ground_station", "relay", "unknown"]
pose: GeometryPose | None = None
trajectory_ref: str | None = None
class SourceLink(BaseModel):
link_id: str
source_device: str
target_device: str
direction: Literal["uplink", "downlink", "peer"]
class SourceSystem(BaseModel):
schema_version: Literal[1] = 1
system_id: str
devices: tuple[SourceDevice, ...]
links: tuple[SourceLink, ...]
Field or operation |
Contract |
Purpose |
|---|---|---|
|
NFC (Unicode’s composed text form)-normalized UTF-8 (a text-to-byte encoding) |
Produces a stable 64-character lowercase ID that is separated by namespace. |
ID fields |
Accept only a lowercase 64-hex digest representation; do not recompute a namespace/identifier preimage |
Keep supplied system, device, and link IDs opaque while rejecting malformed representations. |
|
Exactly one of |
A device has either typed static geometry or an opaque reference to later trajectory ownership. |
|
|
States the directed relationship checked by the v1 role predicate. |
|
Frozen schema-version-1 graph |
Rejects duplicate device/link IDs, missing endpoints, self-links, and incompatible roles. |
The following creates the smallest directional system. A pose is a typed static location and orientation; the trajectory reference remains an opaque identifier owned by a later component.
controller = SourceDevice(
device_id=namespace_hash("example", "controller"),
role="controller",
pose=GeometryPose(position_m=(0.0, 0.0, 0.0), orientation_rad=(0.0, 0.0, 0.0)),
)
aircraft = SourceDevice(
device_id=namespace_hash("example", "aircraft"),
role="aircraft",
trajectory_ref="trajectory-reference-example-1",
)
uplink = SourceLink(
link_id=namespace_hash("example", "uplink"),
source_device=controller.device_id,
target_device=aircraft.device_id,
direction="uplink",
)
system = SourceSystem(
system_id=namespace_hash("example", "system"),
devices=(controller, aircraft),
links=(uplink,),
)
assert system.schema_version == 1
2. What we validated¶
This validation establishes four load-bearing claims. Each is restated and supported by evidence in section 3.
Canonical identifiers (§3.1): equivalent Unicode names have one stable ID representation.
Immutable geometry boundary (§3.2): each device has one declared geometry reference.
Directed role predicate (§3.3): every v1 role-pair/direction outcome is enforced.
Closed graph integrity (§3.4): invalid graph references and duplicate IDs fail closed.
Limits and scope-bounded items appear in section 4; full citations are in section 5.
3. Evidence per claim¶
3.1 Canonical identifiers¶
Claim. namespace_hash returns one unambiguous, stable SHA-256 identifier
for equivalent NFC, Unicode’s composed text form, inputs and rejects empty or
NUL-containing components. Separately, the record ID fields accept only the
lowercase 64-hex digest representation; they validate that format without
recomputing a namespace/identifier preimage. NUL is a zero-byte separator.
Evidence.
tests/unit/test_source_system.py::test_namespace_hash_uses_documented_nfc_utf8_separator_formula
compares the result for decomposed cafe\u0301 with the independently computed
SHA-256 digest of NFC café, a NUL separator, and device. UTF-8, the
text-to-byte encoding used before hashing, gives that byte sequence a defined
representation. The same test asserts equality for the composed and
decomposed spellings. Its companion parameterized test rejects an empty
namespace, an empty identifier, and a NUL inside a component. SHA-256 is the
Secure Hash Algorithm 256-bit digest defined by FIPS 180-4 [1].
SourceDevice, SourceLink, and SourceSystem each validate supplied IDs as
exactly 64 lowercase hexadecimal characters. That representation check is
deliberately distinct from calling namespace_hash: it rejects malformed IDs
but treats a well-formed supplied digest as opaque. The closed-vocabulary unit
test rejects a raw device ID, while
test_id_fields_accept_opaque_canonical_digests_without_preimage_provenance
directly proves that unrelated well-formed digests are accepted without an
available namespace/identifier preimage.
3.2 Immutable geometry boundary¶
Claim. A source device stores exactly one geometry reference and cannot be mutated after validation.
Evidence.
tests/unit/test_source_system.py::test_source_system_serializes_v1_immutable_graph_with_static_and_opaque_geometry
constructs one device with a GeometryPose and one with a nonblank opaque
trajectory reference,
then verifies the complete JSON-compatible schema-version-1 serialization.
The test also attempts to replace a frozen model field and receives Pydantic’s
validation error. test_device_requires_one_unresolved_geometry_reference
rejects both the zero-reference and two-reference cases. This is a record
boundary: the opaque reference has no required URI syntax and does not resolve
a trajectory; the typed pose does not claim time-varying movement.
3.3 Directed role predicate¶
Claim. The v1 role predicate accepts all 25 peer role pairs, exactly one
uplink pair (controller to aircraft), and exactly one downlink pair
(aircraft to controller), while rejecting the other 48 of 75 outcomes.
Evidence.
tests/validation/source_system/test_link_predicate_outcomes.py::test_link_predicate_has_the_documented_outcome_for_every_role_pair_and_direction
is parameterized over the five roles, five target roles, and three directions:
5 × 5 × 3 = 75 independently constructed SourceSystem graphs. It accepts
27 graphs and asserts the structured source_link_invalid error, including
the specific link ID, for the other 48. The companion cardinality test fixes
the matrix at 75 outcomes, 27 accepted and 48 rejected. This test checks the
public graph constructor rather than calling the private predicate directly.
3.4 Closed graph integrity¶
Claim. A system cannot contain duplicate identities, a self-link, or a link whose endpoint is not a member of that system.
Evidence.
tests/unit/test_source_system.py::test_invalid_link_predicates_raise_structured_validation_error
checks a self-link, a missing endpoint, and two reversed directional pairs;
each returns the machine-readable source_link_invalid code and offending
link ID. test_duplicate_device_and_link_ids_are_rejected_without_population_behaviour
checks duplicate device and link IDs, including the separate
source_device_invalid code for duplicate devices. These are identity and
topology constraints only; they do not establish physical radio propagation.
4. Limits and what is not validated¶
The role predicate is policy, not a radio-protocol model. It records the deliberately narrow version-1 controller/aircraft direction convention and accepts all
peerpairs; it does not establish that a protocol, spectrum allocation, antenna, or physical link is feasible.SHA-256 namespace hashing is deterministic, not a global identity proof. It prevents ambiguity within the stated canonicalization rule but does not authenticate an origin, prevent a caller from reusing an identifier, or resolve entity identity across external registries.
A model-accepted digest does not prove its unseen preimage. The ID fields establish only the canonical lowercase 64-hex format. They cannot establish which namespace and identifier produced a supplied digest, whether it was produced by
namespace_hash, or whether that unseen pair was authorized.Geometry is descriptive only. Static poses and opaque trajectory references are validated for shape and exclusivity, not for coordinate-frame correctness, existence, kinematics, or temporal consistency.
The test matrix covers version-1 roles only. A future role or direction requires an explicit predicate and a revised exhaustive matrix; no outcome is inferred for values outside this closed vocabulary.
No signal-generation behavior is validated here. Population ownership, event planning, trajectory resolution, propagation, and waveform generation are outside this component boundary.
5. References¶
National Institute of Standards and Technology, Secure Hash Standard (SHS), FIPS PUB 180-4, August 2015, FIPS 180-4 publication page. Defines SHA-256, the digest used by
namespace_hash.Pydantic, PyPI distribution
pydantic, installed version 2.13.3. Pydantic documentation. Supplies the frozen schema models and model-validation behavior exercised by the tests.pytest, PyPI distribution
pytest, installed version 9.0.2. pytest documentation. Executes the 75-case exhaustive role-pair/direction validation and the unit tests.