[CHIRP-3538]: BSSCI 1.1 interoperability, durability and protocol hardening#31
Open
wirelesstim wants to merge 25 commits into
Open
[CHIRP-3538]: BSSCI 1.1 interoperability, durability and protocol hardening#31wirelesstim wants to merge 25 commits into
wirelesstim wants to merge 25 commits into
Conversation
…ange Both defects Oliver Haala (Fraunhofer IIS) reported when his Fraunhofer AVA base station could not connect. Version negotiation (CHIRP-3539): a station requesting 1.1.0 in con was answered with error 71 and disconnected. The service center now selects a compatible version and returns it in the conRsp version field, so the station accepts with conCmp or declines with error (BSSCI rev1 4.2, 5.3.2). A different major version remains a protocol error. Full unsigned EUI-64 (CHIRP-3540): 0xCAFECAFECAFECAFE was rejected because decoding narrowed uint64 to int64, the outbound scEui was corrupted through its JSON projection, and values above 2^53 lost precision in browser transport. The complete range now survives wire decode and encode, persistence, gRPC, certificates and configuration. Includes the framed interop matrix that replays the Fraunhofer scenario exactly, and the EUI-64 database matrix covering constraint saturation.
…d tooling Follow-through for CHIRP-3540 after EUIs moved to BYTEA(8): rename cascades and the statistics join were still assuming the old signed column, the archive rebuild was destructive and could downgrade high-bit EUIs, and blueprint handler literals were not resolving through the error catalog. Also repairs the web lint and vitest gates so the frontend checks run end to end.
Session resume (CHIRP-3541): Session is split into a transport-free ProtocolSessionState, resume becomes spec-conformant and tenant-scoped, and the connect state machine is completed so activation is atomic - a half-activated session can no longer be observed. Operation lifecycle (CHIRP-3542): the command-direction map is corrected and its generator made deterministic, service-center-initiated commands arriving inbound are rejected, pending operations are persisted before they are cached and before the frame is sent, an inbound error is answered only with errorAck instead of a guessed completion, and SC-initiated operations are finalized after completion instead of leaking. Migration 000140 purges pending operations orphaned by session ownership.
… hardening Downlink reception status (CHIRP-3543): dlRxStat reports are correlated by the base station they were expected from, and an expiry worker with config-backed timing retires queries that never receive a report. Certificate issuance (CHIRP-3544): issuance is hardened against cross-tenant minting - a certificate can only be produced for a base station the caller's tenant owns. Adds the BSSCI timing keys to the shipped configuration.
Downlink dispatch (CHIRP-3543) converges on a single owner of the pending -> reserved -> queued lifecycle: exact queue-ID reservation, idempotent queued confirmation, batch pending-operation insert, and dlDataQueRsp repair. A downlink is claimed by exactly one dispatcher and is neither lost nor sent twice. Operation durability (CHIRP-3542): service-center operation IDs are allocated monotonically and never rolled back, the counter is persisted before the pending record, and the frame is written last. An ambiguous write closes the transport so recovery happens through resume with the original IDs. Connect-stage error exchanges now terminate cleanly and a NULL negotiated version stays empty rather than failing the resume.
CHIRP-3544. A composite resolver maps the TLS client certificate to an identity: an EUI common name resolves against the registered base station, and an org-<UUID> common name resolves through the deployment's organization resolver. Connect-time enforcement checks the SHA-256 fingerprint with a race-safe backfill from the stored PEM, and strict mode binds the certificate CN to the connecting EUI.
…tions CHIRP-3545. The boundary checker was matching nothing for its scoped rules and therefore reporting success regardless of the code. With the checker made meaningful, the real violations it had been hiding are fixed, the dead broadcast hook and normalization test spy are removed, and BSSCI protocol tuning is exposed through configuration instead of scattered literals. Also integrates the preserved legacy archive, purges leaked status polls, and repairs the full-stack probe gates so they exercise the protocol rather than asserting on their own fixtures.
CHIRP-3545. The protocol servers stop consuming the shared storage facade and instead depend on narrow, consumer-owned contracts satisfied structurally by the repositories, so errors.Is keeps working without adapters. StatusService owns all pending-operation persistence, session counters unify on UpdateOperationIDs, and the attach transactions move to a dedicated persistence collaborator. Both protocol servers are constructor-injected, with ConfigureRuntime supplying the circular dependencies before Start. SCACI threads context through the subsystem, taking its SOLID exemptions to zero. MQTT events are no longer published for an unresolved organization.
The transparent proxy returns codes.Internal (failed proxying s2c) when the CLIENT side of a stream goes away - which happens on every KC-Web page navigation and realtime reconnect. The stream interceptor counted those as upstream failures, so normal browsing opened the core circuit breaker and every page failed with 'circuit breaker is open/recovering'; long-lived streams also pinned the half-open probe slots, sticking recovery. Streams now run outside the breaker's execution slots: an open breaker still rejects them up front, a terminal error still feeds the counters via the new UpstreamBreaker.RecordResult - but only when the client context is still alive, so client-initiated teardown is never a failure. Unary methods keep the original in-breaker execution and per-RPC timeout.
…sume rows The pending-operation cache is keyed by the ephemeral runtime session ID, but connection teardown only cleaned it for terminal sessions - an active session lost unexpectedly leaked its cached entries forever, and the inconsistent-resume path swept under a freshly hydrated runtime ID that matched nothing. Teardown now always evicts the dead session's cache via the new StatusService.EvictCachedOperations while the persisted rows keep their resume semantics. The resume reissue loop also removes rows that fail reconstitution (they could never be reissued yet resurfaced on every resume) and stops at the first send failure instead of hammering a broken connection.
Streams run outside the breaker's execution slots, so the previous gate (reject only when open) let a half-open breaker admit unlimited streams alongside its slot-accounted unary probes, bypassing MaxRequests and muddying the recovery verdict. Streams now require a closed breaker; half-open returns the recovering message and recovery is proven by the unary probe path alone.
…uards Start now verifies every dependency the composition root wires unconditionally, so a wiring regression fails at startup instead of as a nil dereference under traffic; feature-controlled collaborators stay optional. ConfigureRuntime rejects double configuration and any call after Start. The SCACI constructor gains the same fail-fast checks for its four previously unvalidated collaborators (org resolver, session snapshot provider, propagation, error recorder). Removes the dead Server.broadcaster field and its Dependencies entry: SCACI broadcasting reaches consumers through the service bundle, the server never read it.
…op dead create branch Fresh sessions are persisted synchronously via PersistConnectSync before operation logging, so the async path's create branch was unreachable - and it wrote session.ID from the goroutine, a latent data race on the live session. The renamed PersistResumeAsync handles only the resume update, reads nothing but the immutable pre-goroutine snapshot, and rejects misuse on a fresh session. Fresh-session persistence coverage moves to PersistConnectSync tests.
The test job excluded pkg/bssci although its -short run finishes in seconds, so the packages this branch changes most were never tested in CI. The KC-DB postgres testcontainers suite (~15 min) gets its own job in parallel with the image builds instead of a blanket exclusion.
…ence The attach transaction intentionally returns repository errors unwrapped so handlers can match sentinels via errors.Is; the pass-through annotations document that at each return site.
… rebuilt Semantic reconstruction moved from the post-conCmp reissue loop into the pre-conRsp resume preparation, alongside the existing strict decode: a persisted operation that cannot be rebuilt rejects the whole resume with EAGAIN, preserving every row and its downlink queue state, instead of deleting recoverable work after activation (which could orphan a reserved queue row - there is no reclaim sweeper). A reissue send failure now aborts connect completion: the handler returns the error, an ambiguous write closes the transport, and status polling never starts on a dead connection. Covered by per-type malformed-row rejection tests, an aborted-reissue test, and a production-statusService eviction test.
A stream admitted while the breaker was closed could finish during half-open, and RecordResult's Execute would consume a probe slot and close or reopen the breaker - recovery was not exclusively decided by the unary probes. RecordResult is now a no-op outside the closed state, with a deterministic old-stream-during-half-open test.
Start now validates the wiring before setting started, so a failed validation leaves the server startable once the composition root completes it; a second Start and any Start after Stop are rejected, and Stop is idempotent. The dlRxStatQry expiry worker starts only after the listener is actually up (immediate or deferred-certificate path). Adds real Start/Stop lifecycle tests and covers the four trailing SCACI constructor checks (orgResolver, sessionSnapshotProvider, propagationSvc, errorRecorder).
… returns The pass-through comments claimed handlers match these errors via errors.Is; neither consumer does - the attach handler maps every persistence failure to the database catalog error, and wrapping with %w would preserve errors.Is regardless. Every return now wraps with the corresponding catalog log-message prefix, proven by injected-sentinel tests.
…res suite KC-Core drops -short (the full suite runs in minutes and covers the resume/lifecycle integration tests), lint/test/test-postgres run on every branch push so pushed branches get check runs, and every image job now needs test-postgres and publishes only from main/dev (or a manual dispatch) - broken database code can no longer publish images while the PostgreSQL suite is still running.
SetupEnvDBOrSkip fail-fasts under CI=true, so the postgres job now runs a migrated postgres:17.5 service on 5433 alongside the testcontainers suites - the combination the local gates already run.
wirelesstim
force-pushed
the
CHIRP-3538-bssci-version-negotiation-eui64
branch
from
July 22, 2026 21:03
ba289f6 to
13c2dca
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the BSSCI 1.1 interoperability story reported by Oliver Haala (Fraunhofer IIS), whose Fraunhofer AVA base station (BSSCI v1.1, EUI
CA-FE-CA-FE-CA-FE-CA-FE) could not connect to the service center. Both root causes are fixed, and the surrounding protocol/durability surface is hardened.Jira: CHIRP-3538 (sub-tasks CHIRP-3539 … CHIRP-3545, CHIRP-3550)
The two reported defects
Version negotiation (CHIRP-3539). A base station requesting
1.1.0inconwas answered with error 71 (Unsupported minor version) and disconnected. The service center now offers its own supported version in theconRspversionfield and lets the station accept viaconCmpor decline viaerror, per BSSCI rev1 §4.2/§5.3.2.Unsigned EUI-64 range (CHIRP-3540).
0xCAFECAFECAFECAFEwas rejected. The cause was signed narrowing ofuint64→int64during message decoding, plus outboundscEuicorruption through JSON projection and precision loss above 2^53. The full unsigned range now survives end-to-end: wire decode and encode, persistence, gRPC/browser, certificates, configuration.EUIs move from signed
BIGINTtoBYTEA(8). Run migrations before upgrading; EUIs aboveINT64_MAXoverflowed silently on the old schema.What else is in here
conRsp, so an operation that cannot be rebuilt rejects the resume withEAGAINand preserves its row and downlink queue state rather than dropping recoverable work after activation.errorAckdispositions are tracked per session; only anerrorAckthat replaces a known pending SC operation finalizes it.pending → reserved → queuedwith exact-match reservation, idempotent confirmation anddlDataQueRsprepair;dlRxStatQrypairs persist atomically.Start, reproducible quality gates from a standalone clone, release-manifest provenance, and a new export-tree gate.Verification
13c2dca: lint, full KC-Core suite (no-short), and the KC-DB postgres integration suite.make pre-commit,make pre-push,make scaci-verify,make bssci-verify,make verify-solid.70b3d59cd00009e6connects and stays online (protocol log,basestation_onlineevent, andis_online = tin the database).Merge order
This PR first. The ECE submodule bump follows once this squash-merges, repinned to the resulting
mainSHA.