Skip to content

Add safety gating and harden evidentiary audit for v1 - #2

Merged
jakthom merged 3 commits into
mainfrom
fix/architecture-review-remediation
Jul 28, 2026
Merged

Add safety gating and harden evidentiary audit for v1#2
jakthom merged 3 commits into
mainfrom
fix/architecture-review-remediation

Conversation

@jakthom

@jakthom jakthom commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Prepares teleop for its v1 compatibility commitment by adding fail-closed safety gating, strengthening the evidentiary audit format, closing trusted-verification gaps, and documenting the full exported API.

Safety

  • Adds a fail-closed safety.Guard with command timeout, dead-man re-actuation, loop watchdog, latching trips, and emergency stop.
  • Measures safety deadlines on the controller session monotonic clock.
  • Runs the guard as a controller processor so authorization changes are recorded and timeout transitions occur without application polling.
  • Records application commands at the actuator decision boundary.

Audit and cryptography

  • Uses Go standard-library Ed25519, HMAC-SHA-256, SHA-256, and RFC 6962 Merkle trees.
  • Adds ReadTrusted, which requires an independently obtained public key, a complete footer, format v3, and signed coverage through every returned event.
  • Treats a log-declared public key as internal consistency only; PublicKey in VerifyOptions makes signature verification implicit and rejects key substitution.
  • Buffers streaming events until a verified tree head covers them, rejects unsigned tails, and caps the pending buffer against memory exhaustion.
  • Strictly enforces version-specific JSON fields and rejects duplicate fields, unknown fields, and v3 metadata smuggled into legacy authenticated logs.
  • Binds every v3 event to the non-zero controller session declared by the manifest.
  • Validates the full declared public key and its KeyID; the short ID is never treated as a trust anchor.
  • Makes anchored checkpoints self-describing and independently verifiable with VerifyCheckpoint.
  • Forces signers and external anchors to use an integrity chain regardless of option order.
  • Defensively copies caller keys during verification, clears recorder-owned HMAC material, and releases private signer references on terminal close/failure.

Key-management boundary

The package handles safe key use and trust pinning, but lifecycle management remains a deployment responsibility: hardware/remote signer provisioning, device-to-key binding, public-key distribution, rotation, revocation, archival verification, destruction, and external anchor monitoring. Production signers must support Ed25519. GenerateKey remains intended for tests and development.

Compatibility and documentation

  • Format v3 is hardened; v1 and v2 logs remain readable.
  • Adds package-level GoDoc, a runnable ReadTrusted example, documentation for every exported identifier, and a pkg.go.dev badge.
  • Documents exact distinctions among integrity, HMAC authentication, signed origin authentication, external existence witnessing, and provenance.
  • Hardware mappings have been validated by the project author across the supported targets.

Validation

  • go test ./...
  • CGO_ENABLED=0 go test ./...
  • go test -race ./..., with the final audit changes re-run under go test -race ./audit
  • go vet ./...
  • golangci-lint run ./... — 0 issues
  • repeated FuzzVerify runs over hundreds of thousands of executions
  • govulncheck ./... — no reachable vulnerabilities
  • go mod tidy, formatting, and diff checks clean

This is a focused internal review and test hardening pass, not a formal independent cryptographic assessment.

Automated release

The release automation now mirrors open-ships/n2k:

  • VERSION establishes the deliberate 1.0.0 baseline.
  • After this PR is merged, successful CI on the current main commit creates and pushes v1.0.0, then publishes a generated GitHub Release.
  • Superseded CI runs and already-tagged commits are skipped.
  • Later successful main commits increment the latest patch version unless VERSION is raised to a newer baseline.

jakthom added 3 commits July 27, 2026 22:37
Adds a safety package that gates operator output on conditions it can
affirmatively establish, and strengthens audit logs from integrity-only
to evidentiary.

safety:
- Command timeout measured on a monotonic clock, so a wall-clock step
  cannot make stale input appear fresh.
- Dead-man switch with a re-actuation deadline: a control held
  indefinitely is treated as defeated rather than engaged.
- Control-loop watchdog, latching trips requiring an explicit re-arm,
  and a latching emergency stop that Arm alone cannot clear.
- Guards run as controller processors, so transitions are recorded and
  the gate trips on its own timer without application involvement.

audit:
- Ed25519 signing over tree heads via crypto.Signer, separating
  verification from authorship. Verification distinguishes signatures
  checked against the log's self-declared key from those checked against
  a caller-supplied trusted key; only the latter carries weight.
- RFC 6962 Merkle tree with inclusion and consistency proofs, enabling
  selective disclosure and append-only verification.
- Periodic signed checkpoints and an Anchor interface publishing heads
  outside the recording process, making destruction and truncation
  detectable rather than merely suspected.
- Session provenance: build revision, host, configuration, operator.
- Format version 3. Version 1 and 2 logs remain readable and are now
  covered by tests. Verify is fuzzed, since it parses untrusted input.

teleop:
- Monotonic readings on every event header, plus wall-clock step
  detection published as ClockEvent.
- Controller.RecordCommand closes the causal chain at the actuator
  boundary. It never blocks; a full queue is an explicit fault.

Also carries the pending architecture-review remediation across the xbox
providers, terminal monitor, gesture, action, and testkit packages.
CI on Linux hung for ten minutes on the CGO_ENABLED=0 step. Reproducing it in
a container surfaced one pre-existing defect and two of my own.

xbox: poll readiness is not a whole event

waitReadable returns once poll reports readable, which guarantees one byte,
not a 24-byte evdev event. Read then called binary.Read, which is io.ReadFull,
and blocked in a syscall that neither the context nor Close can interrupt
until the remainder arrived. For a partial event at the tail of a kernel
buffer that is never, so the input path stops delivering with no error and no
timeout.

Reads now go through a buffer that decodes only complete records, leaving poll
as the single blocking point. A spurious readiness is treated as spurious
rather than as a fault, and a read deadline bounds the read where the
descriptor supports one. TestLinuxReadCancellation also leaked its descriptor
on the failure path, and because these tests run in parallel, a recycled
descriptor number appeared as a spurious readiness in a sibling test.

teleop: state and freshness metadata were not committed together

handleObservation set c.state before publishing and c.meta after, so a
snapshot taken in between returned new state with stale metadata. A consumer
enforcing a command timeout reads that as "no input has ever arrived". It
fails safe, but it trips a gate for no reason, and a gate that trips
spuriously is one operators learn to work around. Both are now committed under
one lock before publishing.

teleop: accepted commands could be discarded at close

RecordCommand reports success once a command is queued. Close could then
discard the queue without publishing, dropping a command from the record after
telling the caller it was accepted. terminate now drains pending commands
before the terminal neutral state, preserving issue order.

safety: an unobserved hold is not a defeated switch

A control the snapshot shows as held whose press the guard has not yet seen
was reported as ReasonDeadManStale, which latches. That state is ordinary
while a press propagates through the pipeline, so arming during one demanded
a re-arm. It is now ReasonDeadManUnconfirmed, which inhibits without latching
and clears when the press is observed.

Verified on Linux in a container across all four CI steps: cgo enabled, cgo
disabled, and -race, each repeated five times.
@jakthom jakthom changed the title Add safety gating and evidentiary audit guarantees Add safety gating and harden evidentiary audit for v1 Jul 28, 2026
@jakthom
jakthom merged commit 669fc7a into main Jul 28, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant