ebusgateway

package module
v0.6.32 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 25, 2026 License: AGPL-3.0 Imports: 42 Imported by: 0

README

helianthus-ebusgateway

helianthus-ebusgateway is the runtime/API edge for Helianthus eBUS systems. It connects to an eBUS transport and exposes GraphQL, subscriptions, MCP, optional UI surfaces (/ui, /portal), and optional mDNS advertisement.

Purpose and Scope

What belongs in this repository
  • Gateway runtime assembly (gateway.go, cmd/gateway).
  • GraphQL query/mutation/subscription surfaces (graphql/).
  • MCP JSON-RPC tool surface (mcp/).
  • Optional UI mount and mDNS advertisement (ui/, mdns/).
  • Hardware-backed smoke entrypoint and unknown-device dump plumbing (cmd/smoke, smoke*.go, register_dump*.go).
What does not belong in this repository
  • Low-level transport framing and bus primitives (use helianthus-ebusgo).
  • Registry/provider model definitions and plane/projection semantics (use helianthus-ebusreg).
  • Platform deployment bundles or auth/TLS edge policy management (handled by deployment infrastructure).

Status and Maturity

  • Active, CI-validated gateway service with race-enabled tests.
  • Suitable for onboarding and issue-focused runtime/API changes.
  • Smoke mode is intentionally opt-in and environment-backed (EBUS_SMOKE=1 + local config file).

Stable Instance Identity

  • The gateway can expose an installation-scoped stable GUID with -instance-guid <uuid>.
  • When configured, the same GUID is published through GraphQL at gatewayIdentity.instanceGuid.
  • Zeroconf advertisement keeps _helianthus-graphql._tcp and adds TXT instance_guid=<uuid>.
  • Home Assistant should treat this GUID as canonical identity and treat host, port, path, and transport as rediscoverable transport coordinates.

Helianthus Dependency Chain

helianthus-ebusgo  ->  helianthus-ebusreg  ->  helianthus-ebusgateway  ->  operators/automation clients
 (transport/proto)     (registry/schema)        (runtime/API)

Quickstart (copy/paste)

0) Prerequisite: private module access (outside CI)
# Align local module settings with CI for private dependencies.
export GOPRIVATE='github.com/d3vi1/*'
export GONOSUMDB='github.com/d3vi1/*'
export GOPROXY=direct

# Use a GitHub token with read access to private repos.
export GH_TOKEN='<your_github_token>'

# CI uses a tokenized Git URL rewrite; keep local onboarding non-persistent.
export GIT_CONFIG_COUNT=1
export GIT_CONFIG_KEY_0="url.https://x-access-token:${GH_TOKEN}@github.com/.insteadOf"
export GIT_CONFIG_VALUE_0="https://github.com/"

After local checks, clear auth-related shell variables: unset GIT_CONFIG_COUNT GIT_CONFIG_KEY_0 GIT_CONFIG_VALUE_0 GH_TOKEN

1) Clone and baseline validation
git clone https://github.com/d3vi1/helianthus-ebusgateway.git
cd helianthus-ebusgateway
./scripts/ci_local.sh
go test ./...
go vet ./...
go build ./...
go test -race -count=1 ./...
2) Inspect runtime flags locally
go run ./cmd/gateway -h
3) Run gateway against a local ENH endpoint
go run ./cmd/gateway \
  -transport enh \
  -network unix \
  -address /var/run/ebusd/ebusd.socket \
  -http-addr :8080
4) Probe GraphQL and MCP surfaces
curl -fsS http://127.0.0.1:8080/graphql \
  -H 'content-type: application/json' \
  --data '{"query":"{ __typename }"}'

curl -fsS http://127.0.0.1:8080/graphql \
  -H 'content-type: application/json' \
  --data '{"query":"{ gatewayIdentity { instanceGuid } }"}'

curl -fsS http://127.0.0.1:8080/mcp \
  -H 'content-type: application/json' \
  --data '{"jsonrpc":"2.0","id":"ready","method":"ping","params":{}}'

Portal API probes and operational notes live in docs: https://github.com/d3vi1/helianthus-docs-ebus/blob/main/api/portal.md

Local Smoke-Test Configuration Example

Smoke mode reads YAML blocks from repo-root AGENT-local.md. Minimal example:

enh:
  type: unix
  path: /var/run/ebusd/ebusd.socket
  timeout_sec: 10

expected_devices:
  - address: 0x08
    description: "boiler"
    manufacturer: "Vaillant"
    device_id: "BAI00"
    sw_version: ""
    hw_version: ""

smoke:
  profile: enh
  source_address: 0x10
  scan_timeout_sec: 5
  method_timeout_sec: 10
  report_json_output: artifacts/smoke-report.json

Run smoke:

EBUS_SMOKE=1 go run ./cmd/smoke

Notes:

  • cmd/smoke fails fast when AGENT-local.md is missing or invalid.
  • Smoke checks are read-only and write a JSON report (artifacts/smoke-report.json by default).

Transport Endpoint Examples

Protocol can be inferred from endpoint URI in -address:

go run ./cmd/gateway -address enh://127.0.0.1:19001 -http-addr :8080
go run ./cmd/gateway -address ens://127.0.0.1:19002 -http-addr :8080
go run ./cmd/gateway -address ebusd-tcp://127.0.0.1:9999 -http-addr :8080
go run ./cmd/gateway -address udp-plain://203.0.113.10:9999 -http-addr :8080
go run ./cmd/gateway -address tcp-plain://203.0.113.10:9999 -http-addr :8080

Gateway Flag Cheat Sheet

Flag Default Notes
-transport enh enh, ens, ebusd-tcp, udp-plain, tcp-plain
-network unix unix, tcp, or udp
-address /var/run/ebusd/ebusd.socket socket path, host:port, or endpoint URI
-http-addr :8080 empty disables HTTP server
-graphql-path /graphql query/mutation endpoint
-subscription-path /graphql/subscriptions WebSocket/SSE subscriptions
-snapshot-path /snapshot projection snapshot endpoint
-mcp-path /mcp MCP JSON-RPC endpoint
-ui-path /ui set empty to disable UI
-portal-path /portal set empty to disable dynamic portal surface
-instance-guid empty lowercase UUIDv4 published via GraphQL and Zeroconf
-mdns true set false outside trusted LAN
-dump-upload-path disabled unknown-device dump upload endpoint path

Validation Commands

Area Command
terminology gate (CI parity) `if git grep -nIwiE 'm[a]ster
compile go build ./...
vet go vet ./...
tests (CI parity) go test -race -count=1 ./...
smoke tests (unit/integration) go test ./... -run Smoke -count=1
gateway flags smoke-check go run ./cmd/gateway -h
smoke entrypoint smoke-check EBUS_SMOKE=0 go run ./cmd/smoke
ebusd helper smoke-check go run ./cmd/ebusdscan -h
Repositories and docs
Issue workflow conventions
  • Use one issue-focused branch (example: issue-93-readme-refresh).
  • Keep PR scope aligned to issue acceptance criteria.
  • Include closing keyword in PR body (example: Fixes #93).
  • Request agent review comment after opening the PR (@codex review).

Documentation

Index

Constants

View Source
const (
	StartupAdmissionRejoinBackoffBaseSeconds = 5
	StartupAdmissionRejoinBackoffMaxSeconds  = 60

	StartupAdmissionEscalationFailureCountThreshold = 5
	StartupAdmissionEscalationDurationSeconds       = 300
	StartupAdmissionEscalationWindowSeconds         = 900
)
View Source
const (
	TransportENH           TransportProtocol = "enh"
	TransportENS           TransportProtocol = "ens"
	TransportUDPPlain      TransportProtocol = "udp-plain"
	TransportTCPPlain      TransportProtocol = "tcp-plain"
	TransportEbusdTCP      TransportProtocol = "ebusd-tcp"
	TransportAdapterDirect TransportProtocol = "adapter-direct"

	DefaultSemanticZonePresenceMissThreshold = 3
	DefaultSemanticZonePresenceHitThreshold  = 2
	DefaultSemanticDHWStaleTTL               = 15 * time.Minute
	DefaultSemanticEnergyInterval            = 5 * time.Minute
	DefaultSemanticRegulatorRecheckInterval  = 60 * time.Second
	DefaultSemanticRegulatorAbsenceGrace     = 5 * time.Minute

	DefaultMetricsPath                             = "/metrics"
	DefaultObserveFirstRecentMessageCapacity       = 1024
	DefaultObserveFirstPeriodicityCapacity         = 256
	DefaultObserveFirstPeriodicityStaleTTL         = time.Hour
	DefaultObserveFirstSeriesBudget                = 1024
	DefaultObserveFirstWarmupConnectedWindow       = 30 * time.Second
	DefaultObserveFirstWarmupCompletedTransactions = 20
	DefaultObserveFirstWarmupPostResetWindow       = 5 * time.Second
	DefaultObserveFirstWarmupPostResetTransactions = 10
	DefaultObserveFirstWarmupOuterWindow           = 5 * time.Minute

	// Default values for startup-admission escalation thresholds (frozen per
	// AD17 in startup-admission-discovery-w17-26.locked):
	//
	// The continuous_threshold_s value is frozen at 5 minutes (not
	// operator-tunable). The state_min_stability_s is operator-tunable but
	// bounded by AD22 invariant: state_min_stability_s * 5 <= continuous_threshold_s.
	StartupAdmissionContinuousThresholdSeconds      = 300
	StartupAdmissionStateMinStabilitySecondsDefault = 30
	StartupAdmissionStateMinStabilitySecondsMin     = 5
	StartupAdmissionStateMinStabilitySecondsMax     = 60
)
View Source
const (
	DefaultSemanticReadFailureBudget      = 2
	DefaultSemanticReadOpenCooldown       = 15 * time.Second
	DefaultSemanticReadHalfOpenProbeLimit = 1
)
View Source
const (
	DefaultShadowCacheCapacity                 = 4096
	DefaultShadowCachePinnedCapacity           = 2048
	DefaultShadowCacheWriteConfirmPinnedCap    = 256
	DefaultShadowCacheTombstoneRetainWindow    = 15 * time.Minute
	DefaultShadowCacheTombstoneHardLifespan    = 24 * time.Hour
	DefaultShadowCacheCompactorCadence         = time.Minute
	DefaultShadowCacheCompactorBatchSize       = 64
	DefaultShadowCacheShutdownCompactorTimeout = time.Second
)
View Source
const (
	PassiveACKCorrelatorM2A = protocol.ACKCorrelatorM2A
)
View Source
const (
	PassiveACKPositionRequestACK = protocol.ACKPositionRequestACK
)

Variables

View Source
var (
	ErrSemanticReadCircuitOpen           = errors.New("semantic read circuit breaker open")
	ErrSemanticReadSupersededInFlight    = errors.New("semantic read superseded while in flight")
	ErrSemanticReadRevalidationExhausted = errors.New("semantic read shadow revalidation exhausted")
)
View Source
var ErrSourceSelectionBusInquiryUnsupported = errors.New("source address selection: inquiry not supported (InquiryEnabled=false)")
View Source
var ErrStartupAdmissionStabilityInvariant = errors.New("startup admission: state_min_stability_s * 5 must be <= continuous_threshold_s (AD22)")

ErrStartupAdmissionStabilityInvariant is returned when state_min_stability_s violates the AD22 5:1 invariant against continuous_threshold_s.

View Source
var VaillantBaselineTopologySeed = []byte{0x08, 0x15, 0x26, 0x04, 0xF6, 0xEC}

BaselineTopologySeed is the set of addresses protected from LRU eviction. Vaillant default: {0x08 BAI00, 0x15 BASV2, 0x26 VR_71, 0x04 NETX3-A, 0xF6 NETX3-B, 0xEC SOL00}. Operators MAY override via config; validation enforces responder-range [0x03, 0xFE] excluding 0xAA (SYN) and 0xFE (broadcast).

View Source
var VaillantStructuralStartupProbeTargets = []byte{0x08, 0x15, 0x26}

VaillantStructuralStartupProbeTargets is the bounded set of Vaillant device targets used as the structural startup-probe fallback when the source-address selector's passive warmup observed no probable targets.

Why this is narrower than VaillantBaselineTopologySeed: stealth slaves (0x04/0xF6 NETX3, 0xEC SOL00) are passive-only and do not respond to directed identity probes. The structural set is the active-probable subset {boiler, regulator, primary controller}.

These are TARGETS for active probes from the admitted source — never source addresses themselves.

Functions

func ChainBusObservers

func ChainBusObservers(observers ...protocol.BusObserver) protocol.BusObserver

func CheckExplicitSourceCompanionConflict added in v0.6.18

func CheckExplicitSourceCompanionConflict(explicitSource byte, selection *protocol.SourceAddressSelection, metrics *StartupSourceSelectionMetrics) bool

CheckExplicitSourceCompanionConflict compares the configured explicit source against the selector's selected source and emits advisory conflict observability when they disagree.

func DefaultStartupAdmissionSourceSelectionConfig added in v0.6.18

func DefaultStartupAdmissionSourceSelectionConfig() protocol.SourceAddressSelectionConfig

DefaultStartupAdmissionSourceSelectionConfig returns the SourceAddressSelectionConfig used by the startup-admission-discovery plan for source-selection-capable direct transports. See plan AD01/AD02/AD09 and helianthus-docs-ebus/architecture/startup-admission-and-discovery.md §2.2.3.

func EmitStartupResetWarn added in v0.6.18

func EmitStartupResetWarn(logger func(format string, args ...interface{}))

EmitStartupResetWarn is a package-level log helper for the AD17 restart-reset WARN line emitted once on process start. Callers use this immediately after NewStartupSourceSelectionMetrics to satisfy AD17's observability contract.

func EntryContainsAddress added in v0.6.18

func EntryContainsAddress(entry registry.DeviceEntry, addr byte) bool

EntryContainsAddress reports whether the entry's full address set (including aliases) contains addr. Use this for membership/lookup checks where any face on the entry should match — e.g. register dump target resolution and registry containment filters in passive discovery — instead of comparing only PrimaryDisplayAddress, which would miss the non-display side of an aliased canonical pair.

Phase C M-C6b helper.

func FormatStartupSourceSelectionExplicitLog added in v0.6.18

func FormatStartupSourceSelectionExplicitLog(source byte) string

FormatStartupSourceSelectionExplicitLog returns the low-confidence explicit-validate-only log line emitted before the first active frame.

func IsB524ResponseCoherent added in v0.6.18

func IsB524ResponseCoherent(responseData []byte, group byte, addr uint16) bool

IsB524ResponseCoherent reports whether a B524 (Vaillant extended- register access) response payload is structurally coherent with the original request: the response echoes the request's group and register address in valid positions.

Two valid echo positions are accepted because B524 replies use two frame layouts depending on whether the opcode is preceded by a status / length prefix:

layout-a  resp[1]=group, resp[2..3]=addr (LE)
layout-b  resp[2]=group, resp[3..4]=addr (LE)

A response that fails both checks is not a coherent reply — it may be a NACK, a fragment, or a passively-misclassified frame.

Used by:

  • cmd/gateway/semantic_vaillant.go isB524ProbeCoherent (the active-probe acceptance check during discoverB524Root)
  • bus_observability_store.go passiveResponseIsCoherentVaillantEvidence (the passive strong-evidence promotion gate)

Both call sites share a single source of truth for "what counts as a coherent B524 response" so that passive-evidence promotion uses the same acceptance criterion as active discovery.

func NewRegisterDumpUploadHandler

func NewRegisterDumpUploadHandler(outputDir string) http.Handler

func NewSourceSelectionBusAdapter added in v0.6.18

func NewSourceSelectionBusAdapter(reconstructor *PassiveTransactionReconstructor, name string, inquiryEnabled bool) (protocol.SourceAddressSelectionBus, error)

NewSourceSelectionBusAdapter returns a protocol.SourceAddressSelectionBus subscribed to the given reconstructor with priority=NonCritical and default buffer.

func PassiveTransportSupported

func PassiveTransportSupported(cfg Config) bool

func RejoinBackoffSchedule added in v0.6.18

func RejoinBackoffSchedule(attempt int, baseSeconds, maxSeconds int) time.Duration

RejoinBackoffSchedule returns the nth attempt's backoff duration: Base * 2^(n-1), capped at Max. attempt=1 → Base; attempt=2 → 2*Base; etc.

func RunSmoke

func RunSmoke(ctx context.Context, cfg smokeConfig, opts SmokeOptions) (runErr error)

func RunSmokeFromEnv

func RunSmokeFromEnv(ctx context.Context, opts SmokeOptions) error

func ScanWithFullRangeGuard added in v0.6.18

func ScanWithFullRangeGuard(ctx context.Context, bus registry.ScanBus, reg *registry.DeviceRegistry, source byte, targets []byte, admissionPath TransportAdmissionPath, diagnosticFlag bool, evidenceHasVaillantRoot bool) ([]registry.DeviceEntry, error)

ScanWithFullRangeGuard applies the AD05 full-range retry guard before dispatching to the ebusreg scan implementation.

func SelectDefaultStartupSourceAddress added in v0.6.18

func SelectDefaultStartupSourceAddress(ctx context.Context) (protocol.SourceAddressSelection, error)

SelectDefaultStartupSourceAddress applies the docs-backed Helianthus source-selection policy without passive observations. This covers transports that cannot expose an observe-first lane: "auto" still means source selection, not source 0x00.

func SnapshotContainsAddress added in v0.6.18

func SnapshotContainsAddress(snap registry.DeviceEntrySnapshot, addr byte) bool

SnapshotContainsAddress is the value-typed counterpart of EntryContainsAddress for callers that have already taken a DeviceEntrySnapshot via LookupEntrySnapshot / IterateSnapshots.

P9.2 — race-free address-membership check. Pre-P9.2 callers had to take an EntryContainsAddress(entry, addr) on a live *deviceEntry pointer (entry.Addresses() reads through to mutable storage); this helper reads the snapshot's Addresses slice (already a value-typed copy taken under the registry's RLock).

func SnapshotTargetAddressForRouting added in v0.6.18

func SnapshotTargetAddressForRouting(snap registry.DeviceEntrySnapshot) byte

SnapshotTargetAddressForRouting is the value-typed counterpart of TargetAddressForRouting for callers iterating value-typed DeviceEntrySnapshot via IterateSnapshots / LookupEntrySnapshot.

P9.3 — closes the lock-free read race surface for B524 root candidate enumeration in the semantic Vaillant poller. Reads from the snapshot's Faces slice (already a value-typed copy taken under the registry's RLock) and falls back to the snapshot's PrimaryDisplayAddress.

func StartupAdmissionConfigWithHint added in v0.6.18

func StartupAdmissionConfigWithHint(hint byte, hintSet bool) protocol.SourceAddressSelectionConfig

StartupAdmissionConfigWithHint returns the default startup-admission selector config augmented with a hint candidate, when a hint is available. When hintSet is false the returned config is identical to DefaultStartupAdmissionSourceSelectionConfig (no hint biasing applied).

The hint is a HISTORICAL signal from a prior admission cycle (loaded from runtime_state.ebus.self.last_admitted_source per runtime-state-w19-26.locked M4_SOURCE_SELECTION_HINT). It biases candidate ordering so the cached source is tried first; the selector still validates the candidate against the live bus (AD24 — cache never bypasses warmup).

func StartupSourceSelectionExpvarNames added in v0.6.18

func StartupSourceSelectionExpvarNames() []string

func TargetAddressForRouting added in v0.6.18

func TargetAddressForRouting(entry registry.DeviceEntry) byte

TargetAddressForRouting returns the routing-correct target byte for an M2S write. Prefers AddressByRole(SlotRoleSlave) so an aliased canonical pair (e.g. BAI 0x03↔0x08) returns the target byte 0x08 rather than the alias-primary which may be the initiator. Falls back to PrimaryDisplayAddress only when no target-role face exists (single-initiator device, or a face with SlotRoleUnknown that the AddressClass-fallback in registry.AddressByRole cannot classify).

Phase C M-C6b helper. M-C7 will replace these per-callsite usages with explicit Frame.FrameType + Frame.Validate flow at the semantic API boundary.

func ValidateBaselineTopologySeed added in v0.6.18

func ValidateBaselineTopologySeed(seed []byte) error

ValidateBaselineTopologySeed checks the config-provided seed against the responder-address range.

func ValidateSourceSelectionMode added in v0.6.18

func ValidateSourceSelectionMode(v string) error

ValidateSourceSelectionMode returns nil if v is one of the four enum values the plan admits per AD23. Returns a FATAL-level error otherwise — callers should refuse to emit artifacts with an out-of- range value.

func ValidateStartupAdmissionStability added in v0.6.18

func ValidateStartupAdmissionStability(stateMinStabilitySeconds int) error

ValidateStartupAdmissionStability enforces AD22.

Types

type ActivePassiveDeduplicator

type ActivePassiveDeduplicator struct {
	// contains filtered or unexported fields
}

func NewActivePassiveDeduplicator

func NewActivePassiveDeduplicator(cfg Config) (*ActivePassiveDeduplicator, error)

func (*ActivePassiveDeduplicator) AttachReconstructor

func (deduplicator *ActivePassiveDeduplicator) AttachReconstructor(ctx context.Context, reconstructor *PassiveTransactionReconstructor) error

func (*ActivePassiveDeduplicator) Budgets

func (deduplicator *ActivePassiveDeduplicator) Budgets() dedupTimingBudgets

func (*ActivePassiveDeduplicator) Close

func (deduplicator *ActivePassiveDeduplicator) Close() error

func (*ActivePassiveDeduplicator) LocalAddressSnapshot

func (deduplicator *ActivePassiveDeduplicator) LocalAddressSnapshot() LocalAddressSnapshot

func (*ActivePassiveDeduplicator) Observe

func (deduplicator *ActivePassiveDeduplicator) Observe(key WatchKey) WatchObservation

func (*ActivePassiveDeduplicator) OnBusEvent

func (deduplicator *ActivePassiveDeduplicator) OnBusEvent(event protocol.BusEvent) error

func (*ActivePassiveDeduplicator) OnPassiveClassifiedEvent

func (deduplicator *ActivePassiveDeduplicator) OnPassiveClassifiedEvent(event PassiveClassifiedEvent)

func (*ActivePassiveDeduplicator) SetWatchObserver added in v0.6.32

func (deduplicator *ActivePassiveDeduplicator) SetWatchObserver(observer WatchObserver)

func (*ActivePassiveDeduplicator) Subscribe

func (deduplicator *ActivePassiveDeduplicator) Subscribe(name string, priority DedupSubscriberPriority, buffer int) (*AdjudicatedPassiveSubscription, error)

type ActiveTransactionFingerprint

type ActiveTransactionFingerprint struct {
	Epoch            uint64
	TransactionClass DedupTransactionClass
	OutcomeClass     DedupOutcomeClass
	ResponseClass    DedupResponseClass
	FamilyPolicy     ObserveFirstFamilyPolicy
	SharedWatchKey   WatchKey
	Source           byte
	Target           byte
	RequestBytes     []byte
	ResponseBytes    []byte
	Hash             [32]byte
	ObservedAt       time.Time
}

type AdaptermuxDiagSnapshot added in v0.6.18

type AdaptermuxDiagSnapshot struct {
	// SynSuppressedPreEcho mirrors activeTxnDiag.synSuppressedPreEcho
	// — total SYNs the existing P10.2 / pre-first-echo gate has
	// suppressed. Provided for context alongside the new batch-21
	// counters so an operator reading /metrics can compare the
	// suppressed population to the gap populations below.
	SynSuppressedPreEcho uint64
	// SynSeenDuringGrantWindow counts SYNs observed during gateway
	// ownership where gatewayTxnActive=false (Attack 1 — grant→first-
	// write window).
	SynSeenDuringGrantWindow uint64
	// SynSeenWhileInterWriteEmpty counts SYNs observed during a
	// gateway-owned active txn where the echo queue is empty AND at
	// least one byte has been delivered to active (Attack 3 — inter-
	// write queue-empty window).
	SynSeenWhileInterWriteEmpty uint64
	// SynSeenAfterTransportWindowExpired counts SYNs observed during
	// gateway ownership where the upstream ENH transport's
	// postGrantPreEcho window has closed via deadline-expiry in the
	// current transaction (Attack 2 — batch-22 round-3). Forensic
	// only; correlates with the residual pre_echo_syn rate
	// unexplained by Attack 1 + Attack 3 instrumentation.
	SynSeenAfterTransportWindowExpired uint64
	// SynSuppressedBetweenWrites counts SYNs the betweenWritesSyn
	// (queueJustDrained) gate suppressed — the Attack 3 (inter-write
	// empty-queue) closure introduced in batch-26 round-7. Subset of
	// SynSuppressedPreEcho. Operator dashboards: rate(SynSuppressedBetweenWrites)
	// should rise after round-7 deploy while
	// ebus_active_echo_mismatch_subclass_total{subclass="pre_echo_syn_raw"}
	// drops.
	SynSuppressedBetweenWrites uint64
}

AdaptermuxDiagSnapshot is the subset of adaptermux's activeTxnDiag snapshot exposed via the Prometheus surface for batch-21 forensic instrumentation. Field semantics match the originating counters in `internal/adaptermux/diag.go`. Plumbing through this small struct (rather than importing the full ActiveTxnSnapshot) keeps `bus_observability_store` decoupled from the adaptermux package.

type AddressObservation added in v0.6.18

type AddressObservation struct {
	PositiveACKCount int
}

type AddressSlot added in v0.6.18

type AddressSlot struct {
	Addr              byte
	Role              string
	DiscoverySource   string
	VerificationState string
	// PriorityTier is the eBUS standard priority class (p0..p4) when
	// Addr is a canonical source from sourceAddressTableV1, else "".
	PriorityTier protocol.SourceAddressPriorityIndex
	// FreeUse is true when Addr is a canonical source AND the eBUS
	// standard table marks the row free-use (e.g. 0x07, 0x17, 0x7F,
	// 0x1F/0x3F/0x7F/0xF7). False for non-canonical addresses or
	// preallocated canonical sources.
	FreeUse      bool
	RegistrySlot *registry.AddressSlot
}

type AddressTable added in v0.6.18

type AddressTable struct {
	// contains filtered or unexported fields
}

func NewAddressTable added in v0.6.18

func NewAddressTable(regs ...*registry.DeviceRegistry) *AddressTable

func (*AddressTable) CanonicalAddressTableSnapshot added in v0.6.18

func (t *AddressTable) CanonicalAddressTableSnapshot() []CanonicalAddressView

CanonicalAddressTableSnapshot returns a snapshot of all 50 canonical eBUS addresses (25 sources + 25 companions per architecture/ebus_standard/12-source-address-table.md), with each row labelled by its current runtime observation state. Addresses present in the wrapped registry are marked Observed=true with their actual DiscoverySource; addresses not yet observed are marked Observed=false with DiscoverySource="never_observed".

func (*AddressTable) Lookup added in v0.6.18

func (t *AddressTable) Lookup(addr byte) (*AddressSlot, bool)

type AddressTableInserter added in v0.6.18

type AddressTableInserter struct {
	// contains filtered or unexported fields
}

func NewAddressTableInserter added in v0.6.18

func NewAddressTableInserter(table *AddressTable, cfg Config) *AddressTableInserter

func (*AddressTableInserter) BackfillUnidentifiedAddresses added in v0.6.18

func (i *AddressTableInserter) BackfillUnidentifiedAddresses()

BackfillUnidentifiedAddresses iterates the registry's existing entries and invokes the wired enrichmentIdentityProbeFn for any address that looks unidentified (manufacturer empty OR Vaillant + serial empty). Used to close the race where a passive observation during the gateway's startup-admission window lands in the registry before SetEnrichmentIdentityProbeFn was wired.

Callers MUST defer the invocation until the gateway's startup barrier (admittedSource finalization) has closed — otherwise the probe submissions will race the startup directed scan and emit bus traffic during the admission validation window. See P5 round-3 finding (Codex P2 on PR #583, 2026-05-08).

The probe fn is idempotent (per-address sync.Map in EnqueueAddressIdentityProbe), so repeat calls are harmless.

func (*AddressTableInserter) OnPassiveClassifiedEvent added in v0.6.18

func (i *AddressTableInserter) OnPassiveClassifiedEvent(event PassiveClassifiedEvent)

func (*AddressTableInserter) SetEnrichmentIdentityProbeFn added in v0.6.18

func (i *AddressTableInserter) SetEnrichmentIdentityProbeFn(fn func(addr byte))

SetEnrichmentIdentityProbeFn wires the post-insertion per-address identity probe so new passive slots get a 0x07/0x04 + B5.09 ScanID read against them. See AddressTableInserter.enrichmentIdentityProbeFn.

Phase post-C P5 (live validation 2026-05-08): without this hook passive-observed entries (e.g. NETX3 0xF1↔0xF6) stay at empty manufacturer / deviceID / serialNumber forever, which prevents identity-merge from grouping aliased faces.

This setter is fire-only: it installs the fn pointer and returns without invoking it. Callers that need to backfill addresses inserted before the hook was wired should call BackfillUnidentifiedAddresses AFTER the gateway's startup barrier (semantic scheduler readiness) clears — see main.go for the wiring.

func (*AddressTableInserter) SetEnrichmentRefreshFn added in v0.6.18

func (i *AddressTableInserter) SetEnrichmentRefreshFn(fn func())

SetEnrichmentRefreshFn wires the post-insertion B524 root re-discovery trigger so the regulator surface populates without a gateway restart. See AddressTableInserter.enrichmentRefreshFn for semantics.

func (*AddressTableInserter) SetRuntimeStateObserver added in v0.6.18

func (i *AddressTableInserter) SetRuntimeStateObserver(fn RuntimeStateObserver)

SetRuntimeStateObserver wires the runtime-state Manager hook. The inserter calls fn once per passive observation (new insert OR refresh of an existing slot) so known_bus_members[] gets populated and LastSeenAt stays current as a basis for M5 revalidation ordering. Codex P2 follow-up on PR #615 — without this wiring, runtime_state.json's known_bus_members[] stays empty after a fresh install and M5 has nothing to revalidate.

type AdjudicatedPassiveEvent

type AdjudicatedPassiveEvent struct {
	Event                   PassiveClassifiedEvent
	Fingerprint             PassiveTransactionFingerprint
	FamilyPolicy            ObserveFirstFamilyPolicy
	Disposition             DedupDisposition
	SuppressShadow          bool
	SuppressWatchEfficiency bool
	ThirdPartyEligible      bool
	ObservabilityOnly       bool
	LocalParticipantInbound bool
	MatchedActiveDuplicate  bool
	Epoch                   uint64
}

type AdjudicatedPassiveSubscription

type AdjudicatedPassiveSubscription struct {
	// contains filtered or unexported fields
}

func (*AdjudicatedPassiveSubscription) Close

func (subscription *AdjudicatedPassiveSubscription) Close()

func (*AdjudicatedPassiveSubscription) Events

func (subscription *AdjudicatedPassiveSubscription) Events() <-chan AdjudicatedPassiveEvent

type AdmissionStabilityWindow added in v0.6.18

type AdmissionStabilityWindow struct {
	// contains filtered or unexported fields
}

AdmissionStabilityWindow gates emission of bus_admission state transitions through the configured state_min_stability_s window. A new state is admitted to the envelope body ONLY after it has been stable for the full window duration; transient flaps (state changes within the window) do not flip the envelope. Matches AD08 flap mitigation.

func NewAdmissionStabilityWindow added in v0.6.18

func NewAdmissionStabilityWindow(windowSeconds int) *AdmissionStabilityWindow

func (*AdmissionStabilityWindow) EmittedState added in v0.6.18

func (w *AdmissionStabilityWindow) EmittedState() string

EmittedState returns the state currently reflected in the envelope.

func (*AdmissionStabilityWindow) Observe added in v0.6.18

func (w *AdmissionStabilityWindow) Observe(state string) (emitted string, flipped bool)

Observe records a state change observation. Returns (newEmittedState, flipped) where flipped reports whether the envelope should update.

type B509WatchKey

type B509WatchKey struct {
	Target          byte
	RegisterAddress uint16
}

func NewB509WatchKey

func NewB509WatchKey(target byte, registerAddress uint16) B509WatchKey

func (B509WatchKey) Canonical

func (key B509WatchKey) Canonical() string

func (B509WatchKey) Family

func (key B509WatchKey) Family() WatchFamily

func (B509WatchKey) String

func (key B509WatchKey) String() string

type B516WatchKey

type B516WatchKey struct {
	Target byte
	Period byte
	Source byte
	Usage  byte
}

func NewB516WatchKey

func NewB516WatchKey(target, period, source, usage byte) B516WatchKey

func (B516WatchKey) Canonical

func (key B516WatchKey) Canonical() string

func (B516WatchKey) Family

func (key B516WatchKey) Family() WatchFamily

func (B516WatchKey) String

func (key B516WatchKey) String() string

type B524WatchKey

type B524WatchKey struct {
	Target          byte
	Opcode          byte
	Group           byte
	Instance        byte
	RegisterAddress uint16
}

func NewB524WatchKey

func NewB524WatchKey(target, opcode, group, instance byte, registerAddress uint16) B524WatchKey

func (B524WatchKey) Canonical

func (key B524WatchKey) Canonical() string

func (B524WatchKey) Family

func (key B524WatchKey) Family() WatchFamily

func (B524WatchKey) String

func (key B524WatchKey) String() string

type B555WatchKey

type B555WatchKey struct {
	Target byte
	Opcode byte
	Zone   byte
	HC     byte

	Weekday    byte
	Slot       byte
	HasWeekday bool
	HasSlot    bool
}

func NewB555ProgramWatchKey

func NewB555ProgramWatchKey(target, opcode, zone, hc byte) B555WatchKey

func NewB555WatchKey

func NewB555WatchKey(target, opcode, zone, hc, weekday, slot byte) B555WatchKey

func (B555WatchKey) Canonical

func (key B555WatchKey) Canonical() string

func (B555WatchKey) Family

func (key B555WatchKey) Family() WatchFamily

func (B555WatchKey) String

func (key B555WatchKey) String() string

type BroadcastListener

type BroadcastListener struct {
	// contains filtered or unexported fields
}

func StartBroadcastListener

func StartBroadcastListener(ctx context.Context, cfg Config, router *router.BusEventRouter) (*BroadcastListener, error)

func StartBroadcastListenerWithReconstructor

func StartBroadcastListenerWithReconstructor(ctx context.Context, router *router.BusEventRouter, reconstructor *PassiveTransactionReconstructor) (*BroadcastListener, error)

func StartBroadcastListenerWithTransport

func StartBroadcastListenerWithTransport(ctx context.Context, cfg Config, router *router.BusEventRouter, wrap func(transport.RawTransport) transport.RawTransport) (*BroadcastListener, error)

func (*BroadcastListener) Close

func (listener *BroadcastListener) Close() error

func (*BroadcastListener) Start

func (listener *BroadcastListener) Start(ctx context.Context)

type BusAdmission added in v0.6.18

type BusAdmission struct {
	State           string                       `json:"state"`
	Source          uint8                        `json:"source"`
	CompanionTarget uint8                        `json:"companion_target"`
	Reason          string                       `json:"reason,omitempty"`
	SourceSelection *BusAdmissionSourceSelection `json:"source_selection,omitempty"`
}

BusAdmission is the additive data-body field surfaced in the ebus.v1.bus_observability envelope per AD08. It reflects the current admission state and associated metadata. State transitions are gated by the state-stability window (30s default); transient flaps do NOT flip this field nor the envelope's data_hash.

type BusAdmissionActiveProbe added in v0.6.18

type BusAdmissionActiveProbe struct {
	Target *uint8 `json:"target,omitempty"`
	Opcode string `json:"opcode,omitempty"`
	Status string `json:"status,omitempty"`
}

type BusAdmissionRejectedCandidate added in v0.6.18

type BusAdmissionRejectedCandidate struct {
	Source             uint8  `json:"source"`
	Reason             string `json:"reason,omitempty"`
	OccupancyState     string `json:"occupancy_state,omitempty"`
	EvidenceProvenance string `json:"evidence_provenance,omitempty"`
}

type BusAdmissionSourceSelection added in v0.6.18

type BusAdmissionSourceSelection struct {
	State                   string                          `json:"state"`
	Mode                    string                          `json:"mode,omitempty"`
	Outcome                 string                          `json:"outcome,omitempty"`
	Reason                  string                          `json:"reason,omitempty"`
	SelectedSource          *uint8                          `json:"selected_source,omitempty"`
	FailedSource            *uint8                          `json:"failed_source,omitempty"`
	CompanionTarget         *uint8                          `json:"companion_target,omitempty"`
	ActiveProbe             *BusAdmissionActiveProbe        `json:"active_probe,omitempty"`
	Retryable               bool                            `json:"retryable"`
	NextAction              string                          `json:"next_action,omitempty"`
	LastSuccessfulSource    *uint8                          `json:"last_successful_source,omitempty"`
	AutomaticRetryScheduled bool                            `json:"automatic_retry_scheduled"`
	RejectedCandidates      []BusAdmissionRejectedCandidate `json:"rejected_candidates,omitempty"`
}

type BusBusyAggregate

type BusBusyAggregate struct {
	TotalSeconds float64         `json:"total_seconds"`
	Windows      []BusBusyWindow `json:"windows"`
}

type BusBusyWindow

type BusBusyWindow struct {
	Window string  `json:"window"`
	Ratio  float64 `json:"ratio"`
}

type BusErrorAggregate

type BusErrorAggregate struct {
	Scope string `json:"scope"`
	Class string `json:"class"`
	Phase string `json:"phase"`
	Count uint64 `json:"count"`
}

type BusFrameAggregate

type BusFrameAggregate struct {
	Scope     string `json:"scope"`
	Source    string `json:"source"`
	Target    string `json:"target"`
	Family    string `json:"family"`
	FrameType string `json:"frame_type"`
	Count     uint64 `json:"count"`
}

type BusMessageRecord

type BusMessageRecord struct {
	Scope       string
	Family      string
	FrameType   string
	Outcome     string
	ObservedAt  time.Time
	Source      byte
	Target      byte
	RequestLen  int
	ResponseLen int
}

type BusObservabilityBoundedList

type BusObservabilityBoundedList struct {
	Count    int `json:"count"`
	Capacity int `json:"capacity"`
}

type BusObservabilityCapability

type BusObservabilityCapability struct {
	ActiveSupported    bool   `json:"active_supported"`
	PassiveSupported   bool   `json:"passive_supported"`
	BroadcastSupported bool   `json:"broadcast_supported"`
	PassiveAvailable   bool   `json:"passive_available"`
	PassiveState       string `json:"passive_state"`
	PassiveReason      string `json:"passive_reason,omitempty"`
	EndpointState      string `json:"endpoint_state"`
	TapConnected       bool   `json:"tap_connected"`
}

type BusObservabilityCounters

type BusObservabilityCounters struct {
	SeriesBudgetOverflowTotal      uint64 `json:"series_budget_overflow_total"`
	PeriodicityBudgetOverflowTotal uint64 `json:"periodicity_budget_overflow_total"`
}

type BusObservabilityDegraded

type BusObservabilityDegraded struct {
	Active  bool     `json:"active"`
	Reasons []string `json:"reasons,omitempty"`
}

type BusObservabilitySnapshot

type BusObservabilitySnapshot struct {
	Summary     BusObservabilitySummary `json:"summary"`
	Messages    []BusMessageRecord      `json:"messages,omitempty"`
	Periodicity []BusPeriodicityEntry   `json:"periodicity,omitempty"`
}

type BusObservabilityStartup

type BusObservabilityStartup struct {
	LastUpdatedAt *time.Time `json:"last_updated_at,omitempty"`
	Phase         string     `json:"phase"`
	CacheEpoch    uint64     `json:"cache_epoch"`
	LiveEpoch     uint64     `json:"live_epoch"`
}

type BusObservabilityStatus

type BusObservabilityStatus struct {
	LastUpdatedAt          *time.Time                    `json:"last_updated_at,omitempty"`
	TransportClass         string                        `json:"transport_class"`
	PublisherCadenceSec    float64                       `json:"publisher_cadence_sec"`
	PublisherCadenceSource string                        `json:"publisher_cadence_source"`
	Capability             BusObservabilityCapability    `json:"capability"`
	Warmup                 BusObservabilityWarmup        `json:"warmup"`
	TimingQuality          BusObservabilityTimingQuality `json:"timing_quality"`
	Degraded               BusObservabilityDegraded      `json:"degraded"`
	BusAdmission           *BusAdmission                 `json:"bus_admission,omitempty"`
	Startup                *BusObservabilityStartup      `json:"startup,omitempty"`
	FeatureFlags           ObserveFirstFeatureFlagState  `json:"feature_flags"`
}

type BusObservabilityStore

type BusObservabilityStore struct {
	// contains filtered or unexported fields
}

func NewBusObservabilityStore

func NewBusObservabilityStore(cfg Config) *BusObservabilityStore

func (*BusObservabilityStore) AttachReconstructor

func (store *BusObservabilityStore) AttachReconstructor(ctx context.Context, reconstructor *PassiveTransactionReconstructor) error

func (*BusObservabilityStore) Close

func (store *BusObservabilityStore) Close() error

func (*BusObservabilityStore) EvidenceBuffer added in v0.6.18

func (store *BusObservabilityStore) EvidenceBuffer() *EvidenceBuffer

EvidenceBuffer returns the runtime passive-evidence buffer used by the discovery promoter. Returns nil if the buffer was not constructed (e.g. invalid seed at startup); callers must nil-check.

func (*BusObservabilityStore) MetricsHandler

func (store *BusObservabilityStore) MetricsHandler() http.Handler

func (*BusObservabilityStore) ObserveWatchDirectApply

func (store *BusObservabilityStore) ObserveWatchDirectApply(event WatchEfficiencyDirectApplyEvent)

func (*BusObservabilityStore) ObserveWatchRead

func (store *BusObservabilityStore) ObserveWatchRead(event WatchEfficiencyReadEvent)

func (*BusObservabilityStore) OnBusEvent

func (store *BusObservabilityStore) OnBusEvent(event protocol.BusEvent) error

func (*BusObservabilityStore) OnPassiveClassifiedEvent

func (store *BusObservabilityStore) OnPassiveClassifiedEvent(event PassiveClassifiedEvent)

func (*BusObservabilityStore) PeriodicitySnapshot

func (store *BusObservabilityStore) PeriodicitySnapshot() []BusPeriodicityEntry

func (*BusObservabilityStore) ProtocolSpecimens

func (store *BusObservabilityStore) ProtocolSpecimens(family string) []ProtocolSpecimenExport

func (*BusObservabilityStore) RecentMessages

func (store *BusObservabilityStore) RecentMessages(limit int) []BusMessageRecord

func (*BusObservabilityStore) RecordBusAdmissionTransition added in v0.6.18

func (store *BusObservabilityStore) RecordBusAdmissionTransition(state string, source, companionTarget byte, reason string) bool

RecordBusAdmissionTransition is the production-side setter for the additive bus_admission field per AD08. The state argument MUST be one of {"pending", "active", "degraded"}; source/companionTarget are byte values from source-address selection or override; reason is non-empty only when state="degraded".

When an AdmissionStabilityWindow is installed (production path), the state observation is gated through it; transient flaps within the window do NOT flip the envelope nor data_hash. When no window is installed, the transition is applied immediately (test path).

Returns true if the envelope's bus_admission field actually changed as a result of this call (including stability-window-mediated flips).

func (*BusObservabilityStore) RenderPrometheus

func (store *BusObservabilityStore) RenderPrometheus() string

func (*BusObservabilityStore) SetAdaptermuxDiagProvider added in v0.6.18

func (store *BusObservabilityStore) SetAdaptermuxDiagProvider(provider func() AdaptermuxDiagSnapshot)

SetAdaptermuxDiagProvider registers a snapshot provider callback for batch-21 forensic counters. Wired in cmd/gateway/main.go after the adaptermux is constructed; the callback closes over the mux and is invoked on each /metrics scrape. nil-store and nil-provider are no- ops (defensive — keeps test setup that does not exercise adaptermux from blowing up).

func (*BusObservabilityStore) SetAdmissionStabilityWindow added in v0.6.18

func (store *BusObservabilityStore) SetAdmissionStabilityWindow(window *AdmissionStabilityWindow)

SetAdmissionStabilityWindow installs the AD08 / AD22 flap-mitigation window on the store. When set, RecordBusAdmissionTransition will only flip the envelope's bus_admission field after the new state has been stable for state_min_stability_s. Caller (cmd/gateway/main.go) constructs the window from cfg.StateMinStabilitySeconds and passes it once at startup. If unset, RecordBusAdmissionTransition writes through immediately (legacy behavior; tests may use this mode).

func (*BusObservabilityStore) SetAdmittedSourceProvider added in v0.6.18

func (store *BusObservabilityStore) SetAdmittedSourceProvider(provider func() byte)

SetAdmittedSourceProvider installs the function the store uses to learn which address has been admitted as the gateway's source. This must be set after the source-address selection completes; the store uses it to filter self-source traffic out of evidence recording so the gateway never feeds its own probes back into the promotion pipeline. Nil-safe: when unset, all non-self-evident traffic flows to the buffer (legacy / test default).

func (*BusObservabilityStore) SetEnergyFreshnessMetricsRefresher

func (store *BusObservabilityStore) SetEnergyFreshnessMetricsRefresher(refresher func(now time.Time, passiveState string))

func (*BusObservabilityStore) SetStartupSurfaceProvider

func (store *BusObservabilityStore) SetStartupSurfaceProvider(provider func() *BusObservabilityStartup)

func (*BusObservabilityStore) SetV8RolloutProvider added in v0.6.32

func (store *BusObservabilityStore) SetV8RolloutProvider(provider func() V8RolloutSnapshot)

SetV8RolloutProvider registers a snapshot provider callback for the v8 frame-atomic-visibility rollout counters. Wired in cmd/gateway/main.go after the gateway's protocol.Bus and the adaptermux's v8 classifier are constructed; the callback closes over both. nil-store and nil-provider are no-ops so tests that do not exercise the v8 path are unaffected.

func (*BusObservabilityStore) Snapshot

type BusObservabilitySummary

type BusObservabilitySummary struct {
	LastUpdatedAt    *time.Time                  `json:"last_updated_at,omitempty"`
	Status           BusObservabilityStatus      `json:"status"`
	Messages         BusObservabilityBoundedList `json:"messages"`
	Periodicity      BusObservabilityBoundedList `json:"periodicity"`
	Counters         BusObservabilityCounters    `json:"counters"`
	Errors           []BusErrorAggregate         `json:"errors,omitempty"`
	Frames           []BusFrameAggregate         `json:"frames,omitempty"`
	Busy             *BusBusyAggregate           `json:"busy,omitempty"`
	Reconstructor    *BusReconstructorAggregate  `json:"reconstructor,omitempty"`
	SpecimenFamilies int                         `json:"specimen_families"`
	SpecimenCount    int                         `json:"specimen_count"`
}

type BusObservabilityTimingQuality

type BusObservabilityTimingQuality struct {
	Active      string `json:"active"`
	Passive     string `json:"passive"`
	Busy        string `json:"busy"`
	Periodicity string `json:"periodicity"`
}

type BusObservabilityWarmup

type BusObservabilityWarmup struct {
	State                 string  `json:"state"`
	Blocker               string  `json:"blocker,omitempty"`
	ElapsedSeconds        float64 `json:"elapsed_seconds,omitempty"`
	CompletedTransactions int     `json:"completed_transactions"`
	RequiredTransactions  int     `json:"required_transactions"`
	CompletionMode        string  `json:"completion_mode,omitempty"`
}

type BusPeriodicityEntry

type BusPeriodicityEntry struct {
	SourceBucket string
	TargetBucket string
	Primary      byte
	Secondary    byte
	Family       string
	State        string
	LastSeen     time.Time
	SampleCount  int
	LastInterval time.Duration
	MeanInterval time.Duration
	MinInterval  time.Duration
	MaxInterval  time.Duration
	// contains filtered or unexported fields
}

type BusReconstructorAggregate

type BusReconstructorAggregate struct {
	Recoveries []BusReconstructorRecovery `json:"recoveries"`
	// PrefixResyncSkippedTotal — bytes dropped because the parser had
	// not observed a SymbolSyn since the previous frame boundary
	// (P6 Layer 1 inter-frame SYN gate). Operator canary for
	// continuation-byte injection / startup resync.
	PrefixResyncSkippedTotal uint64 `json:"prefix_resync_skipped_total"`
	// InvalidSrcClassSkippedTotal — bytes rejected because the byte in
	// source position was not initiator-class (P6 Layer 2 SRC
	// AddressClass validation). Direct measure of operator-confirmed
	// Mode B (upstream SRC byte loss in ENH StreamEventStarted
	// handling / proxy STARTED-drop). Sustained non-zero rate after
	// deploy is the signal to revisit P6.1 / P6.2 follow-ups.
	InvalidSrcClassSkippedTotal uint64 `json:"invalid_src_class_skipped_total"`
}

type BusReconstructorRecovery

type BusReconstructorRecovery struct {
	Reason string `json:"reason"`
	Count  uint64 `json:"count"`
}

type CanonicalAddressView added in v0.6.18

type CanonicalAddressView struct {
	Address           byte
	Role              string // "initiator" or "target"
	PeerAddress       byte
	PriorityTier      protocol.SourceAddressPriorityIndex
	FreeUse           bool
	Description       string
	Observed          bool
	DiscoverySource   string
	VerificationState string
}

CanonicalAddressView is a snapshot of one canonical eBUS address as seen by the AddressTable: the eBUS standard table row metadata (tier, role, free-use, peer) combined with this address's runtime observation state.

Observation state distinguishes:

  • "never_observed": the address is in the canonical table but no passive ACK / active scan has placed it in the registry.
  • "passive_observed": placed by AD05 inserter from passive frames.
  • "static_seed": placed by EnableStaticSeedTable.
  • "active_confirmed": placed by startup scan / probe.

This view exists so MCP/GraphQL consumers can distinguish "this canonical address has not yet emitted traffic" from "this canonical address is not real on this bus" — important for A.7e audit + operator UX.

type Config

type Config struct {
	Transport                transport.RawTransport
	PassiveTransport         transport.RawTransport // pre-configured passive transport (adapter-direct mode)
	ProxyListenAddr          string                 // TCP listen address for ENH proxy clients (empty disables)
	TransportConfig          TransportConfig
	BusConfig                protocol.BusConfig
	QueueCapacity            int
	Providers                []registry.PlaneProvider
	ScanOnStart              bool
	ScanSource               byte
	ScanSourceAuto           bool
	StartupProbeTargets      []byte
	StartupCompanionTarget   byte
	ScanTimeout              time.Duration
	ScanRequestTimeout       time.Duration
	ScanInterval             time.Duration
	BackgroundScanInterval   time.Duration
	BootLiveTimeout          time.Duration
	StateMinStabilitySeconds int
	StartupSource            StartupSourceOverride
	DiagnosticFullRangeRetry bool
	// SemanticInterval is a legacy single-interval semantic polling configuration.
	// Prefer SemanticDiscoveryInterval / SemanticConfigInterval / SemanticStateInterval.
	SemanticInterval                       time.Duration
	SemanticDiscoveryInterval              time.Duration
	SemanticConfigInterval                 time.Duration
	SemanticStateInterval                  time.Duration
	SemanticEnergyInterval                 time.Duration
	SemanticRequestTimeout                 time.Duration
	SemanticReadBreakerFailureBudget       int
	SemanticReadBreakerFailureBudgetSet    bool
	SemanticReadBreakerOpenCooldown        time.Duration
	SemanticReadBreakerHalfOpenProbeLimit  int
	SemanticZonePresenceMissThreshold      int
	SemanticZonePresenceHitThreshold       int
	SemanticDHWStaleTTL                    time.Duration
	SemanticRegulatorRecheckInterval       time.Duration
	SemanticRegulatorAbsenceGrace          time.Duration
	SemanticCachePath                      string
	BroadcastListen                        bool
	PassiveAbsenceThreshold                time.Duration
	PassiveTransactionWatchdog             time.Duration
	PassiveReconnectInitialDelay           time.Duration
	PassiveReconnectMaxDelay               time.Duration
	PassiveDedupActivePublishBudget        time.Duration
	PassiveDedupPassiveDeliveryBudget      time.Duration
	PassiveDedupPendingGraceTimeout        time.Duration
	PassiveDedupActiveFingerprintRetention time.Duration
	PassiveDedupPendingCapacity            int
	PassiveDedupRecoveryHysteresis         time.Duration
	PassiveDedupRecoveryEventThreshold     int
	LocalAddressSnapshotter                LocalBusAddressSnapshotter
	AdmittedSource                         func() byte
	WatchObserver                          WatchObserver
	WatchEfficiencyObserver                WatchEfficiencyObserver
	HTTPAddr                               string
	MetricsPath                            string
	GraphQLPath                            string
	SnapshotPath                           string
	SubscriptionPath                       string
	MCPPath                                string
	UIPath                                 string
	PortalPath                             string
	MDNSAdvertise                          bool
	MDNSInstance                           string
	InstanceGUID                           string
	// InstanceGUIDSource is the AD27 provenance tag passed by the
	// add-on alongside InstanceGUID. One of: "runtime_state",
	// "legacy_migrated", "generated", "cli-override". Empty when the
	// flag wasn't supplied (older add-on, direct CLI invocation), in
	// which case the Manager defaults to "cli-override" with a
	// deprecation log.
	InstanceGUIDSource string
	// RuntimeStatePath overrides /data/runtime_state.json for tests +
	// alternate deployments. Empty uses the runtimestate package default.
	RuntimeStatePath                        string
	DumpOutputDir                           string
	DumpUploadPath                          string
	DumpUploadURL                           string
	DumpIncludePII                          bool
	ObserveFirstEnabled                     bool
	PassiveStateDirectApply                 bool
	PassiveConfigDirectApply                bool
	ExternalWritePolicy                     ObserveFirstExternalWritePolicy
	ObserveFirstFlags                       ObserveFirstFeatureFlags
	ObserveFirstRecentMessageCapacity       int
	ObserveFirstPeriodicityCapacity         int
	ObserveFirstPeriodicityStaleTTL         time.Duration
	ObserveFirstSeriesBudget                int
	ObserveFirstWarmupConnectedWindow       time.Duration
	ObserveFirstWarmupCompletedTransactions int
	ObserveFirstWarmupPostResetWindow       time.Duration
	ObserveFirstWarmupPostResetTransactions int
	ObserveFirstWarmupOuterWindow           time.Duration

	// EnableStaticSeedTable, when true, plants the
	// helianthus-ebusreg/vaillant/productids static seed entries
	// into the registry at gateway startup. Used to deterministically
	// surface Vaillant addresses that don't respond to active scan
	// (e.g. NETX3 broadcast face 0x04 / 0xFF, SOL00 0xEC). Default
	// false to preserve the strict observe-first AD05 contract;
	// operators opt in via the addon config / CLI flag.
	//
	// Phase post-C P3 (live validation 2026-05-08): NETX3's 0x04
	// face was absent from the registry because broadcast-source
	// frames never carry an ACKCorrelation that would flow through
	// the inserter. Static seed bypasses that gate.
	EnableStaticSeedTable bool

	// PhantomInitiatorRejectBytes is a comma-separated list of hex bytes
	// (e.g. "0x71,0xFD") that the adapter-direct mux's IsKnownInitiatorByte
	// predicate rejects as transient bit-arbitration AND-collision
	// artifacts. When an external session's FAILED winner byte matches
	// one of these values, the byte is suppressed instead of forwarded
	// — preventing downstream consumers (ebusd's bus reconstructor)
	// from mistaking the phantom for a real initiator and advancing
	// their state machine into a stuck state.
	//
	// Default "0x71" preserves the live-HA test bus behavior observed
	// in batch-27 iter7 (F-30): gateway 0x7F AND-collided with
	// initiator 0xF1 yields 0x71, which is NOT a real initiator on
	// that bus. Empty string disables filtering entirely on deployments
	// where 0x71 IS a real initiator. Multi-byte CSV (e.g. "0x71,0xFD")
	// extends rejection to additional phantoms — operators should set
	// this explicitly when deploying onto a bus whose topology differs
	// from the reference. Per Codex P2 thread on PR #634 (batch-24).
	PhantomInitiatorRejectBytes string
}

func DefaultConfig

func DefaultConfig() Config

type DedupDisposition

type DedupDisposition string
const (
	DedupDispositionUnmatchedThirdParty DedupDisposition = "unmatched_third_party"
	DedupDispositionMatchedActiveCopy   DedupDisposition = "matched_active_duplicate"
	DedupDispositionLocalParticipantIn  DedupDisposition = "local_participant_inbound"
	DedupDispositionObservabilityOnly   DedupDisposition = "observability_only"
	DedupDispositionDiscontinuity       DedupDisposition = "discontinuity"
)

type DedupOutcomeClass

type DedupOutcomeClass string
const (
	DedupOutcomeSuccess        DedupOutcomeClass = "success"
	DedupOutcomeNACK           DedupOutcomeClass = "nack"
	DedupOutcomeTimeout        DedupOutcomeClass = "timeout"
	DedupOutcomeCollision      DedupOutcomeClass = "collision"
	DedupOutcomeTransportReset DedupOutcomeClass = "transport_reset"
	DedupOutcomeDecodeReset    DedupOutcomeClass = "decode_reset"
	DedupOutcomeAbandoned      DedupOutcomeClass = "abandoned"
)

type DedupResponseClass

type DedupResponseClass string
const (
	DedupResponseValueBearing     DedupResponseClass = "value_bearing"
	DedupResponseACKOnly          DedupResponseClass = "ack_only"
	DedupResponseHeaderOnly       DedupResponseClass = "header_only"
	DedupResponseErrorOrAmbiguous DedupResponseClass = "error_or_ambiguous"
)

type DedupSubscriberPriority

type DedupSubscriberPriority uint8
const (
	DedupSubscriberCritical DedupSubscriberPriority = iota + 1
	DedupSubscriberNonCritical
)

type DedupTransactionClass

type DedupTransactionClass string
const (
	DedupTransactionClassInitiatorTarget    DedupTransactionClass = "initiator_target"
	DedupTransactionClassInitiatorInitiator DedupTransactionClass = "initiator_initiator"
	DedupTransactionClassBroadcast          DedupTransactionClass = "broadcast"
	DedupTransactionClassLocalParticipantIn DedupTransactionClass = "local_participant_inbound"
	DedupTransactionClassAbandonedPartial   DedupTransactionClass = "abandoned_partial"
)

type DegradedModeAccumulator added in v0.6.18

type DegradedModeAccumulator struct {
	// contains filtered or unexported fields
}

DegradedModeAccumulator tracks both consecutive rejoin failures and a rolling-window cumulative degraded-duration counter. Escalation fires when EITHER threshold is reached. Per AD17, the accumulator is in-process only — no restart persistence.

func NewDegradedModeAccumulator added in v0.6.18

func NewDegradedModeAccumulator() *DegradedModeAccumulator

func (*DegradedModeAccumulator) ClearLatch added in v0.6.18

func (a *DegradedModeAccumulator) ClearLatch()

ClearLatch clears the escalated flag after state_min_stability_s of continuous active state. Caller is responsible for the stability timer; this method only flips the flag.

func (*DegradedModeAccumulator) CumulativeDegradedMs added in v0.6.18

func (a *DegradedModeAccumulator) CumulativeDegradedMs() uint64

CumulativeDegradedMs returns the sum of degraded-ms across all buckets in the rolling window.

func (*DegradedModeAccumulator) Escalated added in v0.6.18

func (a *DegradedModeAccumulator) Escalated() bool

Escalated reports whether the latch is currently set.

func (*DegradedModeAccumulator) RecordDegradedTick added in v0.6.18

func (a *DegradedModeAccumulator) RecordDegradedTick() bool

RecordDegradedTick advances the cumulative accumulator by one second of degraded-state occupancy. Called by a 1-second ticker in runtime when admission state is degraded. Returns true if the accumulator crossed the escalation threshold.

func (*DegradedModeAccumulator) RecordFailure added in v0.6.18

func (a *DegradedModeAccumulator) RecordFailure() bool

RecordFailure increments the consecutive failure counter and updates the accumulator with the failure moment. Returns true if the accumulator crossed the escalation threshold as a result of this call.

func (*DegradedModeAccumulator) RecordSuccess added in v0.6.18

func (a *DegradedModeAccumulator) RecordSuccess()

RecordSuccess clears the consecutive failure counter. Does NOT clear the rolling accumulator — flaps remain visible in the window. Latch clears only after state_min_stability_s of continuous active (caller handles the stability window separately).

type DumpBus

type DumpBus interface {
	Send(ctx context.Context, frame protocol.Frame) (*protocol.Frame, error)
}

type EvidenceBuffer added in v0.6.18

type EvidenceBuffer struct {
	// contains filtered or unexported fields
}

EvidenceBuffer is a bounded, LRU-with-baseline-protection buffer of per-address observations. Its max capacity is max_entries=128 (per AD05). When the buffer is full and a new non-baseline address is observed, the oldest non-baseline entry is evicted. Baseline addresses are NEVER evicted — they may update in place but never leave the buffer.

func NewEvidenceBuffer added in v0.6.18

func NewEvidenceBuffer(maxEntries int, baseline []byte) (*EvidenceBuffer, error)

func (*EvidenceBuffer) Demote added in v0.6.18

func (b *EvidenceBuffer) Demote(addr byte) bool

Demote clears the promoted flag for an address and resets its observation counters, allowing the runtime promotion pipeline to give up on a candidate that has repeatedly failed active confirmation. The address remains in the buffer (so subsequent fresh evidence can re-promote it after threshold) but is no longer returned by PromotedAddresses.

Returns true iff the address was promoted before this call.

func (*EvidenceBuffer) Len added in v0.6.18

func (b *EvidenceBuffer) Len() int

Len returns the current count of stored entries.

func (*EvidenceBuffer) PromotedAddresses added in v0.6.18

func (b *EvidenceBuffer) PromotedAddresses() []byte

PromotedAddresses returns the sorted set of addresses that have crossed the promotion threshold. Caller uses this to build the directed probe target list for the ebusreg ScanDirected call.

func (*EvidenceBuffer) Record added in v0.6.18

func (b *EvidenceBuffer) Record(record EvidenceRecord) (promoted bool)

Record adds or updates evidence for an address. Returns whether the address crossed the promotion threshold as a result (≥2 weak/strong observations OR any strong observation).

EvidencePresenceOnly records a touch without contributing to the promotion threshold (`observations` and `strongObs` are NOT incremented). Used to keep an address fresh in the buffer without promoting it (see EvidencePresenceOnly docstring).

type EvidenceRecord added in v0.6.18

type EvidenceRecord struct {
	Address  byte
	Strength EvidenceStrength
	Observed time.Time
	Kind     string
}

EvidenceRecord is a single observation for a bus address.

type EvidenceStrength added in v0.6.18

type EvidenceStrength uint8

EvidenceStrength classifies an observation's contribution weight toward promoting a suspect to a confirmed-identity candidate.

const (
	// EvidencePresenceOnly records that an address was observed on
	// the bus without contributing to promotion. Used for sign-of-
	// life broadcast sources (e.g. 0x07 0xFF), where the operator's
	// stated requirement is "consumed passively as presence
	// evidence, not used as a discovery probe target."
	EvidencePresenceOnly EvidenceStrength = iota + 1
	// EvidenceWeak counts toward promotion (>=2 weak observations
	// promote). Initiator-class frames, request-target traffic without a
	// coherent B524 response.
	EvidenceWeak
	// EvidenceStrong promotes on a single observation. Only used
	// when the response payload demonstrably implements the
	// Vaillant extended-register protocol (passes the B524 probe
	// coherency check) or carries a B509 ScanID identity reply.
	EvidenceStrong
)

type ExtRegisterRequest

type ExtRegisterRequest struct {
	Opcode   byte
	Group    byte
	Instance byte
	Addr     uint16
}

type Gateway

type Gateway struct {
	Transport transport.RawTransport
	Bus       *protocol.Bus
	Registry  *registry.DeviceRegistry
	Router    *router.BusEventRouter
	// contains filtered or unexported fields
}

func New

func New(ctx context.Context, cfg Config) (*Gateway, error)

func (*Gateway) AddRouterPlane

func (g *Gateway) AddRouterPlane(plane router.Plane)

func (*Gateway) Close

func (g *Gateway) Close() error

func (*Gateway) DumpUnknownDevices

func (g *Gateway) DumpUnknownDevices(ctx context.Context, entries []registry.DeviceEntry, opts UnknownDeviceDumpOptions) ([]UnknownDeviceDumpResult, error)

func (*Gateway) RefreshRouterPlanes

func (g *Gateway) RefreshRouterPlanes() int

func (*Gateway) Start

func (g *Gateway) Start(ctx context.Context)

type LocalAddressSnapshot

type LocalAddressSnapshot struct {
	Address byte
	Known   bool
	Epoch   uint64
}

type LocalBusAddressSnapshotter

type LocalBusAddressSnapshotter interface {
	LocalAddressSnapshot() LocalAddressSnapshot
}

type ObserveFirstDirectApplyPolicy

type ObserveFirstDirectApplyPolicy string
const (
	ObserveFirstDirectApplyPolicyNever           ObserveFirstDirectApplyPolicy = "never"
	ObserveFirstDirectApplyPolicyStateDefault    ObserveFirstDirectApplyPolicy = "state_default"
	ObserveFirstDirectApplyPolicyConfigOptIn     ObserveFirstDirectApplyPolicy = "config_opt_in"
	ObserveFirstDirectApplyPolicyEnergyMergeOnly ObserveFirstDirectApplyPolicy = "energy_merge_only"
)

type ObserveFirstExternalWritePolicy

type ObserveFirstExternalWritePolicy string
const (
	ObserveFirstExternalWritePolicyInvalidateOnly      ObserveFirstExternalWritePolicy = "invalidate_only"
	ObserveFirstExternalWritePolicyRecordOnly          ObserveFirstExternalWritePolicy = "record_only"
	ObserveFirstExternalWritePolicyRecordAndInvalidate ObserveFirstExternalWritePolicy = "record_and_invalidate"
)

func ParseObserveFirstExternalWritePolicy

func ParseObserveFirstExternalWritePolicy(value string) (ObserveFirstExternalWritePolicy, error)

type ObserveFirstFamily

type ObserveFirstFamily string
const (
	ObserveFirstFamilyOther ObserveFirstFamily = "other"
	ObserveFirstFamilyB509  ObserveFirstFamily = "B509"
	ObserveFirstFamilyB516  ObserveFirstFamily = "B516"
	ObserveFirstFamilyB524  ObserveFirstFamily = "B524"
	ObserveFirstFamilyB555  ObserveFirstFamily = "B555"
)

type ObserveFirstFamilyPolicy

type ObserveFirstFamilyPolicy struct {
	Family                         ObserveFirstFamily
	RequestIntent                  ObserveFirstRequestIntent
	ResponseClass                  DedupResponseClass
	CorrelationPolicy              WatchCorrelationPolicy
	DirectApplyPolicy              ObserveFirstDirectApplyPolicy
	UsesRuntimeExternalWritePolicy bool
	EffectiveExternalWritePolicy   ObserveFirstExternalWritePolicy
}

type ObserveFirstFeatureFlagNormalizationReason

type ObserveFirstFeatureFlagNormalizationReason string
const (
	ObserveFirstFeatureFlagNormalizationReasonMasterOffClamp             ObserveFirstFeatureFlagNormalizationReason = "master_off_clamp"
	ObserveFirstFeatureFlagNormalizationReasonConfigRequiresState        ObserveFirstFeatureFlagNormalizationReason = "config_requires_state"
	ObserveFirstFeatureFlagNormalizationReasonConfigRequiresInvalidation ObserveFirstFeatureFlagNormalizationReason = "config_requires_invalidation"
)

type ObserveFirstFeatureFlagState

type ObserveFirstFeatureFlagState struct {
	ObserveFirstEnabled      bool                            `json:"observe_first_enabled"`
	PassiveStateDirectApply  bool                            `json:"passive_state_direct_apply"`
	PassiveConfigDirectApply bool                            `json:"passive_config_direct_apply"`
	ExternalWritePolicy      ObserveFirstExternalWritePolicy `json:"external_write_policy"`
	LastUpdatedAt            *time.Time                      `json:"last_updated_at,omitempty"`
	Normalizations           []string                        `json:"normalizations,omitempty"`
}

type ObserveFirstFeatureFlagView

type ObserveFirstFeatureFlagView interface {
	ObserveFirstEnabled() bool
	PassiveStateDirectApply() bool
	PassiveConfigDirectApply() bool
	ExternalWritePolicy() ObserveFirstExternalWritePolicy
}

type ObserveFirstFeatureFlags

type ObserveFirstFeatureFlags struct {
	// contains filtered or unexported fields
}

func DefaultObserveFirstFeatureFlags

func DefaultObserveFirstFeatureFlags() ObserveFirstFeatureFlags

func NormalizeObserveFirstFeatureFlags

func NormalizeObserveFirstFeatureFlags(enabled, stateDirect, configDirect bool, policy ObserveFirstExternalWritePolicy) ObserveFirstFeatureFlags

func NormalizeObserveFirstFeatureFlagsFromView

func NormalizeObserveFirstFeatureFlagsFromView(view ObserveFirstFeatureFlagView) ObserveFirstFeatureFlags

func (ObserveFirstFeatureFlags) ExternalWritePolicy

func (flags ObserveFirstFeatureFlags) ExternalWritePolicy() ObserveFirstExternalWritePolicy

func (ObserveFirstFeatureFlags) NormalizationReasons

func (ObserveFirstFeatureFlags) ObserveFirstEnabled

func (flags ObserveFirstFeatureFlags) ObserveFirstEnabled() bool

func (ObserveFirstFeatureFlags) PassiveConfigDirectApply

func (flags ObserveFirstFeatureFlags) PassiveConfigDirectApply() bool

func (ObserveFirstFeatureFlags) PassiveStateDirectApply

func (flags ObserveFirstFeatureFlags) PassiveStateDirectApply() bool

func (ObserveFirstFeatureFlags) State

func (ObserveFirstFeatureFlags) StateAt

type ObserveFirstRequestIntent

type ObserveFirstRequestIntent string
const (
	ObserveFirstRequestIntentUnknown   ObserveFirstRequestIntent = "unknown"
	ObserveFirstRequestIntentRead      ObserveFirstRequestIntent = "read"
	ObserveFirstRequestIntentWrite     ObserveFirstRequestIntent = "write"
	ObserveFirstRequestIntentBroadcast ObserveFirstRequestIntent = "broadcast"
)

type ObserveFirstTrafficScope

type ObserveFirstTrafficScope string
const (
	ObserveFirstTrafficScopeActive  ObserveFirstTrafficScope = "active"
	ObserveFirstTrafficScopePassive ObserveFirstTrafficScope = "passive"
)

type PassiveACKCorrelation added in v0.6.18

type PassiveACKCorrelation = protocol.ACKCorrelation

type PassiveACKCorrelator added in v0.6.18

type PassiveACKCorrelator = protocol.ACKCorrelator

type PassiveACKPosition added in v0.6.18

type PassiveACKPosition = protocol.ACKPosition

type PassiveAbandonReason

type PassiveAbandonReason string
const (
	PassiveAbandonReasonCorruptedRequest    PassiveAbandonReason = "corrupted_request"
	PassiveAbandonReasonCorruptedTarget     PassiveAbandonReason = "corrupted_target"
	PassiveAbandonReasonNACK                PassiveAbandonReason = "nack"
	PassiveAbandonReasonNoResponse          PassiveAbandonReason = "no_response"
	PassiveAbandonReasonNoProgress          PassiveAbandonReason = "no_progress"
	PassiveAbandonReasonUnexpectedSYN       PassiveAbandonReason = "unexpected_syn"
	PassiveAbandonReasonUnexpectedSymbol    PassiveAbandonReason = "unexpected_symbol"
	PassiveAbandonReasonTransportReset      PassiveAbandonReason = "transport_reset"
	PassiveAbandonReasonDecodeFault         PassiveAbandonReason = "decode_fault"
	PassiveAbandonReasonDisconnected        PassiveAbandonReason = "disconnected"
	PassiveAbandonReasonCRCMismatch         PassiveAbandonReason = "crc_mismatch"
	PassiveAbandonReasonAmbiguousRetransmit PassiveAbandonReason = "ambiguous_retransmission"
	PassiveAbandonReasonShutdown            PassiveAbandonReason = "shutdown"
	PassiveAbandonReasonScanTimeout         PassiveAbandonReason = "scan_timeout"
	PassiveAbandonReasonScanCollision       PassiveAbandonReason = "scan_collision"
	PassiveAbandonReasonArbitrationFragment PassiveAbandonReason = "arbitration_fragment"
	PassiveAbandonReasonSelfEcho            PassiveAbandonReason = "self_echo"

	// F-19c (batch-16): defensive bound-check abandon reasons. These
	// fire at byte-observation time in handleRequestSymbolLocked /
	// handleResponseSymbolLocked when the candidate frame violates the
	// eBUS spec at a structural offset (QQ initiator-address rule, ZZ
	// non-SYN/non-ESC, NN_m / NN_s ≤ maxPassiveDataLen), before the
	// LEN-completion or SYN-trigger paths could mis-classify the
	// buffer.
	//
	// Spec references:
	//   - OSI-7 Application Layer Spec V1.6.1 §2.3 (NN cap: 14
	//     mfr-specific, 10 standardised; codebase uses 16 per
	//     industry folklore via maxPassiveDataLen).
	//   - john30/ebusd symbol.h:39-66 + symbol.cpp:209-229
	//     (initiator-address nibble rule).
	//   - Wikipedia OSI-2 / eBUS data-link layer reference for
	//     escape encoding scope (QQ/ZZ never escape-encoded).
	PassiveAbandonReasonInvalidQQ       PassiveAbandonReason = "invalid_qq"
	PassiveAbandonReasonInvalidZZ       PassiveAbandonReason = "invalid_zz"
	PassiveAbandonReasonInvalidNNMaster PassiveAbandonReason = "invalid_nn_m"
	PassiveAbandonReasonInvalidNNSlave  PassiveAbandonReason = "invalid_nn_s"
	PassiveAbandonReasonBufferOverflow  PassiveAbandonReason = "buffer_overflow"
)

type PassiveBusTap

type PassiveBusTap struct {
	// contains filtered or unexported fields
}

func StartPassiveBusTap

func StartPassiveBusTap(ctx context.Context, cfg Config, consumer PassiveTapConsumer) (*PassiveBusTap, error)

func StartPassiveBusTapWithTransport

func StartPassiveBusTapWithTransport(ctx context.Context, cfg Config, consumer PassiveTapConsumer, wrap func(transport.RawTransport) transport.RawTransport) (*PassiveBusTap, error)

func (*PassiveBusTap) Close

func (tap *PassiveBusTap) Close() error

func (*PassiveBusTap) Snapshot

func (tap *PassiveBusTap) Snapshot() PassiveTapStatus

type PassiveClassifiedEvent

type PassiveClassifiedEvent struct {
	Kind                PassiveClassifiedEventKind
	FrameType           protocol.FrameType
	Request             protocol.Frame
	Response            protocol.Frame
	HasRequest          bool
	HasResponse         bool
	Timing              PassiveTimingMarkers
	ObservedAt          time.Time
	AbandonReason       PassiveAbandonReason
	DiscontinuityReason PassiveDiscontinuityReason
	ACKCorrelation      PassiveACKCorrelation
	Err                 error
	Subscriber          string
	// OffendingSymbol is the raw observed byte that triggered the
	// abandon at the moment abandonLocked was called. Populated for
	// abandons whose cause is a single observed wire byte (e.g.
	// UnexpectedSymbol, NACK, UnexpectedSYN, InvalidQQ/ZZ/NN, NN-bound
	// overruns). Zero for lifecycle abandons that are not byte-driven
	// (Shutdown, TransportReset, NoProgress watchdog) and for default
	// frame-type defensive fall-throughs that cannot identify a single
	// causal byte. F-19e (batch-18, 2026-05-13): added so the 0.7
	// events/min `unexpected_symbol` rate observed post-F-19d becomes
	// forensically diagnosable — the offending byte distribution
	// reveals whether the cascade is dominated by wire SYN at
	// unexpected phases, mid-frame data bytes, or escape-sequence
	// fragments.
	OffendingSymbol byte
	// OffendingWasEscaped carries the upstream wasEscaped flag
	// (F-19d) for the offending byte. Meaningful for phases that
	// receive bytes from the unescape decoder (Request, Response);
	// always false for phases that observe only structural bytes
	// (Idle, ACK, FinalACK, Terminal) or for lifecycle abandons.
	OffendingWasEscaped bool
}

type PassiveClassifiedEventKind

type PassiveClassifiedEventKind uint8
const (
	PassiveClassifiedEventBroadcastFrame PassiveClassifiedEventKind = iota + 1
	PassiveClassifiedEventMasterFrame
	PassiveClassifiedEventTransaction
	PassiveClassifiedEventAbandonedTransaction
	PassiveClassifiedEventDiscontinuity
)

type PassiveClassifiedSubscription

type PassiveClassifiedSubscription struct {
	// contains filtered or unexported fields
}

func (*PassiveClassifiedSubscription) Close

func (subscription *PassiveClassifiedSubscription) Close()

func (*PassiveClassifiedSubscription) Events

func (subscription *PassiveClassifiedSubscription) Events() <-chan PassiveClassifiedEvent

type PassiveDiscontinuityReason

type PassiveDiscontinuityReason string
const (
	PassiveDiscontinuityConnected               PassiveDiscontinuityReason = "connected"
	PassiveDiscontinuityDisconnected            PassiveDiscontinuityReason = "disconnected"
	PassiveDiscontinuityTransportReset          PassiveDiscontinuityReason = "transport_reset"
	PassiveDiscontinuityDecodeFault             PassiveDiscontinuityReason = "decode_fault"
	PassiveDiscontinuityReadTimeout             PassiveDiscontinuityReason = "read_timeout"
	PassiveDiscontinuityShutdown                PassiveDiscontinuityReason = "shutdown"
	PassiveDiscontinuityCriticalSubscriberFault PassiveDiscontinuityReason = "critical_subscriber_overflow"
)

type PassiveDiscoveryPromoter added in v0.6.18

type PassiveDiscoveryPromoter struct {
	// contains filtered or unexported fields
}

PassiveDiscoveryPromoter is the runtime-phase counterpart to the startup directed-probe scan. It bridges the gap where a Vaillant device — typically the regulator — boots after the gateway and therefore never appears in the directed startup probe target list.

Pipeline:

  1. The bus_observability store records passive evidence per observed address into its EvidenceBuffer. Once an address crosses the buffer's promotion threshold (>=2 weak observations OR >=1 strong observation; strong = B524 / B509 ScanID response), it is exposed via PromotedAddresses().

  2. The promoter periodically polls PromotedAddresses(), filters candidates already in the registry / equal to the admitted source / outside the responder range, and runs bounded active confirmation against each remaining candidate using the existing B524 capability probe.

  3. On confirmation: a minimal Vaillant entry is registered, router planes are refreshed (so live event routing reflects the new device), and the semantic poller's discovery refresh is enqueued so the regulator surface populates without a gateway restart.

Source-address invariant: active confirmation always sources from the gateway's admitted source. The promoter never overrides the admitted source under any condition.

Rate limiting: per-address attempt counter with exponential backoff via RejoinBackoffSchedule. A failed confirmation schedules the next attempt at backoff(attempt) with a jittered floor; success clears the per-address state.

func NewPassiveDiscoveryPromoter added in v0.6.18

func NewPassiveDiscoveryPromoter(opts PassiveDiscoveryPromoterOptions) (*PassiveDiscoveryPromoter, error)

NewPassiveDiscoveryPromoter constructs a promoter. Returns an error if any required option is nil.

func (*PassiveDiscoveryPromoter) Run added in v0.6.18

Run polls the evidence buffer at the configured tick interval and runs active confirmation on each promoted candidate not already in the registry. Returns when ctx is cancelled.

func (*PassiveDiscoveryPromoter) SetRegisterFn added in v0.6.18

func (p *PassiveDiscoveryPromoter) SetRegisterFn(fn func(target byte))

SetRegisterFn overrides the registry-write function. Used by tests to substitute a no-op or to capture the registered address. The production path uses the default registry.Register helper.

func (*PassiveDiscoveryPromoter) Snapshot added in v0.6.18

Snapshot captures current promoter counters and per-address state for diagnostics and tests.

type PassiveDiscoveryPromoterOptions added in v0.6.18

type PassiveDiscoveryPromoterOptions struct {
	// Registry is the gateway device registry — used to filter
	// candidates already known and to register confirmed devices.
	// Required.
	Registry *registry.DeviceRegistry

	// EvidenceBuffer feeds candidate addresses. Required.
	EvidenceBuffer *EvidenceBuffer

	// ConfirmFn runs the B524 capability coherency probe against the
	// candidate target using the admitted source. Returns true on
	// coherent response. Required.
	ConfirmFn func(ctx context.Context, target byte) bool

	// SemanticRefreshFn enqueues the semantic poller's discovery
	// refresh after a successful registration so the regulator
	// surface populates. Optional — promoter still registers and
	// refreshes router planes when nil.
	SemanticRefreshFn func()

	// RouterRefreshFn refreshes router planes after a successful
	// registration so live event routing reflects the new device.
	// Optional.
	RouterRefreshFn func()

	// AdmittedSourceFn returns the gateway's admitted source. The
	// promoter filters this address out of candidates so the gateway
	// never confirms its own source. Required.
	AdmittedSourceFn func() byte

	// TickInterval bounds how often the promoter polls
	// PromotedAddresses. Defaults to 30s if unset.
	TickInterval time.Duration

	// Now returns the current time. Defaults to time.Now if unset.
	Now func() time.Time
}

PassiveDiscoveryPromoterOptions configures a new promoter.

type PassiveDiscoveryPromoterSnapshot added in v0.6.18

type PassiveDiscoveryPromoterSnapshot struct {
	ConfirmedTotal uint64
	RejectedTotal  uint64
	SkippedTotal   uint64
	PendingByAddr  map[byte]int
}

Snapshot returns a point-in-time view of promoter state for diagnostics and tests.

func (PassiveDiscoveryPromoterSnapshot) String added in v0.6.18

String renders the snapshot for log lines.

type PassiveEndpointState

type PassiveEndpointState string
const (
	PassiveEndpointStateUnknown                    PassiveEndpointState = "unknown"
	PassiveEndpointStateConnected                  PassiveEndpointState = "connected"
	PassiveEndpointStateTemporarilyDisconnected    PassiveEndpointState = "temporarily_disconnected"
	PassiveEndpointStateUnsupportedOrMisconfigured PassiveEndpointState = "unsupported_or_misconfigured"
	PassiveEndpointStateClosed                     PassiveEndpointState = "closed"
)

type PassiveReconstructorSnapshot

type PassiveReconstructorSnapshot struct {
	TapStatus           PassiveTapStatus
	FanoutOverflowTotal map[string]uint64
	RecoveryTotal       map[string]uint64
	// AbandonsByReason counts how many transactions the reconstructor
	// classified into each PassiveAbandonReason. Operators query this
	// to determine if a specific (src, dst) pair is failing
	// classification at unusual rates — e.g. live evidence of B503
	// frames hitting unexpected_symbol despite Grafana ground truth
	// showing positive ACKs on the wire (A.9 diagnostic surface).
	AbandonsByReason map[string]uint64
	// PrefixResyncSkippedTotal counts non-SYN bytes the parser dropped
	// because no SymbolSyn was observed since the previous frame
	// boundary (P6 Layer 1 — inter-frame SYN gate). Direct measure of
	// continuation-byte / startup resync events; expected to spike at
	// startup then plateau near zero on a clean bus.
	PrefixResyncSkippedTotal uint64
	// InvalidSrcClassSkippedTotal counts non-initiator-class bytes the
	// parser rejected in source position (P6 Layer 2 — SRC AddressClass
	// validation). Direct measure of upstream byte-loss frequency
	// (operator-confirmed Mode B: "[SYN] [TGT] [PB=0xB5] [SB] [data]"
	// signature where the actual SRC byte was eaten by the ENH
	// transport's StreamEventStarted capture or by an analogous proxy
	// drop). Sustained non-zero rate after deploy quantifies the
	// cost-of-deferral for the upstream P6.1/P6.2 follow-ups.
	InvalidSrcClassSkippedTotal uint64
}

type PassiveSubscriberPriority

type PassiveSubscriberPriority uint8
const (
	PassiveSubscriberCritical PassiveSubscriberPriority = iota + 1
	PassiveSubscriberNonCritical
)

type PassiveTapConsumer

type PassiveTapConsumer interface {
	OnPassiveTapEvent(PassiveTapEvent)
}

type PassiveTapEvent

type PassiveTapEvent struct {
	Kind       PassiveTapEventKind
	Symbol     byte
	ObservedAt time.Time
	Err        error

	// WasEscaped is true iff Symbol was produced by the eBUS byte-
	// stuffing decoder from a wire `0xA9 0x00` (→ logical 0xA9) or
	// `0xA9 0x01` (→ logical 0xAA). False means EITHER (a) a raw
	// passthrough byte that the local decoder saw on the wire, OR
	// (b) an upstream-logical byte where the transport did not expose
	// escape provenance. For F-23 transports that do expose
	// transport.StreamEvent.WasEscaped, the passive tap preserves the
	// upstream wire-side ground truth instead of hardcoding false.
	//
	// F-19d (_work_adaptermux_audit/EBUSD-VERIFICATION-2026-05-13-batch17.md):
	// the passive transaction reconstructor uses this flag to
	// disambiguate logical 0xAA bytes — escape-decoded data vs wire
	// SYN frame-terminator — replacing the heuristic
	// isMidRequestFrame() that mis-classified ~9 events/hour into
	// next-frame cascades.
	WasEscaped bool
}

type PassiveTapEventKind

type PassiveTapEventKind uint8
const (
	PassiveTapEventConnected PassiveTapEventKind = iota + 1
	PassiveTapEventDisconnected
	PassiveTapEventSymbol
	PassiveTapEventReset
	PassiveTapEventDecodeFault
	PassiveTapEventReadTimeout
)

type PassiveTapStatus

type PassiveTapStatus struct {
	Connected           bool
	EndpointState       PassiveEndpointState
	LastError           string
	ConnectAttemptCount uint64
	ConnectCount        uint64
	ConnectFailureCount uint64
	DisconnectCount     uint64
	ResetCount          uint64
	DecodeFaultCount    uint64
	ObservedSymbolCount uint64
	LastConnectAt       time.Time
	LastDisconnectAt    time.Time
	LastObservedSymbol  time.Time
}

type PassiveTimingMarkers

type PassiveTimingMarkers struct {
	RequestStart  time.Time
	RequestEnd    time.Time
	ResponseStart time.Time
	ResponseEnd   time.Time
	Terminal      time.Time
}

type PassiveTransactionFingerprint

type PassiveTransactionFingerprint struct {
	Epoch            uint64
	TransactionClass DedupTransactionClass
	OutcomeClass     DedupOutcomeClass
	ResponseClass    DedupResponseClass
	FamilyPolicy     ObserveFirstFamilyPolicy
	SharedWatchKey   WatchKey
	Source           byte
	Target           byte
	RequestBytes     []byte
	ResponseBytes    []byte
	Hash             [32]byte
	ObservedAt       time.Time
}

type PassiveTransactionReconstructor

type PassiveTransactionReconstructor struct {
	// contains filtered or unexported fields
}

func StartPassiveTransactionReconstructor

func StartPassiveTransactionReconstructor(ctx context.Context, cfg Config) (*PassiveTransactionReconstructor, error)

func (*PassiveTransactionReconstructor) Close

func (reconstructor *PassiveTransactionReconstructor) Close() error

func (*PassiveTransactionReconstructor) OnPassiveTapEvent

func (reconstructor *PassiveTransactionReconstructor) OnPassiveTapEvent(event PassiveTapEvent)

func (*PassiveTransactionReconstructor) SetLocalAddressSnapshotter

func (reconstructor *PassiveTransactionReconstructor) SetLocalAddressSnapshotter(snapshotter LocalBusAddressSnapshotter)

SetLocalAddressSnapshotter provides the reconstructor with a way to query the gateway's local bus address. The snapshotter is queried dynamically so the local address can be discovered at runtime (e.g. during the startup scan). Must be called before AttachReconstructor or at least before passive symbols start arriving; it is protected by stateMu.

func (*PassiveTransactionReconstructor) Snapshot

func (*PassiveTransactionReconstructor) Subscribe

func (reconstructor *PassiveTransactionReconstructor) Subscribe(name string, priority PassiveSubscriberPriority, buffer int) (*PassiveClassifiedSubscription, error)

type ProtocolSpecimenExport

type ProtocolSpecimenExport struct {
	Family      string    `json:"family"`
	Source      byte      `json:"source"`
	Target      byte      `json:"target"`
	FrameType   string    `json:"frame_type"`
	RequestHex  string    `json:"request_hex"`
	ResponseHex string    `json:"response_hex,omitempty"`
	RequestLen  int       `json:"request_len"`
	ResponseLen int       `json:"response_len"`
	Outcome     string    `json:"outcome"`
	FirstSeenAt time.Time `json:"first_seen_at"`
	LastSeenAt  time.Time `json:"last_seen_at"`
	Count       uint64    `json:"count"`
}

type RuntimeStateObserver added in v0.6.18

type RuntimeStateObserver func(addr byte, observedAt time.Time, reportedSource string)

RuntimeStateObserver is the runtime-state cache notification hook. AddressTableInserter calls it once per passive observation (new insert OR refresh of an existing slot) so the runtimestate.Manager can populate/refresh known_bus_members[] for M5 revalidation.

observedAt is the bus-event timestamp; reportedSource is the runtimestate.LastSource string (e.g. "passive_observed").

Production wiring lives in cmd/gateway/main.go, calling runtimeStateMgr.UpsertKnownBusMember. The hook is fired synchronously inside maybeInsert; observers MUST be cheap (the Manager's UpsertKnownBusMember is a brief mu-guarded field swap and is safe to call at bus-event rate).

type SemanticReadCircuitBreakerOptions

type SemanticReadCircuitBreakerOptions struct {
	FailureBudget      int
	OpenCooldown       time.Duration
	HalfOpenProbeLimit int
	OnTransition       func(SemanticReadCircuitBreakerTransition)
	OnSuppressed       func(SemanticReadCircuitBreakerSuppression)
}

type SemanticReadCircuitBreakerSuppression

type SemanticReadCircuitBreakerSuppression struct {
	Key             string
	State           SemanticReadCircuitState
	SuppressedTotal uint64
	RetryAfter      time.Duration
}

type SemanticReadCircuitBreakerTransition

type SemanticReadCircuitBreakerTransition struct {
	Key                 string
	From                SemanticReadCircuitState
	To                  SemanticReadCircuitState
	ConsecutiveFailures int
}

type SemanticReadCircuitState

type SemanticReadCircuitState string
const (
	SemanticReadCircuitStateClosed   SemanticReadCircuitState = "closed"
	SemanticReadCircuitStateOpen     SemanticReadCircuitState = "open"
	SemanticReadCircuitStateHalfOpen SemanticReadCircuitState = "half-open"
)

type SemanticReadExecutionStats

type SemanticReadExecutionStats struct {
	ServedFromShadow        bool
	ServedFromPassiveShadow bool
	ActiveFetchAttempted    bool
	ActiveFetchSucceeded    bool
	ActiveFetchDuration     time.Duration
}

type SemanticReadScheduler

type SemanticReadScheduler struct {
	// contains filtered or unexported fields
}

SemanticReadScheduler coalesces identical semantic reads and provides a small in-memory cache to avoid multiplying eBUS traffic under multiple consumers.

It is intentionally generic: callers choose the key and the maxAge policy.

func NewSemanticReadScheduler

func NewSemanticReadScheduler() *SemanticReadScheduler

func (*SemanticReadScheduler) Get

func (s *SemanticReadScheduler) Get(ctx context.Context, key string, maxAge time.Duration, fetch func(context.Context) ([]byte, error)) ([]byte, error)

Get returns a cached value when it is fresh, otherwise it performs fetch once and shares the result with concurrent callers.

If fetch fails, the last successful value (if any) remains cached.

func (*SemanticReadScheduler) GetWatch

func (s *SemanticReadScheduler) GetWatch(ctx context.Context, key WatchKey, maxAge time.Duration, fetch func(context.Context) ([]byte, error)) ([]byte, error)

func (*SemanticReadScheduler) GetWatchWithStats

func (s *SemanticReadScheduler) GetWatchWithStats(
	ctx context.Context,
	key WatchKey,
	maxAge time.Duration,
	fetch func(context.Context) ([]byte, error),
) ([]byte, SemanticReadExecutionStats, error)

func (*SemanticReadScheduler) SetCircuitBreaker

func (s *SemanticReadScheduler) SetCircuitBreaker(options SemanticReadCircuitBreakerOptions)

func (*SemanticReadScheduler) SetShadowCache

func (s *SemanticReadScheduler) SetShadowCache(cache *ShadowCache)

type ShadowCache

type ShadowCache struct {
	// contains filtered or unexported fields
}

func NewShadowCache

func NewShadowCache(options ShadowCacheOptions) *ShadowCache

func (*ShadowCache) BootstrapRuntimeDescriptor

func (cache *ShadowCache) BootstrapRuntimeDescriptor(descriptor WatchDescriptor, sources ...WatchActivationSource) error

BootstrapRuntimeDescriptor registers a descriptor/source pair into the runtime catalog+activation graph so shadow reads/writes can participate for keys that were discovered after cache construction.

func (*ShadowCache) CaptureGeneration

func (cache *ShadowCache) CaptureGeneration(key WatchKey) uint64

func (*ShadowCache) Close

func (cache *ShadowCache) Close(ctx context.Context) error

func (*ShadowCache) CompactOnce

func (cache *ShadowCache) CompactOnce() time.Duration

func (*ShadowCache) Entry

func (cache *ShadowCache) Entry(key WatchKey) (ShadowEntryView, bool)

func (*ShadowCache) FeatureFlags

func (cache *ShadowCache) FeatureFlags() ObserveFirstFeatureFlags

func (*ShadowCache) Invalidate

func (cache *ShadowCache) Invalidate(invalidation ShadowInvalidation) ShadowInvalidationResult

func (*ShadowCache) Lookup

func (cache *ShadowCache) Lookup(key WatchKey, maxAge time.Duration) ShadowLookupResult

func (*ShadowCache) Observe added in v0.6.32

func (cache *ShadowCache) Observe(key WatchKey) WatchObservation

func (*ShadowCache) RefreshActivations

func (cache *ShadowCache) RefreshActivations()

func (*ShadowCache) RevalidatePinnedBudget

func (cache *ShadowCache) RevalidatePinnedBudget() ShadowCacheSummary

func (*ShadowCache) SnapshotEligibility

func (cache *ShadowCache) SnapshotEligibility(key WatchKey) ShadowEligibilitySnapshot

func (*ShadowCache) StartCompactor

func (cache *ShadowCache) StartCompactor()

func (*ShadowCache) Summary

func (cache *ShadowCache) Summary() ShadowCacheSummary

func (*ShadowCache) WatchSummary

func (cache *ShadowCache) WatchSummary() WatchSummary

func (*ShadowCache) Write

func (cache *ShadowCache) Write(write ShadowWrite) ShadowWriteResult

type ShadowCacheOptions

type ShadowCacheOptions struct {
	FeatureFlags             ObserveFirstFeatureFlagView
	Catalog                  *WatchCatalog
	Activations              *WatchActivationSet
	Capacity                 int
	PinnedCapacity           int
	WriteConfirmPinnedCap    int
	TombstoneRetainWindow    time.Duration
	TombstoneHardLifespan    time.Duration
	CompactorCadence         time.Duration
	CompactorBatchSize       int
	ShutdownCompactorTimeout time.Duration
	Now                      func() time.Time
}

type ShadowCacheSummary

type ShadowCacheSummary struct {
	Enabled                  bool
	PinnedBudgetDegraded     bool
	CompactorDegraded        bool
	TotalEntries             int
	PinnedEntries            int
	EvictableEntries         int
	StaticPinnedFootprint    int
	WriteConfirmPinnedActive int
}

type ShadowConfidenceTier

type ShadowConfidenceTier string
const (
	ShadowConfidenceHigh    ShadowConfidenceTier = "high_confidence"
	ShadowConfidenceLimited ShadowConfidenceTier = "limited_confidence"
	ShadowConfidenceNone    ShadowConfidenceTier = "no_confidence"
)

type ShadowEligibilitySnapshot

type ShadowEligibilitySnapshot struct {
	Present    bool
	Eligible   bool
	Generation uint64
	State      ShadowEntryState
	ObservedAt time.Time
	ExpiresAt  time.Time
}

type ShadowEntryState

type ShadowEntryState string
const (
	ShadowEntryStatePresent     ShadowEntryState = "present"
	ShadowEntryStateInvalidated ShadowEntryState = "invalidated"
	ShadowEntryStateTombstone   ShadowEntryState = "tombstone"
)

type ShadowEntryView

type ShadowEntryView struct {
	CanonicalKey           string
	Descriptor             WatchDescriptor
	Source                 ShadowWriteSource
	Confidence             ShadowConfidenceTier
	State                  ShadowEntryState
	Value                  []byte
	ObservedAt             time.Time
	ExpiresAt              time.Time
	Generation             uint64
	LastWriteGeneration    uint64
	InvalidationGeneration uint64
	InvalidationReason     ShadowInvalidationReason
	InvalidationSource     ShadowInvalidationSource
	InvalidatedAt          time.Time
	Pinned                 bool
}

type ShadowInvalidation

type ShadowInvalidation struct {
	Key           WatchKey
	Reason        ShadowInvalidationReason
	Source        ShadowInvalidationSource
	InvalidatedAt time.Time
}

type ShadowInvalidationReason

type ShadowInvalidationReason string
const (
	ShadowInvalidationReasonExternalWrite ShadowInvalidationReason = "external_write"
	ShadowInvalidationReasonRollback      ShadowInvalidationReason = "rollback"
	ShadowInvalidationReasonManual        ShadowInvalidationReason = "manual"
	ShadowInvalidationReasonPolicyReject  ShadowInvalidationReason = "policy_reject"
)

type ShadowInvalidationResult

type ShadowInvalidationResult struct {
	Generation uint64
	State      ShadowEntryState
}

type ShadowInvalidationSource

type ShadowInvalidationSource string
const (
	ShadowInvalidationSourcePassive  ShadowInvalidationSource = "passive"
	ShadowInvalidationSourceActive   ShadowInvalidationSource = "active"
	ShadowInvalidationSourceOperator ShadowInvalidationSource = "operator"
	ShadowInvalidationSourceSystem   ShadowInvalidationSource = "system"
)

type ShadowLookupResult

type ShadowLookupResult struct {
	Entry         ShadowEntryView
	Found         bool
	Eligible      bool
	Descriptor    WatchDescriptor
	HasDescriptor bool
}

type ShadowWrite

type ShadowWrite struct {
	Key             WatchKey
	Source          ShadowWriteSource
	Confidence      ShadowConfidenceTier
	Value           []byte
	ObservedAt      time.Time
	StartGeneration uint64
}

type ShadowWriteRejectionReason

type ShadowWriteRejectionReason string
const (
	ShadowWriteRejectionReasonStaleTimestamp        ShadowWriteRejectionReason = "stale_timestamp"
	ShadowWriteRejectionReasonSameTimestampConflict ShadowWriteRejectionReason = "same_timestamp_conflict"
	ShadowWriteRejectionReasonGenerationAdvanced    ShadowWriteRejectionReason = "generation_advanced"
	ShadowWriteRejectionReasonPolicyReject          ShadowWriteRejectionReason = "policy_reject"
	ShadowWriteRejectionReasonCapacity              ShadowWriteRejectionReason = "capacity"
)

type ShadowWriteResult

type ShadowWriteResult struct {
	Accepted            bool
	Generation          uint64
	LastWriteGeneration uint64
	Reason              ShadowWriteRejectionReason
}

type ShadowWriteSource

type ShadowWriteSource string
const (
	ShadowWriteSourcePassive         ShadowWriteSource = "passive"
	ShadowWriteSourceActiveConfirmed ShadowWriteSource = "active_confirmed"
)

type SmokeCheckResult

type SmokeCheckResult struct {
	OK      bool
	Details string
	Error   string
}

type SmokeOptions

type SmokeOptions struct {
	RootDir        string
	Providers      []registry.PlaneProvider
	Logger         *log.Logger
	SourceAddress  byte
	OnGatewayReady func(ctx context.Context, gateway *Gateway, logger *log.Logger)
	GraphQLCheck   func(ctx context.Context, gateway *Gateway) SmokeCheckResult
	MCPCheck       func(ctx context.Context, gateway *Gateway) SmokeCheckResult
}

type SourceSelectionArtifact added in v0.6.18

type SourceSelectionArtifact struct {
	Admission SourceSelectionArtifactAdmission `json:"admission"`
	Discovery SourceSelectionArtifactDiscovery `json:"discovery"`
}

SourceSelectionArtifact is the machine-readable summary emitted at the end of the startup source-selection + discovery window per plan §M7.

type SourceSelectionArtifactAdmission added in v0.6.18

type SourceSelectionArtifactAdmission struct {
	State            string                                 `json:"state"`
	Source           uint8                                  `json:"source"`
	CompanionTarget  uint8                                  `json:"companion_target"`
	WarmupDurationS  float64                                `json:"warmup_duration_s"`
	ReasonIfDegraded string                                 `json:"reason_if_degraded"`
	TransportKind    string                                 `json:"transport_kind"`
	SourceSelection  SourceSelectionArtifactSourceSelection `json:"source_selection"`
}

type SourceSelectionArtifactBuilder added in v0.6.18

type SourceSelectionArtifactBuilder struct {
	// contains filtered or unexported fields
}

SourceSelectionArtifactBuilder aggregates runtime events over the startup window and produces the final SourceSelectionArtifact on Emit.

func NewSourceSelectionArtifactBuilder added in v0.6.18

func NewSourceSelectionArtifactBuilder(transportKind string) *SourceSelectionArtifactBuilder

func (*SourceSelectionArtifactBuilder) Emit added in v0.6.18

func (*SourceSelectionArtifactBuilder) EmitToFile added in v0.6.18

func (b *SourceSelectionArtifactBuilder) EmitToFile(path string) error

EmitToFile writes the artifact JSON to the given path. Once a snapshot is emitted (e.g. by the 60s startup-window goroutine), subsequent calls are no-ops via the emitOnce guard — this prevents the AD20-reviewer- flagged race where a defer-on-shutdown emit could overwrite the canonical 60s window snapshot with later state.

To re-arm the builder for a new window, call ResetEmitOnce.

func (*SourceSelectionArtifactBuilder) RecordBaselineEvidence added in v0.6.18

func (b *SourceSelectionArtifactBuilder) RecordBaselineEvidence(address uint8, count int)

func (*SourceSelectionArtifactBuilder) RecordProbe added in v0.6.18

func (b *SourceSelectionArtifactBuilder) RecordProbe(wireBytes int)

func (*SourceSelectionArtifactBuilder) ResetEmitOnce added in v0.6.18

func (b *SourceSelectionArtifactBuilder) ResetEmitOnce()

ResetEmitOnce re-arms EmitToFile so the next call will write again. Used by tests; production callers should not need this.

func (*SourceSelectionArtifactBuilder) SetActiveExplicitSource added in v0.6.18

func (b *SourceSelectionArtifactBuilder) SetActiveExplicitSource(source uint8)

SetActiveExplicitSource flips Admission.State to "active" and records the explicit Source used from the first active frame.

func (*SourceSelectionArtifactBuilder) SetBaselineEvidenceProvider added in v0.6.18

func (b *SourceSelectionArtifactBuilder) SetBaselineEvidenceProvider(provider func() map[string]int)

SetBaselineEvidenceProvider installs a callback that returns the per-baseline-address evidence counts at emit time. Called by Emit and EmitToFile under the builder's mutex; the provider must be safe for concurrent invocation. Resolves cruise-run #20 validation finding that per_baseline_address_evidence_counts was unconditionally empty in the emitted artifact even when the registry observed traffic to baseline addresses.

func (*SourceSelectionArtifactBuilder) SetDegraded added in v0.6.18

func (b *SourceSelectionArtifactBuilder) SetDegraded(reason string)

func (*SourceSelectionArtifactBuilder) SetExplicitSource added in v0.6.18

func (b *SourceSelectionArtifactBuilder) SetExplicitSource(source uint8)

func (*SourceSelectionArtifactBuilder) SetPostStartupSustainedRateProbesPer15s added in v0.6.18

func (b *SourceSelectionArtifactBuilder) SetPostStartupSustainedRateProbesPer15s(rate float64)

func (*SourceSelectionArtifactBuilder) SetPromotedSuspects added in v0.6.18

func (b *SourceSelectionArtifactBuilder) SetPromotedSuspects(n int)

func (*SourceSelectionArtifactBuilder) SetSourceSelection added in v0.6.18

func (b *SourceSelectionArtifactBuilder) SetSourceSelection(source, companionTarget uint8, warmupDuration time.Duration)

func (*SourceSelectionArtifactBuilder) SetSourceSelectionActive added in v0.6.18

func (b *SourceSelectionArtifactBuilder) SetSourceSelectionActive()

func (*SourceSelectionArtifactBuilder) SetSourceSelectionMode added in v0.6.18

func (b *SourceSelectionArtifactBuilder) SetSourceSelectionMode(v string) error

type SourceSelectionArtifactDiscovery added in v0.6.18

type SourceSelectionArtifactDiscovery struct {
	WireBytes                            int            `json:"wire_bytes"`
	WindowS                              float64        `json:"window_s"`
	StartupBurstPct                      float64        `json:"startup_burst_pct"`
	PostStartupSustainedRateProbesPer15s float64        `json:"post_startup_sustained_rate_probes_per_15s"`
	ProbeCount                           int            `json:"probe_count"`
	PromotedSuspectsWithoutIdentity      int            `json:"promoted_suspects_without_identity"`
	PerBaselineAddressEvidenceCounts     map[string]int `json:"per_baseline_address_evidence_counts"`
}

type SourceSelectionArtifactSourceSelection added in v0.6.18

type SourceSelectionArtifactSourceSelection struct {
	Mode string `json:"mode"`
}

type StartupSourceOverride added in v0.6.18

type StartupSourceOverride struct {
	// Source is the static source address to use for all Helianthus-
	// originated active frames on source-selection-capable direct transports when
	// this override is set. When nil, override is unset.
	Source *uint8

	// Validate, when true, runs the source-address selector in advisory-only mode in
	// parallel with the override path so the companion-target conflict
	// heuristic can still detect a mismatch and emit a WARN log. Does
	// NOT gate active traffic (active frames fire immediately with the
	// override source). Default: false.
	Validate bool
}

StartupSourceOverride configures opt-in static-source admission on source-selection-capable direct transports per AD09. Default (unset) preserves the standard source-address selector warmup path.

type StartupSourceSelectionMetrics added in v0.6.18

type StartupSourceSelectionMetrics struct {
	DegradedTotal                  *expvar.Int
	State                          *expvar.Int
	ExplicitSourceActive           *expvar.Int
	WarmupEventsSeen               *expvar.Int
	WarmupCyclesTotal              *expvar.Int
	ExplicitValidateOnlyTotal      *expvar.Int
	ExplicitSourceConflictDetected *expvar.Int
	DegradedEscalated              *expvar.Int
	DegradedSinceMs                *expvar.Int
	ConsecutiveFailures            *expvar.Int
	DegradedCumulativeMs           *expvar.Int
	// contains filtered or unexported fields
}

StartupSourceSelectionMetrics bundles the 11 expvar surfaces exposed by the startup source-selection plan. Names and semantics match the plan exactly; callers publish this bundle via expvar.Publish at startup and update counters/gauges as runtime events occur.

func GetOrInitStartupSourceSelectionMetrics added in v0.6.18

func GetOrInitStartupSourceSelectionMetrics() *StartupSourceSelectionMetrics

GetOrInitStartupSourceSelectionMetrics returns the process-global StartupSourceSelectionMetrics instance, creating it and publishing its expvars on first call.

func NewStartupSourceSelectionMetrics added in v0.6.18

func NewStartupSourceSelectionMetrics() *StartupSourceSelectionMetrics

NewStartupSourceSelectionMetrics allocates the metric bundle. Caller decides whether to publish via expvar.Publish or keep per-instance (for tests).

func (*StartupSourceSelectionMetrics) MarkActive added in v0.6.18

func (m *StartupSourceSelectionMetrics) MarkActive()

MarkActive sets State=1 and clears DegradedSinceMs. Holds m.mu so the pair {State, DegradedSinceMs} updates atomically vs MarkDegraded / MarkPending — prevents the AD20-reviewer-flagged race where concurrent transitions could double-count DegradedTotal.

func (*StartupSourceSelectionMetrics) MarkDegraded added in v0.6.18

func (m *StartupSourceSelectionMetrics) MarkDegraded(now time.Time)

MarkDegraded sets State=2, increments DegradedTotal, records DegradedSinceMs if not already set.

func (*StartupSourceSelectionMetrics) MarkPending added in v0.6.18

func (m *StartupSourceSelectionMetrics) MarkPending()

MarkPending sets State=0. Holds m.mu (see MarkActive note).

func (*StartupSourceSelectionMetrics) Publish added in v0.6.18

func (m *StartupSourceSelectionMetrics) Publish()

Publish registers all expvars under the "startup_source_selection_" prefix. Idempotent-safe behavior: does NOT re-register if Publish was already called (expvar panics on duplicate publishes); caller must only call once per process. In tests, use metrics without publishing.

func (*StartupSourceSelectionMetrics) RecordExplicitValidateOnly added in v0.6.18

func (m *StartupSourceSelectionMetrics) RecordExplicitValidateOnly()

RecordExplicitValidateOnly increments ExplicitValidateOnlyTotal.

func (*StartupSourceSelectionMetrics) RecordWarmupEvent added in v0.6.18

func (m *StartupSourceSelectionMetrics) RecordWarmupEvent()

RecordWarmupEvent increments WarmupEventsSeen by 1.

func (*StartupSourceSelectionMetrics) SetConsecutiveFailures added in v0.6.18

func (m *StartupSourceSelectionMetrics) SetConsecutiveFailures(n int)

SetConsecutiveFailures records the current count.

func (*StartupSourceSelectionMetrics) SetDegradedCumulativeMs added in v0.6.18

func (m *StartupSourceSelectionMetrics) SetDegradedCumulativeMs(ms uint64)

SetDegradedCumulativeMs records the current rolling-window cumulative.

func (*StartupSourceSelectionMetrics) SetDegradedEscalated added in v0.6.18

func (m *StartupSourceSelectionMetrics) SetDegradedEscalated(latched bool)

SetDegradedEscalated sets the latch (0 or 1).

func (*StartupSourceSelectionMetrics) SetExplicitSourceActive added in v0.6.18

func (m *StartupSourceSelectionMetrics) SetExplicitSourceActive(active bool)

SetExplicitSourceActive(true) sets ExplicitSourceActive=1; false sets 0.

func (*StartupSourceSelectionMetrics) SetExplicitSourceConflictDetected added in v0.6.18

func (m *StartupSourceSelectionMetrics) SetExplicitSourceConflictDetected()

SetExplicitSourceConflictDetected flips ExplicitSourceConflictDetected to 1 (latching for the current run; a cycle reset would restart it).

func (*StartupSourceSelectionMetrics) StartWarmupCycle added in v0.6.18

func (m *StartupSourceSelectionMetrics) StartWarmupCycle() uint64

StartWarmupCycle resets WarmupEventsSeen to 0 and increments WarmupCyclesTotal. Returns the new cycle sequence number.

type TransportAdmissionPath added in v0.6.18

type TransportAdmissionPath uint8

TransportAdmissionPath is the source-selection dispatch decision for a given transport kind per the startup source-selection plan's transport capability matrix (see plan AD11 and helianthus-docs-ebus/architecture/startup-admission-and-discovery.md §10).

const (
	// TransportAdmissionSourceSelectionCapable denotes a direct transport on which the
	// gateway runs the source-address selector warmup before any
	// non-explicit active frame. Applies to ENH, ENS, UDP-plain, TCP-plain.
	TransportAdmissionSourceSelectionCapable TransportAdmissionPath = iota + 1

	// TransportAdmissionStaticFallback denotes the ebusd-tcp path, where the
	// gateway does NOT instantiate source-address selection and uses the configured
	// ScanSource on the ebusd-owned transport path per AD13.
	TransportAdmissionStaticFallback
)

func ClassifyTransportAdmission added in v0.6.18

func ClassifyTransportAdmission(kind TransportProtocol) (TransportAdmissionPath, error)

ClassifyTransportAdmission returns the admission path dispatch for the given TransportProtocol per the startup-admission-discovery plan's transport capability matrix. Unknown or empty values return (zero, error).

func ResolveAdmissionPath added in v0.6.18

func ResolveAdmissionPath(kind TransportProtocol) (path TransportAdmissionPath, adapterDirectSpecialCase bool)

ResolveAdmissionPath returns the admission-path dispatch with adapter-direct special-cased to JoinCapable. Adapter-direct multiplexer mode always wraps a source-selection-capable underlying transport (ENH or ENS in practice; UDP/TCP-plain are not configurations the multiplexer is built for). The source-address selection bus adapter subscribes to the same PassiveTransactionReconstructor regardless of multiplexer presence, so adapter-direct deployments MUST run source-address selection.

This helper exists because ClassifyTransportAdmission is intentionally pure (one transport at a time, no multiplexer-context awareness) and rejects adapter-direct as needing inner-transport unwrap. Callers that have access to the full Config (and therefore know about the multiplexer wrapper) should use this helper instead of ClassifyTransportAdmission directly.

Returns the resolved admission path. The boolean indicates whether the adapter-direct special case fired (so the caller can log the multiplexer detection once, not twice). Empty/unknown protocols fall back to StaticFallback with the second return false.

Resolves cruise-run #20 validation finding: startup_scan.go had its own ClassifyTransportAdmission calls that took the static-fallback path on adapter-direct, contradicting main.go's special-case. Centralising the logic here keeps all call sites in agreement.

type TransportConfig

type TransportConfig struct {
	Protocol     TransportProtocol
	Network      string
	Address      string
	ReadTimeout  time.Duration
	WriteTimeout time.Duration
	DialTimeout  time.Duration
	Dial         func(ctx context.Context, network, address string, timeout time.Duration) (net.Conn, error)
}

type TransportProtocol

type TransportProtocol string

func CanonicalTransportProtocol added in v0.6.18

func CanonicalTransportProtocol(protocol TransportProtocol) TransportProtocol

CanonicalTransportProtocol is the exported form of canonicalTransportProtocol. External callers (e.g. cmd/gateway's responder-capability provider) use it to normalise raw protocol strings to the enum literals used across the package.

Aliases handled: "ebusd" → "ebusd-tcp"; all forms are trimmed + lower-cased. Unknown strings pass through as the normalised TransportProtocol with no alias resolution (caller decides what to do with unknown values).

type UnknownDeviceDumpOptions

type UnknownDeviceDumpOptions struct {
	OutputDir      string
	UploadURL      string
	IncludePII     bool
	IncludeTraffic bool
	TrafficWindow  time.Duration
	SourceAddress  byte
	B509Addresses  []uint16
	B524Requests   []ExtRegisterRequest
	Logger         *log.Logger
	Now            func() time.Time
}

type UnknownDeviceDumpResult

type UnknownDeviceDumpResult struct {
	Address        byte
	BundlePath     string
	ManifestPath   string
	Uploaded       bool
	UploadURL      string
	UploadStatus   string
	UploadHTTPCode int
	Error          string
}

type V8RolloutSnapshot added in v0.6.32

type V8RolloutSnapshot struct {
	// Round9AbsorbEntered counts transactions that entered the
	// legacy pre-v8 round-9 absorb path in protocol.Bus.
	// Published as helianthus_round9_absorb_entered_total.
	//
	// HelianthusRound9FiredUnderProxy alert fires on any growth:
	// once the v8 frame-atomic-visibility path is fully deployed
	// the round-9 fallback should never trigger. Growth indicates
	// either an upstream regression or a corner case the v8 path
	// does not yet cover.
	Round9AbsorbEntered uint64

	// PayloadAaAutoSynAbsorbed counts wire SYN bytes the round-9
	// absorb path consumed during payload-0xAA writes.
	// Published as helianthus_payload_aa_auto_syn_absorbed_total.
	// Forensic — paired with Round9AbsorbEntered for severity
	// dashboards (per-byte cost of legacy-fallback firings).
	PayloadAaAutoSynAbsorbed uint64

	// PayloadAaAutoSynRecovered counts transactions where the
	// absorb path's drain successfully recovered to a clean
	// terminator after consuming wire SYNs.
	// Published as helianthus_payload_aa_auto_syn_recovered_total.
	// Forensic — ratio recovered/entered shows how often the
	// fallback actually rescued the transaction vs hit drain
	// exhaustion.
	PayloadAaAutoSynRecovered uint64

	// PayloadAaAutoSynDrainExhausted counts transactions where the
	// absorb path exhausted its drain budget without recovering.
	// Published as helianthus_payload_aa_auto_syn_drain_exhausted_total.
	// Forensic — non-zero indicates the round-9 fallback is
	// running but failing to rescue; the transaction will surface
	// as a bus error.
	PayloadAaAutoSynDrainExhausted uint64

	// V8ShadowWouldHaveDroppedTotal mirrors
	// v8classifier.Classifier.ShadowWouldHaveDroppedTotal — bytes
	// the v8 classifier WOULD have dropped under ModeEnforce but
	// did NOT drop in ModeShadow. Published as
	// helianthus_v8_shadow_would_have_dropped_total.
	//
	// HelianthusV8ShadowWouldHaveDroppedGrowing alert fires on
	// rate > 0 in shadow mode — operators must assess enforce-
	// mode safety before rolling out. ModeOff and ModeEnforce
	// both report 0 here by design (see v8classifier.go:805).
	V8ShadowWouldHaveDroppedTotal uint64
}

V8RolloutSnapshot is the per-scrape view of the frame-atomic-visibility v8 rollout counters. Three of the four payload_aa_* counters live on helianthus-ebusgo's *protocol.Bus (entered/recovered/exhausted), the Round9AbsorbEntered counter also lives there, and the shadow-mode would-have-dropped counter lives on the gateway's instance-local *v8classifier.Classifier. Snapshotting them into a single struct keeps bus_observability_store decoupled from both packages.

Field naming matches the Prometheus surface 1:1 (snake-case lowering of the Go field name with a leading "helianthus_" prefix) — see helianthus-docs-ebus/deployment/prometheus-alerts.md for the operator alerts that consume these counters.

type WatchActivationSet

type WatchActivationSet struct {
	// contains filtered or unexported fields
}

func NewWatchActivationSet

func NewWatchActivationSet(catalog *WatchCatalog) *WatchActivationSet

func (*WatchActivationSet) Activate

func (set *WatchActivationSet) Activate(source WatchActivationSource, keys ...WatchKey) error

Activate validates the full batch before mutating the immutable catalog-backed activation set.

func (*WatchActivationSet) ActiveSources

func (set *WatchActivationSet) ActiveSources(key WatchKey) []WatchActivationSource

func (*WatchActivationSet) Deactivate

func (set *WatchActivationSet) Deactivate(source WatchActivationSource, keys ...WatchKey)

func (*WatchActivationSet) Observe

func (set *WatchActivationSet) Observe(key WatchKey) WatchObservation

func (*WatchActivationSet) Summary

type WatchActivationSource

type WatchActivationSource string
const (
	WatchActivationSourcePoller       WatchActivationSource = "poller"
	WatchActivationSourceWriteConfirm WatchActivationSource = "write_confirm"
	WatchActivationSourceTooling      WatchActivationSource = "tooling"
	WatchActivationSourceOperator     WatchActivationSource = "operator"
)

type WatchCatalog

type WatchCatalog struct {
	// contains filtered or unexported fields
}

func NewWatchCatalog

func NewWatchCatalog(descriptors []WatchDescriptor) (*WatchCatalog, error)

func (*WatchCatalog) Descriptor

func (catalog *WatchCatalog) Descriptor(key WatchKey) (WatchDescriptor, bool)

func (*WatchCatalog) DescriptorByCanonical

func (catalog *WatchCatalog) DescriptorByCanonical(key string) (WatchDescriptor, bool)

func (*WatchCatalog) Descriptors

func (catalog *WatchCatalog) Descriptors() []WatchDescriptor

func (*WatchCatalog) Len

func (catalog *WatchCatalog) Len() int

type WatchCorrelationPolicy

type WatchCorrelationPolicy string
const (
	WatchCorrelationPolicyRequestResponse   WatchCorrelationPolicy = "request_response"
	WatchCorrelationPolicyBroadcastSelector WatchCorrelationPolicy = "broadcast_selector"
	WatchCorrelationPolicyRecordInvalidate  WatchCorrelationPolicy = "record_and_invalidate"
	WatchCorrelationPolicyRecordOnly        WatchCorrelationPolicy = "record_only"
	WatchCorrelationPolicyInvalidateOnly    WatchCorrelationPolicy = "invalidate_only"
)

type WatchDescriptor

type WatchDescriptor struct {
	Key               WatchKey
	SemanticClass     WatchSemanticClass
	FreshnessProfile  WatchFreshnessProfile
	DecoderID         string
	CorrelationPolicy WatchCorrelationPolicy
	DirectApplyPolicy WatchDirectApplyPolicy
	FreshnessTTL      *time.Duration
}

func (WatchDescriptor) CanonicalKey

func (descriptor WatchDescriptor) CanonicalKey() string

func (WatchDescriptor) EffectiveFreshnessTTL

func (descriptor WatchDescriptor) EffectiveFreshnessTTL() (time.Duration, error)

func (WatchDescriptor) Family

func (descriptor WatchDescriptor) Family() WatchFamily

type WatchDirectApplyPolicy

type WatchDirectApplyPolicy string
const (
	WatchDirectApplyPolicyNever           WatchDirectApplyPolicy = "never"
	WatchDirectApplyPolicyStateDefault    WatchDirectApplyPolicy = "state_default"
	WatchDirectApplyPolicyConfigOptIn     WatchDirectApplyPolicy = "config_opt_in"
	WatchDirectApplyPolicyEnergyMergeOnly WatchDirectApplyPolicy = "energy_merge_only"
)

type WatchEfficiencyDirectApplyEvent

type WatchEfficiencyDirectApplyEvent struct {
	Key                WatchKey
	Descriptor         WatchDescriptor
	HasDescriptor      bool
	ObservedAt         time.Time
	CandidateEvaluated bool
	Accepted           bool
}

type WatchEfficiencyObserver

type WatchEfficiencyObserver interface {
	ObserveWatchRead(event WatchEfficiencyReadEvent)
	ObserveWatchDirectApply(event WatchEfficiencyDirectApplyEvent)
}

type WatchEfficiencyReadEvent

type WatchEfficiencyReadEvent struct {
	Key           WatchKey
	Descriptor    WatchDescriptor
	HasDescriptor bool
	MaxAge        time.Duration
	Stats         SemanticReadExecutionStats
	ObservedAt    time.Time
}

type WatchFamily

type WatchFamily string
const (
	WatchFamilyB509 WatchFamily = "B509"
	WatchFamilyB516 WatchFamily = "B516"
	WatchFamilyB524 WatchFamily = "B524"
	WatchFamilyB555 WatchFamily = "B555"
)

type WatchFreshnessProfile

type WatchFreshnessProfile string
const (
	WatchFreshnessProfileStateFast WatchFreshnessProfile = "state_fast"
	WatchFreshnessProfileStateSlow WatchFreshnessProfile = "state_slow"
	WatchFreshnessProfileConfig    WatchFreshnessProfile = "config"
	WatchFreshnessProfileDiscovery WatchFreshnessProfile = "discovery"
	WatchFreshnessProfileDebug     WatchFreshnessProfile = "debug"
)

type WatchKey

type WatchKey interface {
	fmt.Stringer
	Canonical() string
	Family() WatchFamily
}

func PassiveWatchKeyFromEvent

func PassiveWatchKeyFromEvent(event PassiveClassifiedEvent) (WatchKey, bool)

func PassiveWatchKeyFromFrame

func PassiveWatchKeyFromFrame(frame protocol.Frame) (WatchKey, bool)

type WatchObservation

type WatchObservation struct {
	State         WatchObservationState
	Descriptor    WatchDescriptor
	HasDescriptor bool
	Sources       []WatchActivationSource
}

type WatchObservationState

type WatchObservationState string
const (
	WatchObservationStateActive      WatchObservationState = "active"
	WatchObservationStateInactive    WatchObservationState = "inactive"
	WatchObservationStateCatalogMiss WatchObservationState = "catalog_miss"
)

type WatchObservationSummary

type WatchObservationSummary struct {
	ActiveTotal      uint64
	InactiveTotal    uint64
	CatalogMissTotal uint64
}

type WatchObserver

type WatchObserver interface {
	Observe(key WatchKey) WatchObservation
}

type WatchSemanticClass

type WatchSemanticClass string
const (
	WatchSemanticClassState     WatchSemanticClass = "state"
	WatchSemanticClassConfig    WatchSemanticClass = "config"
	WatchSemanticClassDiscovery WatchSemanticClass = "discovery"
	WatchSemanticClassDebug     WatchSemanticClass = "debug"
)

type WatchSummary

type WatchSummary struct {
	LastUpdatedAt                 *time.Time                   `json:"last_updated_at,omitempty"`
	Inventory                     WatchSummaryInventory        `json:"inventory"`
	ActivationCounts              WatchSummaryActivationCounts `json:"activation_counts"`
	FreshnessClasses              []WatchSummaryClassCount     `json:"freshness_classes"`
	DirectApplyEligibilityClasses []WatchSummaryClassCount     `json:"direct_apply_eligibility_classes"`
	Degraded                      WatchSummaryDegraded         `json:"degraded"`
}

type WatchSummaryActivationCounts

type WatchSummaryActivationCounts struct {
	CatalogDescriptors int                      `json:"catalog_descriptors"`
	ActiveKeys         int                      `json:"active_keys"`
	SourceClasses      []WatchSummaryClassCount `json:"source_classes"`
}

type WatchSummaryClassCount

type WatchSummaryClassCount struct {
	Class string `json:"class"`
	Count int    `json:"count"`
}

type WatchSummaryDegraded

type WatchSummaryDegraded struct {
	Active               bool     `json:"active"`
	ShadowingEnabled     bool     `json:"shadowing_enabled"`
	PinnedBudgetDegraded bool     `json:"pinned_budget_degraded"`
	CompactorDegraded    bool     `json:"compactor_degraded"`
	Reasons              []string `json:"reasons,omitempty"`
}

type WatchSummaryInventory

type WatchSummaryInventory struct {
	TotalEntries             int                      `json:"total_entries"`
	PinnedEntries            int                      `json:"pinned_entries"`
	EvictableEntries         int                      `json:"evictable_entries"`
	StaticPinnedFootprint    int                      `json:"static_pinned_footprint"`
	WriteConfirmPinnedActive int                      `json:"write_confirm_pinned_active"`
	StateClasses             []WatchSummaryClassCount `json:"state_classes"`
	PinClasses               []WatchSummaryClassCount `json:"pin_classes"`
}

Directories

Path Synopsis
cmd
ebusdscan command
gateway command
matrix-runner command
smoke command
internal
adaptermux
Package adaptermux embeds adapter multiplexing logic directly in the gateway, allowing a single ENH/ENS connection to the adapter hardware with demuxed active (owner) and passive (observer) paths.
Package adaptermux embeds adapter multiplexing logic directly in the gateway, allowing a single ENH/ENS connection to the adapter hardware with demuxed active (owner) and passive (observer) paths.
adaptermux/v8classifier
Package v8classifier implements the frame-atomic-visibility v8 classifier surface for helianthus-ebusgateway's adapter multiplexer.
Package v8classifier implements the frame-atomic-visibility v8 classifier surface for helianthus-ebusgateway's adapter multiplexer.
execution_policy
Package execution_policy is the single shared execution-policy module for the ebus_standard L7 surface.
Package execution_policy is the single shared execution-policy module for the ebus_standard L7 surface.
nm_runtime
Package nm_runtime is the catalog-driven Network-Management emit path.
Package nm_runtime is the catalog-driven Network-Management emit path.
rpc_source
Package rpc_source contains shared validation for caller-supplied RPC source bytes.
Package rpc_source contains shared validation for caller-supplied RPC source bytes.
runtimestate
Package runtimestate is the gateway-owned loader/persister for /data/runtime_state.json.
Package runtimestate is the gateway-owned loader/persister for /data/runtime_state.json.
vaillant/b503session
Package b503session implements the live-monitor session FSM for the Vaillant B503 extended-register protocol as specified in helianthus-docs-ebus/protocols/vaillant/ebus-vaillant-B503.md sections 6, 7.1.1, and 7.4.
Package b503session implements the live-monitor session FSM for the Vaillant B503 extended-register protocol as specified in helianthus-docs-ebus/protocols/vaillant/ebus-vaillant-B503.md sections 6, 7.1.1, and 7.4.
mcp
ebus_standard
Package ebus_standard implements the gateway MCP surfaces for the ebus_standard L7 namespace (M4_GATEWAY_MCP).
Package ebus_standard implements the gateway MCP surfaces for the ebus_standard L7 namespace (M4_GATEWAY_MCP).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL