Skip to content

Make SignalKind own one canonical stable domain identity #481

Description

@forkwright

Finding

stoicheion::SignalKind is the canonical seven-domain signal type, but Semaino independently defines its domain identity three times:

  1. alert.rs maps each variant to a numeric fingerprint discriminant.
  2. convergence.rs repeats that numeric mapping and separately fixes storage at eight slots.
  3. aggregator.rs defines a parallel KindKey enum for baseline ownership.

The first two copies assign known domains 0..=6 and every future variant 255. The aggregator instead folds Osint and every future variant into KindKey::Osint. Because SignalKind is #[non_exhaustive], adding a new domain does not force any of these downstream mappings to change.

Verified against main c8e671845adca3e5b57a205d08aca31eadbc6000.

Evidence

  • crates/stoicheion/src/signal.rs:237-255 defines the authoritative SignalKind variants: RF, mesh, network, proximity, GPS, environmental, and OSINT.
  • crates/semaino/src/alert.rs:314-326 defines private kind_discriminant: known variants map to 0..=6; the wildcard maps to 255. That value participates in AlertFingerprint, so it controls alert suppression identity.
  • crates/semaino/src/convergence.rs:92-108 declares DOMAIN_SLOTS = 8, reserving indices 0..=6 plus one shared future-variant slot. convergence.rs:295-310 repeats the alert mapping independently.
  • crates/semaino/src/aggregator.rs:52-82 defines a separate seven-variant KindKey. Its wildcard maps both current Osint and every future SignalKind variant to Osint.
  • No current open issue owns the domain-identity fork. Bound and scope the convergence grid: per-cell hits and per-signal detection grow unbounded under signal flood #223 bounded convergence storage; security(signals): enforce typed sensitivity and minimized evidence at the canonical pipeline #406 concerns signal sensitivity, not domain enumeration ownership.

Why this matters

These identities govern three different semantics: which signals share a temporal baseline, which domains count as distinct convergence evidence, and which alerts suppress one another. A newly added domain can therefore:

  • contaminate the OSINT baseline;
  • collide with every other future domain in convergence;
  • collide with every other future domain in alert suppression; and
  • acquire different identities in different subsystems without a compiler failure.

This is not a trust-boundary mirror. All three consumers are interpreting the same in-process SignalKind fact.

Desired correction

Make the defining crate own one domain identity. A suitable shape is a SignalDomain enum plus SignalKind::domain() implemented inside stoicheion, where adding a SignalKind variant makes an exhaustive match fail to compile until its domain is assigned. If a stable numeric code is required for fingerprints or storage, make that a method of SignalDomain rather than another matcher.

Have Semaino baseline keys, convergence slots, and alert fingerprints consume that type/code. Derive bounded storage cardinality from the canonical domain set or use a keyed structure that does not require a second maintained slot count.

Done when:

  • one implementation maps SignalKind to domain identity;
  • adding a new SignalKind cannot compile until its domain identity is decided;
  • future domains cannot silently alias OSINT or a shared sentinel;
  • alert, convergence, and baseline tests use one shared domain table; and
  • no private seven-way SignalKind domain matcher remains in Semaino.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions