Documentation
¶
Overview ¶
Package fleetagent is the epic-#405 fleet agent identity model: an enrolled, addressable agent and the single-use enrolment token that mints it. It is pure domain (imports only shared and the stdlib). Secret material (the enrolment token and the agent bearer credential) is never stored in the clear here; the domain carries only hashes, and the plaintext is returned to the caller once at creation time and never persisted.
This is the minimal, token-based identity that the agent-facing API (#409) requires. Mutual TLS with per-agent client certificates is the hardening tracked under #408; this model is built so a certificate fingerprint can replace the bearer-token hash without changing the lifecycle.
Index ¶
- Constants
- Variables
- func BatchMessage(b AgentBatch) []byte
- func BatchMessageV2(b AgentBatchV2) []byte
- func CertFingerprint(der []byte) string
- func DeliveryKey(agent shared.ID, pos StreamPosition, eventIndex int) string
- func DetectionContentHash(content []byte, assetID shared.ID) string
- func KeyBindingMessage(k AgentSigningKey) []byte
- func ProveKeyPossession(priv ed25519.PrivateKey, k AgentSigningKey) string
- func ResponseCommandDigest(c ResponseCommand) string
- func ResponseCommandMessage(c ResponseCommand) []byte
- func ResponseHaltCommandDigest(c ResponseHaltCommand) string
- func ResponseHaltCommandMessage(c ResponseHaltCommand) []byte
- func ResponseObservationRequestDigest(r ResponseObservationRequest) string
- func ResponseTargetEvidenceReceiptDigest(r ResponseTargetEvidenceReceipt) string
- func ResponseVerificationMessage(r ResponseVerificationReport) []byte
- func SameResponseExecutionResult(left, right ResponseExecutionResult) bool
- func SameResponseTargetEvidenceReceipt(left, right ResponseTargetEvidenceReceipt) bool
- func SamplingPolicyDigest(algorithm, policyID, seed string, version uint64) (string, error)
- func SensorStateMessage(r SensorStateReport) []byte
- func SignBatch(priv ed25519.PrivateKey, b AgentBatch) string
- func SignBatchV2(priv ed25519.PrivateKey, b AgentBatchV2) string
- func SignResponseCommand(privateKey ed25519.PrivateKey, c ResponseCommand) string
- func SignResponseHaltCommand(privateKey ed25519.PrivateKey, command ResponseHaltCommand) string
- func SignResponseVerification(private ed25519.PrivateKey, report ResponseVerificationReport) string
- func SignSensorState(private ed25519.PrivateKey, r SensorStateReport) string
- func SignTelemetryGap(priv ed25519.PrivateKey, g TelemetryGapReport) string
- func SignTelemetryManifest(priv ed25519.PrivateKey, m TelemetryBatchManifest) string
- func SortSeqRanges(rs []SeqRange)
- func TelemetryDeliveryKey(agentID shared.ID, session SessionID, streamID shared.ID, ...) (string, error)
- func TelemetryDeliveryStreamID(agentID shared.ID, session SessionID, priority DeliveryPriority) (shared.ID, error)
- func TelemetryEventDigest(payload []byte, assetID shared.ID) string
- func TelemetryGapMessage(g TelemetryGapReport) []byte
- func TelemetryManifestMessage(m TelemetryBatchManifest) []byte
- func TelemetryPayloadDigest(events []EventRef) string
- func VerifyBatch(pub ed25519.PublicKey, b AgentBatch) error
- func VerifyBatchV2(pub ed25519.PublicKey, b AgentBatchV2) error
- func VerifyBatchV2WithKey(k AgentSigningKey, wantPurpose SigningPurpose, now time.Time, b AgentBatchV2) error
- func VerifyBatchWithKey(k AgentSigningKey, wantPurpose SigningPurpose, now time.Time, b AgentBatch) error
- func VerifyKeyPossession(k AgentSigningKey, proofB64 string) error
- func VerifyResponseCommand(publicKey ed25519.PublicKey, c ResponseCommand) error
- func VerifyResponseHaltCommand(publicKey ed25519.PublicKey, command ResponseHaltCommand) error
- func VerifyResponseVerification(public ed25519.PublicKey, report ResponseVerificationReport) error
- func VerifyResponseVerificationWithKey(key AgentSigningKey, now time.Time, report ResponseVerificationReport) error
- func VerifySensorState(pub ed25519.PublicKey, r SensorStateReport) error
- func VerifySensorStateWithKey(k AgentSigningKey, now time.Time, r SensorStateReport) error
- func VerifyTelemetryGap(pub ed25519.PublicKey, g TelemetryGapReport) error
- func VerifyTelemetryGapWithKey(k AgentSigningKey, now time.Time, g TelemetryGapReport) error
- func VerifyTelemetryManifest(pub ed25519.PublicKey, m TelemetryBatchManifest) error
- func VerifyTelemetryManifestWithKey(k AgentSigningKey, now time.Time, m TelemetryBatchManifest) error
- type AckLedger
- type Agent
- func (a *Agent) AssignGroup(group string, now time.Time)
- func (a *Agent) AttestCertificate(fingerprint string)
- func (a *Agent) Decommission(now time.Time)
- func (a *Agent) Decommissioned() bool
- func (a *Agent) Removed() bool
- func (a *Agent) Revoke(by shared.ID, reason string, now time.Time)
- func (a *Agent) Revoked() bool
- type AgentBatch
- type AgentBatchV2
- type AgentSigningKey
- type BootID
- type BoundedProcessObservation
- type DeliveryClass
- type DeliveryOutcome
- type DeliveryPriority
- type DetectionAttribution
- type DetectionBatchItem
- type DetectionBatchItemV2
- type DetectionEvidenceEnvelope
- type DetectionEvidenceEnvelopeV2
- type DetectionRef
- type DetectionRefV2
- type DetectionSpoolPayload
- type EnrolToken
- type EventRef
- type KeyStatus
- type PendingDetectionV2
- type ProvenanceState
- type ResponseCommand
- type ResponseCommandSigningKey
- type ResponseExecutionJournalEntry
- type ResponseExecutionResult
- type ResponseExecutionState
- type ResponseHaltCommand
- type ResponseObservation
- type ResponseObservationRequest
- type ResponseObserverBinding
- type ResponseTargetEvidenceReceipt
- type ResponseVerificationReport
- type RulepackReference
- type SensorStateReport
- type SeqRange
- type SequenceGap
- type SessionID
- type SigningPurpose
- type State
- type StreamPosition
- type TelemetryBatchManifest
- type TelemetryGapReason
- type TelemetryGapReport
- type TelemetryReference
Constants ¶
const ( NoSamplingAlgorithm = "none" NoSamplingPolicyID = "none" NoSamplingVersion = uint64(1) )
SamplingPolicyDigest commits the complete sampling-policy tuple named by A3. Length-prefixing every field makes the commitment unambiguous without relying on JSON key ordering or whitespace, and keeps future policy changes explicit.
const DetectionEvidenceEnvelopeVersion = 1
DetectionEvidenceEnvelopeVersion is the envelope schema version, carried in every envelope so the permanent chain link stays decodable as the shape evolves.
const (
ResponseCommandProtocolVersion = 2
)
const ResponseExecutionJournalVersion = 1
const (
ResponseHaltCommandProtocolVersion = 1
)
const ResponseObservationProtocolVersion = 1
const ResponseTargetEvidenceReceiptVersion = 1
const SigningAlgorithm = "ed25519"
SigningAlgorithm is the only content-signing algorithm A0.2 supports. It is a named constant (not a free string) so an unknown algorithm is rejected at construction rather than silently trusted.
const TelemetryProtocolVersion = 1
TelemetryProtocolVersion is the current agent→control-plane telemetry transport protocol. It is carried on every manifest so the wire envelope can evolve independently of the payload schema (A0.3) and of the agent version.
Variables ¶
var ErrBadBatchSignature = errors.New("detection batch signature invalid")
ErrBadBatchSignature is returned when an agent detection batch's signature does not verify.
var ErrBadManifestSignature = errors.New("telemetry batch manifest signature invalid")
ErrBadManifestSignature is returned when a telemetry batch manifest's signature does not verify.
var ErrBadResponseCommandSignature = errors.New("invalid response command signature")
var ErrBadResponseVerificationSignature = fmt.Errorf("bad response-verification signature")
var ErrBadSensorStateSignature = fmt.Errorf("bad sensor-state signature")
var ErrBadTelemetryGapSignature = errors.New("telemetry gap report signature invalid")
Functions ¶
func BatchMessage ¶
func BatchMessage(b AgentBatch) []byte
BatchMessage is the canonical byte string a batch signature covers: the context tag, agent, engagement, sequence, signing-key id, and the SORTED (id, content-digest) refs — so membership is order-independent but complete, each detection's content is bound, and the named signing key cannot be swapped. It is a sha256 digest of those fields, separated so field boundaries cannot collide.
func BatchMessageV2 ¶ added in v0.2.0
func BatchMessageV2(b AgentBatchV2) []byte
func CertFingerprint ¶
CertFingerprint is the canonical agent certificate fingerprint: the hex SHA-256 of the certificate DER. It is the single source of truth shared by the issuing CA (infrastructure) and the authentication path (adapter), so the two can never drift.
func DeliveryKey ¶ added in v0.2.0
func DeliveryKey(agent shared.ID, pos StreamPosition, eventIndex int) string
DeliveryKey is the idempotency key for one event within a batch: a resend of the same (agent, incarnation, stream, sequence, event index) is the SAME key, so the server's ingest can treat it as a no-op instead of storing a duplicate. It deliberately keys on Epoch too, so the same (stream, sequence, index) in a NEW incarnation is a distinct key (a legitimately different event after a reset), never collapsed into the pre-restart one.
Fields are LENGTH-PREFIXED, not separator-joined: the agent id is a free-form string that could itself contain any separator byte, and a forged boundary in an idempotency key would let one event's ingest suppress a different event (a silent-loss vector). Length prefixing makes the decoding unambiguous regardless of field contents.
func DetectionContentHash ¶
DetectionContentHash is the shared digest both the agent and the control plane compute over a detection's shipped bytes plus the asset it was observed on, so the two sides agree on what the signature binds. Keeping it here (domain) means there is one definition, not two that can drift.
func KeyBindingMessage ¶ added in v0.2.0
func KeyBindingMessage(k AgentSigningKey) []byte
KeyBindingMessage is the canonical proof-of-possession challenge an agent signs when it registers a key: the context tag plus every field that binds the key to its identity — agent, key id, purpose, algorithm, and window. Signing it with the key's private half proves possession AND commits to the binding, so the server cannot be tricked into registering a public key its holder did not intend for this agent/purpose. Field boundaries are separated so they cannot collide.
func ProveKeyPossession ¶ added in v0.2.0
func ProveKeyPossession(priv ed25519.PrivateKey, k AgentSigningKey) string
ProveKeyPossession produces the base64 proof an agent presents at registration: an ed25519 signature over KeyBindingMessage with the key's PRIVATE half. The public half is what gets registered.
func ResponseCommandDigest ¶ added in v0.2.0
func ResponseCommandDigest(c ResponseCommand) string
func ResponseCommandMessage ¶ added in v0.2.0
func ResponseCommandMessage(c ResponseCommand) []byte
ResponseCommandMessage returns the canonical domain-separated bytes signed by the control plane.
func ResponseHaltCommandDigest ¶ added in v0.2.0
func ResponseHaltCommandDigest(c ResponseHaltCommand) string
func ResponseHaltCommandMessage ¶ added in v0.2.0
func ResponseHaltCommandMessage(c ResponseHaltCommand) []byte
func ResponseObservationRequestDigest ¶ added in v0.2.0
func ResponseObservationRequestDigest(r ResponseObservationRequest) string
func ResponseTargetEvidenceReceiptDigest ¶ added in v0.2.0
func ResponseTargetEvidenceReceiptDigest(r ResponseTargetEvidenceReceipt) string
func ResponseVerificationMessage ¶ added in v0.2.0
func ResponseVerificationMessage(r ResponseVerificationReport) []byte
ResponseVerificationMessage is the canonical, domain-separated digest signed by the observer key.
func SameResponseExecutionResult ¶ added in v0.2.0
func SameResponseExecutionResult(left, right ResponseExecutionResult) bool
SameResponseExecutionResult compares exact command-outcome identity while treating equivalent timestamp locations as the same instant.
func SameResponseTargetEvidenceReceipt ¶ added in v0.2.0
func SameResponseTargetEvidenceReceipt(left, right ResponseTargetEvidenceReceipt) bool
SameResponseTargetEvidenceReceipt compares every immutable field, including bounded evidence.
func SamplingPolicyDigest ¶ added in v0.2.0
func SensorStateMessage ¶ added in v0.2.0
func SensorStateMessage(r SensorStateReport) []byte
func SignBatch ¶
func SignBatch(priv ed25519.PrivateKey, b AgentBatch) string
SignBatch produces the base64 ed25519 signature for a batch. Agents sign with the private half of the key whose public half the control plane knows from enrolment (#408).
func SignBatchV2 ¶ added in v0.2.0
func SignBatchV2(priv ed25519.PrivateKey, b AgentBatchV2) string
func SignResponseCommand ¶ added in v0.2.0
func SignResponseCommand(privateKey ed25519.PrivateKey, c ResponseCommand) string
func SignResponseHaltCommand ¶ added in v0.2.0
func SignResponseHaltCommand(privateKey ed25519.PrivateKey, command ResponseHaltCommand) string
func SignResponseVerification ¶ added in v0.2.0
func SignResponseVerification(private ed25519.PrivateKey, report ResponseVerificationReport) string
func SignSensorState ¶ added in v0.2.0
func SignSensorState(private ed25519.PrivateKey, r SensorStateReport) string
func SignTelemetryGap ¶ added in v0.2.0
func SignTelemetryGap(priv ed25519.PrivateKey, g TelemetryGapReport) string
func SignTelemetryManifest ¶ added in v0.2.0
func SignTelemetryManifest(priv ed25519.PrivateKey, m TelemetryBatchManifest) string
SignTelemetryManifest produces the base64 ed25519 signature for a manifest.
func SortSeqRanges ¶ added in v0.2.0
func SortSeqRanges(rs []SeqRange)
SortSeqRanges orders ranges by their start, so callers comparing/persisting gap sets get a canonical order. (Gaps already returns ranges in order; this is for callers that merge ranges from many streams.)
func TelemetryDeliveryKey ¶ added in v0.2.0
func TelemetryDeliveryKey(agentID shared.ID, session SessionID, streamID shared.ID, priority DeliveryPriority, epoch, sequence uint64, eventIndex int) (string, error)
TelemetryDeliveryKey is the A3 idempotency key for one event position. Unlike the older generic DeliveryKey it explicitly commits the authenticated enrollment session and server-derived delivery stream, so a sequence reset in a new incarnation cannot alias another lane or session. eventIndex remains explicit for future batches that may carry more than one event at a delivery sequence; current A2 telemetry uses index zero.
func TelemetryDeliveryStreamID ¶ added in v0.2.0
func TelemetryDeliveryStreamID(agentID shared.ID, session SessionID, priority DeliveryPriority) (shared.ID, error)
TelemetryDeliveryStreamID derives the server-verifiable identity of one A2 telemetry delivery lane. A2 sequence/Epoch coordinates are per priority lane, while an individual TelemetryEnvelope.StreamID identifies its sensor/class provenance; the two are deliberately distinct. Binding the lane to agent + enrollment session + priority prevents a producer from choosing an arbitrary stream namespace to evade ACK/replay state.
func TelemetryEventDigest ¶ added in v0.2.0
TelemetryEventDigest is the shared digest both the agent and the control plane compute over one telemetry event's shipped bytes plus the asset it was observed on, so the two sides agree on what the manifest's EventRef.Digest binds. One definition (domain) so the two sides cannot drift.
func TelemetryGapMessage ¶ added in v0.2.0
func TelemetryGapMessage(g TelemetryGapReport) []byte
TelemetryGapMessage is purpose-separated from telemetry-batch signatures and length-prefixes every field, preventing free-form IDs/reasons from shifting boundaries.
func TelemetryManifestMessage ¶ added in v0.2.0
func TelemetryManifestMessage(m TelemetryBatchManifest) []byte
TelemetryManifestMessage is the canonical byte string a manifest signature covers: the context tag, protocol/schema versions, identity, the full stream position, the loss accounting, the SORTED (event id, digest) pairs, and the payload digest — so the batch's membership, ordinal, incarnation, loss claims and payload are all bound and the named signing key cannot be swapped. It is a sha256 digest with delimiter-separated fields so field boundaries cannot collide.
func TelemetryPayloadDigest ¶ added in v0.2.0
TelemetryPayloadDigest is the batch-level commitment the manifest's PayloadDigest carries: a sha256 over the SORTED (event id, digest) refs. The control plane recomputes it from the manifest's event refs and checks it equals the signed PayloadDigest, so that signed field cannot be a stray value. One definition (domain) so the agent and control plane cannot drift.
func VerifyBatch ¶
func VerifyBatch(pub ed25519.PublicKey, b AgentBatch) error
VerifyBatch checks the batch signature against the agent's public key. A batch that does not verify is refused before any of its detections are sealed — an unsigned or forged batch must never enter the evidence chain.
func VerifyBatchV2 ¶ added in v0.2.0
func VerifyBatchV2(pub ed25519.PublicKey, b AgentBatchV2) error
func VerifyBatchV2WithKey ¶ added in v0.2.0
func VerifyBatchV2WithKey(k AgentSigningKey, wantPurpose SigningPurpose, now time.Time, b AgentBatchV2) error
VerifyBatchV2WithKey applies the existing signing-key lifecycle checks to the v2 signature contract without altering the v1 VerifyBatchWithKey canonical bytes.
func VerifyBatchWithKey ¶ added in v0.2.0
func VerifyBatchWithKey(k AgentSigningKey, wantPurpose SigningPurpose, now time.Time, b AgentBatch) error
VerifyBatchWithKey is the server-side gate that admits a signed batch under the keyed lifecycle. It fails closed on every mismatch, in order: wrong purpose (a key for another stream), a key not bound to the batch's agent, an envelope naming a different KeyID than the resolved key, a key that is not usable at now (pending/expired/revoked), and finally a bad signature. Only a key that passes all of these verifies the batch, so no rotated-out, cross-purpose, or misattributed key can admit a detection.
func VerifyKeyPossession ¶ added in v0.2.0
func VerifyKeyPossession(k AgentSigningKey, proofB64 string) error
VerifyKeyPossession checks a registration proof fail-closed: the key must be internally valid (which re-asserts KeyID == fingerprint(PublicKey)), and the proof must be a well-formed ed25519 signature over the binding message BY THE KEY'S OWN PUBLIC HALF. A holder who does not possess the private key, or who signed a different binding, cannot register the key.
func VerifyResponseCommand ¶ added in v0.2.0
func VerifyResponseCommand(publicKey ed25519.PublicKey, c ResponseCommand) error
func VerifyResponseHaltCommand ¶ added in v0.2.0
func VerifyResponseHaltCommand(publicKey ed25519.PublicKey, command ResponseHaltCommand) error
func VerifyResponseVerification ¶ added in v0.2.0
func VerifyResponseVerification(public ed25519.PublicKey, report ResponseVerificationReport) error
func VerifyResponseVerificationWithKey ¶ added in v0.2.0
func VerifyResponseVerificationWithKey(key AgentSigningKey, now time.Time, report ResponseVerificationReport) error
func VerifySensorState ¶ added in v0.2.0
func VerifySensorState(pub ed25519.PublicKey, r SensorStateReport) error
func VerifySensorStateWithKey ¶ added in v0.2.0
func VerifySensorStateWithKey(k AgentSigningKey, now time.Time, r SensorStateReport) error
func VerifyTelemetryGap ¶ added in v0.2.0
func VerifyTelemetryGap(pub ed25519.PublicKey, g TelemetryGapReport) error
func VerifyTelemetryGapWithKey ¶ added in v0.2.0
func VerifyTelemetryGapWithKey(k AgentSigningKey, now time.Time, g TelemetryGapReport) error
func VerifyTelemetryManifest ¶ added in v0.2.0
func VerifyTelemetryManifest(pub ed25519.PublicKey, m TelemetryBatchManifest) error
VerifyTelemetryManifest checks the manifest signature against a public key. Fail-closed: a bad key size, malformed signature, or a signature that does not verify all return ErrBadManifestSignature.
func VerifyTelemetryManifestWithKey ¶ added in v0.2.0
func VerifyTelemetryManifestWithKey(k AgentSigningKey, now time.Time, m TelemetryBatchManifest) error
VerifyTelemetryManifestWithKey is the fail-closed server-side gate: the resolved key must be a telemetry-batch key, bound to the manifest's agent, named by the manifest's KeyID, usable now, and its public half must verify the signature. It mirrors VerifyBatchWithKey for the telemetry path.
Types ¶
type AckLedger ¶ added in v0.2.0
type AckLedger struct {
// contains filtered or unexported fields
}
AckLedger tracks, for ONE stream incarnation, which sequences have been received, and derives the highest-contiguous ACK and the explicit gaps below it. The ACK a stream reports is the highest sequence S such that every sequence in 1..S has been received — so an ACK can never skip a hole, and any sequence received out of order above a hole is remembered but does NOT advance the ACK until the hole fills. The gaps are the still-missing sequences below the highest one received, i.e. the explicit, queryable "we know these are outstanding" set. A zero-value AckLedger is ready to use.
NOT safe for concurrent use: it is a per-stream, per-caller accumulator — synchronize externally (or use one ledger per goroutine) rather than sharing an instance across goroutines.
func NewAckLedger ¶ added in v0.2.0
func NewAckLedger() *AckLedger
NewAckLedger returns an empty ledger.
func (*AckLedger) Gaps ¶ added in v0.2.0
Gaps returns the still-missing sequence ranges below the highest sequence received, oldest first. An empty result means everything received so far is contiguous (nothing outstanding). These are the explicit gaps the contract requires be surfaced rather than silently tolerated.
func (*AckLedger) HighestContiguous ¶ added in v0.2.0
HighestContiguous returns the ACK: the highest sequence with no hole beneath it (0 = nothing yet).
func (*AckLedger) Observe ¶ added in v0.2.0
Observe records that sequence seq has been received. It returns true if this is the first time seq is seen — INCLUDING a late arrival that fills an earlier gap below the high-water — and false if seq is a true duplicate (idempotent no-op) or invalid (0). This boolean is the authoritative "new vs already-have" signal the ingest path should gate persistence on (together with DeliveryKey), NOT ClassifyDelivery's progress flag. Receiving the sequence just above the contiguous run extends it, absorbing any previously out-of-order sequences that are now contiguous.
The caller should bound an implausible forward jump BEFORE Observe (e.g. reject a batch whose ForwardGap.Missing is absurd): the pending set holds one entry per out-of-order sequence, so an untrusted agent that streams a huge spread of sparse sequences grows memory in proportion to what it sent. That back-pressure/quota is A2/A3's job; Observe itself does no per-call unbounded work.
func (*AckLedger) Pending ¶ added in v0.2.0
Pending returns the received sequences strictly above the contiguous mark, ascending. It is the snapshot A3 persists (with HighestContiguous) so the ledger can be rehydrated across stateless ingests without replaying every sequence.
func (*AckLedger) SeedContiguous ¶ added in v0.2.0
SeedContiguous rehydrates a ledger's contiguous high-water from durable state (A3 persists the ACK mark rather than replaying every sequence). It sets the base only on a fresh ledger; the caller then Observe()s the persisted pending set on top. Seeding a non-empty ledger is a no-op.
type Agent ¶
type Agent struct {
ID shared.ID
TenantID shared.ID
Name string
Platform string
OSVersion string
AgentVersion string
Capabilities []string
// Group is the rollout group an OPERATOR placed this agent in. It is never self-declared: an agent
// that could name its own group could place itself in one pinned to an older, vulnerable version,
// turning a rollout control into a downgrade primitive. Empty means the default group.
Group string
TokenHash string
// Fingerprint is the SHA-256 of the agent's issued client certificate (#408). Empty until a
// certificate is issued from a CSR; it is the cryptographic identity used by mutual-TLS auth.
Fingerprint string
State State
Audit shared.Audit
LastSeenAt time.Time
RevokedAt *time.Time
RevokedBy shared.ID
RevokeReason string
// DecommissionedAt is when the agent self-reported a clean uninstall (#412). Nil unless the agent
// is in StateDecommissioned. There is no "by": decommission is self-reported, not operator-attributed.
DecommissionedAt *time.Time
}
Agent is an enrolled fleet agent. TokenHash is the hash of the bearer credential presented on every call; the plaintext is shown once at enrolment and never stored.
func NewAgent ¶
func NewAgent(id, tenantID shared.ID, name, platform, osVersion, agentVersion string, capabilities []string, tokenHash string, now time.Time) (*Agent, error)
NewAgent validates and constructs an active agent. tokenHash must be the (non-empty) hash of the generated bearer credential; the plaintext is never passed to the domain.
func (*Agent) AssignGroup ¶
AssignGroup places the agent in a rollout group. Validation lives in the rollout domain, which owns what a group name may be; this only records the operator's decision.
func (*Agent) AttestCertificate ¶
AttestCertificate records the SHA-256 fingerprint of the client certificate issued to the agent.
func (*Agent) Decommission ¶
Decommission marks the agent cleanly removed (self-reported on uninstall, #412). It is a no-op on an already-revoked agent — an operator revocation is a stronger, attributed terminal state and must not be overwritten by a self-report. Idempotent: decommissioning an already-decommissioned agent only refreshes the audit timestamp.
func (*Agent) Decommissioned ¶
Decommissioned reports whether the agent was cleanly removed.
func (*Agent) Removed ¶
Removed reports whether the agent's credential may no longer authenticate for any reason — an operator revocation or a self-reported decommission. Auth and coverage both fail closed on it.
type AgentBatch ¶
type AgentBatch struct {
AgentID shared.ID
EngagementID shared.ID
Sequence uint64
KeyID string // the AgentSigningKey this batch is signed with; resolved + verified server-side
Signature string // base64 ed25519 over BatchMessage
Detections []DetectionRef
}
AgentBatch is a sequenced, signed set of detections an agent shipped to the control plane (#423). The sequence is monotonic PER AGENT, so a gap in the sequence is a detectable potential loss rather than a silently-missing batch. The signature is over the batch's canonical membership (id AND content digest) AND its sequence, so neither the membership, the ordinal, nor a detection's CONTENT can be altered without breaking it.
func (AgentBatch) IDs ¶
func (b AgentBatch) IDs() []shared.ID
IDs returns the detection ids in the batch, for callers that only need the membership.
func (AgentBatch) Validate ¶
func (b AgentBatch) Validate() error
Validate checks a batch is well-formed. An empty batch, or one missing its agent/engagement/sequence, or a ref missing its id or content digest, cannot be sequenced/attributed/tamper-checked and is refused.
type AgentBatchV2 ¶ added in v0.2.0
type AgentBatchV2 struct {
Context string `json:"context"`
Version int `json:"version"`
AgentID shared.ID `json:"agent_id"`
EngagementID shared.ID `json:"engagement_id"`
Sequence uint64 `json:"sequence"`
KeyID string `json:"key_id"`
Signature string `json:"signature"`
Detections []DetectionRefV2 `json:"detections"`
}
AgentBatchV2 is a separate wire and signature contract. AgentBatch, BatchMessage, SignBatch, and VerifyBatch remain the v1 contract and must not be changed by v2 callers.
func (AgentBatchV2) Validate ¶ added in v0.2.0
func (b AgentBatchV2) Validate() error
type AgentSigningKey ¶ added in v0.2.0
type AgentSigningKey struct {
KeyID string // evidence.KeyFingerprint(PublicKey); a key names itself, tamper-evident
AgentID shared.ID // the canonical enrolled agent this key is bound to (#606)
Algorithm string // SigningAlgorithm
Purpose SigningPurpose // the one content stream this key may sign
PublicKey ed25519.PublicKey // raw 32-byte ed25519 public key
NotBefore time.Time // key is not usable before this instant
NotAfter time.Time // key is not usable at/after this instant (bounded — supports rotation)
RevokedAt time.Time // zero = not revoked; a set value fails the key closed from that instant
ReplacedBy string // KeyID of the successor minted to rotate this key out; "" if none
}
AgentSigningKey is a first-class content-signing key with a lifecycle. It is minted by NewSigningKey so the KeyID↔PublicKey invariant always holds; a zero-value struct is not a valid key.
func ActiveKeyFor ¶ added in v0.2.0
func ActiveKeyFor(ring []AgentSigningKey, purpose SigningPurpose, now time.Time) (AgentSigningKey, bool)
ActiveKeyFor selects the key to sign with at now among a ring for one purpose: the Active key with the latest NotBefore (so during a rotation OVERLAP the newest usable key is chosen while the older one still verifies inbound traffic). It returns false if no key is currently usable for that purpose.
func NewSigningKey ¶ added in v0.2.0
func NewSigningKey(agentID shared.ID, purpose SigningPurpose, pub ed25519.PublicKey, notBefore, notAfter time.Time) (AgentSigningKey, error)
NewSigningKey mints a signing key from a public key and a validity window, deriving the KeyID from the key itself so KeyID and PublicKey can never disagree. It validates the algorithm, purpose, key size, and that the window is a real bounded interval (NotBefore < NotAfter) — an unbounded key defeats rotation and anti-rollback. Revocation and ReplacedBy are set later, through the lifecycle, not here.
func (AgentSigningKey) SameIdentity ¶ added in v0.2.0
func (k AgentSigningKey) SameIdentity(o AgentSigningKey) bool
SameIdentity reports whether o is the same registration as k by its IMMUTABLE fields only — key id, agent, purpose, algorithm, public key, and window — NOT its lifecycle state (RevokedAt/ReplacedBy). A store uses it to make Register idempotent (a duplicate is a no-op) while refusing an attempt to re-point an existing KeyID at different attributes (anti-rollback → conflict).
func (AgentSigningKey) StatusAt ¶ added in v0.2.0
func (k AgentSigningKey) StatusAt(now time.Time) KeyStatus
StatusAt derives the lifecycle state at now. Revocation takes precedence over the window: a key revoked while still inside its window is Revoked, not Active, from RevokedAt onward.
func (AgentSigningKey) UsableAt ¶ added in v0.2.0
func (k AgentSigningKey) UsableAt(now time.Time) error
UsableAt fails closed: it returns nil ONLY when the key is Active at now, and otherwise a validation error naming why (pending / expired / revoked). Verification paths gate on this before trusting a signature, so a rotated-out or revoked key can never admit a payload.
func (AgentSigningKey) Validate ¶ added in v0.2.0
func (k AgentSigningKey) Validate() error
Validate checks a stored key is internally consistent — the same invariants NewSigningKey enforces, re-asserted after a round-trip through storage so a corrupted or hand-built row is rejected, plus the KeyID↔PublicKey binding (a row whose KeyID does not fingerprint its stored key is refused).
type BoundedProcessObservation ¶ added in v0.2.0
type BoundedProcessObservation struct {
BootID shared.ID
OccurredAt time.Time
ObservedAt time.Time
Process telemetry.ProcessObservation
}
BoundedProcessObservation is one process lifecycle event independently retained from the local durable process sensor. It is intentionally an envelope fragment, not a claim about a response outcome.
type DeliveryClass ¶ added in v0.2.0
type DeliveryClass string
DeliveryClass is how an incoming position relates to the last one accepted for the same stream.
const ( // DeliveryOK — exactly the expected next sequence within the current incarnation. DeliveryOK DeliveryClass = "ok" // DeliveryForwardGap — ahead of the expected next in the current incarnation: one or more sequences are // unaccounted for (a potential loss the caller MUST surface as an explicit gap, never silently accept). DeliveryForwardGap DeliveryClass = "forward_gap" // DeliveryReplay — the incoming batch sequence is not ahead of the high-water within the current // incarnation. This covers BOTH a true duplicate AND a genuine late arrival that fills an earlier gap, // because ClassifyDelivery only knows the scalar high-water, not which sequences were actually received. // It therefore MUST NOT be treated as "discard the payload": the authority for "already have this // signal" is AckLedger.Observe / DeliveryKey (see below), and a below-high-water sequence is frequently // a real gap-fill that closes an outstanding gap. Equating !IsProgress() with drop would silently lose // exactly the signal the contract exists to preserve. DeliveryReplay DeliveryClass = "replay" // DeliveryNewIncarnation — a higher Epoch: a legitimate reboot/reinstall reset, NOT a replay even though // the Sequence dropped back toward 1. If the new incarnation's first sequence is > 1, Missing says how // many were lost across the restart. DeliveryNewIncarnation DeliveryClass = "new_incarnation" // DeliveryStaleIncarnation — a lower Epoch than the one already accepted: an old incarnation resurfacing // (a delayed/duplicated batch from before a restart). Treated as a replay-class event, never accepted as // forward progress. DeliveryStaleIncarnation DeliveryClass = "stale_incarnation" )
type DeliveryOutcome ¶ added in v0.2.0
type DeliveryOutcome struct {
Class DeliveryClass
Missing uint64
}
DeliveryOutcome is the classification plus, when relevant, how many sequences are unaccounted for.
func ClassifyDelivery ¶ added in v0.2.0
func ClassifyDelivery(last, incoming StreamPosition) DeliveryOutcome
ClassifyDelivery compares the last accepted position for a stream against an incoming one and says how they relate — the core of session-aware gap/replay/reset detection. Both positions are assumed to be the SAME priority stream (the caller partitions by priority). A zero-value `last` means nothing has been accepted yet, so the first incarnation is reported as NewIncarnation.
Ordering is by Epoch first (incarnations), then Sequence within an incarnation — never by the opaque Session/Boot ids, which cannot be ordered. This is precisely what keeps a reboot's reset-to-1 from being mistaken for a replay: the reboot advanced Epoch, so it sorts AFTER the previous incarnation.
The caller MUST have Validate()d `incoming` first (Epoch >= 1, Sequence >= 1); a zero-Epoch incoming against a zero-value last would otherwise fall through to the same-incarnation branch and read as a Replay. `last` is the last ACCEPTED position (its zero value means "nothing yet").
func (DeliveryOutcome) HasGap ¶ added in v0.2.0
func (o DeliveryOutcome) HasGap() bool
HasGap reports whether the outcome implies one or more sequences are missing.
func (DeliveryOutcome) IsProgress ¶ added in v0.2.0
func (o DeliveryOutcome) IsProgress() bool
IsProgress reports whether the incoming batch advances the high-water of its incarnation. OK, ForwardGap, and NewIncarnation are progress; Replay and StaleIncarnation are not.
IsProgress is a classification of ORDERING, not a persistence decision. A caller must NOT read !IsProgress() as "drop this batch": a Replay may be a genuine gap-fill below the high-water. Decide idempotent persistence per event via DeliveryKey (dedup) and AckLedger.Observe (which returns true for a first-seen sequence — including a gap-fill — and false only for a true duplicate). Use IsProgress only to drive high-water/gap bookkeeping.
type DeliveryPriority ¶ added in v0.2.0
type DeliveryPriority int
DeliveryPriority is the spool/delivery priority class of a signal. The agent's durable spool drains by class so the signals that matter most for a live engagement survive back-pressure and disk pressure first. The ladder is the contract; assigning a concrete signal to a class is the producer's job (A2).
const ( // PriorityP0 — response-verification results, coverage/honesty, and sensor-health. Losing these would // make the platform lie about what it did or saw, so they drain first and are evicted last. PriorityP0 DeliveryPriority = iota // PriorityP1 — confirmed detections. PriorityP1 // PriorityP2 — privilege changes and critical-file events. PriorityP2 // PriorityP3 — background process/network telemetry (the highest-volume, most-sheddable stream). PriorityP3 )
func TelemetryPriority ¶ added in v0.2.0
func TelemetryPriority(class detection.Class) (DeliveryPriority, error)
TelemetryPriority assigns canonical raw telemetry to the A2 priority ladder. Privilege transitions and sensitive-file observations are never-shed P2; high-volume process/network observations are evictable P3. P0 and P1 are reserved for coverage/verification and confirmed detections respectively.
func (DeliveryPriority) String ¶ added in v0.2.0
func (p DeliveryPriority) String() string
String renders the priority as its stable wire label (P0..P3).
func (DeliveryPriority) Valid ¶ added in v0.2.0
func (p DeliveryPriority) Valid() bool
Valid reports whether p is one of the defined priority classes.
type DetectionAttribution ¶ added in v0.2.0
type DetectionAttribution struct {
TelemetryRefs []TelemetryReference
Rulepack RulepackReference
RedactionPolicyDigest string
}
DetectionAttribution is the typed source-side seam for sources that possess real telemetry transport references. Existing DetectionSink.Emit stays a legacy v1 path because DetectionSensor does not expose such references; callers cannot populate this type by guessing from a detection's host or timestamp.
func (DetectionAttribution) Validate ¶ added in v0.2.0
func (a DetectionAttribution) Validate() error
type DetectionBatchItem ¶ added in v0.2.0
DetectionBatchItem is one detection body covered by an AgentBatch ref. It lives beside the batch contract so the agent transport and control-plane ingest share the exact same wire shape without an infrastructure package importing a server-side use case. AssetID is explicit because the asset is part of DetectionContentHash and must therefore be verified under the batch signature.
func (DetectionBatchItem) Validate ¶ added in v0.2.0
func (i DetectionBatchItem) Validate() error
Validate checks the item before it is signed or admitted by the control plane.
type DetectionBatchItemV2 ¶ added in v0.2.0
type DetectionBatchItemV2 struct {
ID shared.ID `json:"id"`
Detection detection.Detection `json:"detection"`
AssetID shared.ID `json:"asset_id"`
TelemetryRefs []TelemetryReference `json:"telemetry_refs"`
Rulepack RulepackReference `json:"rulepack"`
RedactionPolicyDigest string `json:"redaction_policy_digest"`
}
DetectionBatchItemV2 carries the full body corresponding to a v2 signed manifest entry.
func (DetectionBatchItemV2) Reference ¶ added in v0.2.0
func (i DetectionBatchItemV2) Reference() (DetectionRefV2, error)
func (DetectionBatchItemV2) Validate ¶ added in v0.2.0
func (i DetectionBatchItemV2) Validate() error
type DetectionEvidenceEnvelope ¶ added in v0.2.0
type DetectionEvidenceEnvelope struct {
Context string `json:"context"`
EnvelopeVersion int `json:"envelope_version"`
TenantID shared.ID `json:"tenant_id"`
EngagementID shared.ID `json:"engagement_id"`
AgentID shared.ID `json:"agent_id"`
AssetID shared.ID `json:"asset_id"`
DetectionID shared.ID `json:"detection_id"`
BatchSequence uint64 `json:"batch_sequence"`
KeyID string `json:"key_id"`
RuleID string `json:"rule_id"`
RuleVersion int `json:"rule_version"`
ContentSHA256 string `json:"content_sha256"`
Provenance ProvenanceState `json:"provenance"`
ObservedAt time.Time `json:"observed_at"`
Detection detection.Detection `json:"detection"`
}
DetectionEvidenceEnvelope is the SELF-CONTAINED permanent chain link for a sealed detection (A5, #626). It carries everything needed to attribute and verify the detection — tenant/agent/asset/engagement, the batch identity that admitted it, the agent's content commitment (ContentSHA256, the same digest the agent signed), the rule provenance, and the detection itself — so the link remains verifiable and explainable AFTER the expirable projection row is swept. It deliberately carries NO wall-clock "sealed at": the sealed bytes must be byte-identical across an idempotent re-seal (SealOnce compares content), so every field is derived from the batch/detection, never from the ingest clock.
func DecodeDetectionEvidenceEnvelope ¶ added in v0.2.0
func DecodeDetectionEvidenceEnvelope(content []byte) (DetectionEvidenceEnvelope, error)
DecodeDetectionEvidenceEnvelope parses a sealed chain link's content back into a validated envelope, so a detection stays explainable from the permanent chain after its projection has expired.
func NewDetectionEvidenceEnvelope ¶ added in v0.2.0
func NewDetectionEvidenceEnvelope(tenantID, engagementID, agentID, assetID, detectionID shared.ID, batchSequence uint64, keyID, contentSHA256 string, prov ProvenanceState, det detection.Detection) (DetectionEvidenceEnvelope, error)
NewDetectionEvidenceEnvelope builds a validated envelope from the admitted batch identity and detection. contentSHA256 is the agent-signed content commitment (the verified DetectionContentHash), embedded so the link is self-verifying against its detection without the projection.
func (DetectionEvidenceEnvelope) Canonical ¶ added in v0.2.0
func (e DetectionEvidenceEnvelope) Canonical() ([]byte, error)
Canonical returns the deterministic bytes sealed into the evidence chain. It is stable across an idempotent re-seal (no wall-clock fields), so SealOnce's content comparison converges on a retry.
func (DetectionEvidenceEnvelope) Validate ¶ added in v0.2.0
func (e DetectionEvidenceEnvelope) Validate() error
Validate checks the envelope is well-formed and internally consistent.
func (DetectionEvidenceEnvelope) VerifyContent ¶ added in v0.2.0
func (e DetectionEvidenceEnvelope) VerifyContent() error
VerifyContent recomputes the detection content commitment and checks it equals the embedded, agent-signed ContentSHA256 — so a link tampered after sealing (detection body changed) is detectable from the envelope alone, with no projection needed.
type DetectionEvidenceEnvelopeV2 ¶ added in v0.2.0
type DetectionEvidenceEnvelopeV2 struct {
Context string `json:"context"`
EnvelopeVersion int `json:"envelope_version"`
TenantID shared.ID `json:"tenant_id"`
EngagementID shared.ID `json:"engagement_id"`
AgentID shared.ID `json:"agent_id"`
AssetID shared.ID `json:"asset_id"`
DetectionID shared.ID `json:"detection_id"`
BatchSequence uint64 `json:"batch_sequence"`
KeyID string `json:"key_id"`
ContentSHA256 string `json:"content_sha256"`
TelemetryRefs []TelemetryReference `json:"telemetry_refs"`
Rulepack RulepackReference `json:"rulepack"`
RedactionPolicyDigest string `json:"redaction_policy_digest"`
ObservedAt time.Time `json:"observed_at"`
Detection detection.Detection `json:"detection"`
}
DetectionEvidenceEnvelopeV2 commits the v2 signed attribution as immutable evidence. It is a distinct envelope so the v1 envelope's canonical bytes and decoder contract remain unchanged.
func DecodeDetectionEvidenceEnvelopeV2 ¶ added in v0.2.0
func DecodeDetectionEvidenceEnvelopeV2(content []byte) (DetectionEvidenceEnvelopeV2, error)
DecodeDetectionEvidenceEnvelopeV2 decodes a v2 envelope without broadening the v1 decoder.
func NewDetectionEvidenceEnvelopeV2 ¶ added in v0.2.0
func NewDetectionEvidenceEnvelopeV2(tenantID, engagementID, agentID shared.ID, batchSequence uint64, keyID string, item DetectionBatchItemV2) (DetectionEvidenceEnvelopeV2, error)
NewDetectionEvidenceEnvelopeV2 builds a self-contained immutable v2 commitment.
func (DetectionEvidenceEnvelopeV2) Canonical ¶ added in v0.2.0
func (e DetectionEvidenceEnvelopeV2) Canonical() ([]byte, error)
func (DetectionEvidenceEnvelopeV2) Validate ¶ added in v0.2.0
func (e DetectionEvidenceEnvelopeV2) Validate() error
func (DetectionEvidenceEnvelopeV2) VerifyContent ¶ added in v0.2.0
func (e DetectionEvidenceEnvelopeV2) VerifyContent() error
type DetectionRef ¶
DetectionRef binds a detection id to a digest of its content, so the batch signature covers WHAT each detection is, not merely its id. ContentSHA256 is the hex sha256 the agent computed over the exact bytes it is shipping (see DetectionContentHash); the control plane recomputes it and refuses a mismatch, so a transport that swaps a detection's body for a known id is rejected before sealing.
type DetectionRefV2 ¶ added in v0.2.0
type DetectionRefV2 struct {
ID shared.ID `json:"id"`
ContentSHA256 string `json:"content_sha256"`
AssetID shared.ID `json:"asset_id"`
TelemetryRefs []TelemetryReference `json:"telemetry_refs"`
Rulepack RulepackReference `json:"rulepack"`
RedactionPolicyDigest string `json:"redaction_policy_digest"`
}
DetectionRefV2 is the immutable, signature-covered manifest entry for a v2 detection.
func (DetectionRefV2) Validate ¶ added in v0.2.0
func (r DetectionRefV2) Validate() error
type DetectionSpoolPayload ¶ added in v0.2.0
type DetectionSpoolPayload struct {
Version int `json:"version"`
Item DetectionBatchItemV2 `json:"item"`
ObservedAt time.Time `json:"observed_at"`
}
DetectionSpoolPayload allows v2 attribution to survive the detection WAL. Version 1 remains a deliberately un-attributed legacy payload and is decoded by the shipper for compatibility.
func (DetectionSpoolPayload) Validate ¶ added in v0.2.0
func (p DetectionSpoolPayload) Validate() error
type EnrolToken ¶
type EnrolToken struct {
Hash string
TenantID shared.ID
IssuedBy string
ExpiresAt time.Time
UsedAt time.Time // zero until consumed
CreatedAt time.Time
}
EnrolToken is a single-use, tenant-scoped, expiring token an operator issues so an agent can enrol once. Only its hash is stored.
func NewEnrolToken ¶
func NewEnrolToken(hash string, tenantID shared.ID, issuedBy string, expiresAt, now time.Time) (*EnrolToken, error)
NewEnrolToken validates and constructs an enrolment token record from the token's hash.
type EventRef ¶ added in v0.2.0
type EventRef struct {
ID shared.ID
Digest string // hex sha256 over the event's canonical shipped bytes
}
EventRef binds one shipped telemetry event's id to a digest of its canonical bytes, so the manifest signature covers WHAT each event is, not merely its id — a transport that swaps an event body for a known id is rejected before ingest.
type KeyStatus ¶ added in v0.2.0
type KeyStatus string
KeyStatus is a signing key's lifecycle state AT A GIVEN TIME. It is derived (never stored) from the window + revocation, so the same stored key reads Pending, Active, Expired, or Revoked as time moves.
type PendingDetectionV2 ¶ added in v0.2.0
type PendingDetectionV2 struct {
Batch AgentBatchV2 `json:"batch"`
Item DetectionBatchItemV2 `json:"item"`
}
PendingDetectionV2 is the verified immutable input retained until custody completes.
func DecodePendingDetectionV2 ¶ added in v0.2.0
func DecodePendingDetectionV2(content []byte) (PendingDetectionV2, error)
func (PendingDetectionV2) Canonical ¶ added in v0.2.0
func (p PendingDetectionV2) Canonical() ([]byte, error)
type ProvenanceState ¶ added in v0.2.0
type ProvenanceState string
ProvenanceState is the durability/consistency state of a sealed detection's evidence (A5, #626).
What the IMMUTABLE envelope records is the detection-evidence commitment at admission: once the detection (and its own embedded evidence) is sealed into the permanent chain it is ProvenanceComplete — that is the only state a stable, content-compared seal may honestly assert, since the sealed bytes must be identical across an idempotent re-seal. The remaining states are the QUERY/READ-LAYER lifecycle over a detection's evidence: Pending while the SEPARATE raw-telemetry stream (A3) that a detection summarizes is not yet confirmed durable, Expired when the expirable projection is swept, Broken when chain verification fails. Those transitions are tracked on the mutable projection, never by rewriting the immutable link — wiring the live telemetry-durability cross-check is the remaining A5 tail.
const ( // ProvenancePending: read-layer state — the raw telemetry stream a detection summarizes is not yet // confirmed durable. A detection over not-yet-durable telemetry must NOT be presented as complete. ProvenancePending ProvenanceState = "pending" // ProvenanceComplete: the detection evidence is durably sealed into the permanent chain. This is the // admission state carried by the immutable envelope. ProvenanceComplete ProvenanceState = "complete" // ProvenanceExpired: read-layer state — the expirable projection view has aged out under retention; the // permanent envelope still stands and remains verifiable on its own. ProvenanceExpired ProvenanceState = "expired" // ProvenanceBroken: read-layer state — chain/consistency verification failed for this detection's evidence. ProvenanceBroken ProvenanceState = "broken" )
func (ProvenanceState) Valid ¶ added in v0.2.0
func (p ProvenanceState) Valid() bool
Valid reports whether p is a known provenance state.
type ResponseCommand ¶ added in v0.2.0
type ResponseCommand struct {
ProtocolVersion int
CommandID shared.ID
TenantID shared.ID
AgentID shared.ID
AssetID shared.ID
EngagementID shared.ID
Action rdom.Action
ActionDigest string
AttemptKey string
VerificationChallenge string
AuthorizationTarget engagement.Target
Target responsesaga.TargetFingerprint
Reversal bool
HaltGeneration int64
IssuedAt time.Time
NotAfter time.Time
SigningKeyID string
Signature string
}
ResponseCommand is the control-plane-authorized command accepted by the endpoint response actuator. The signature covers every field that can change what executes. The first protocol version deliberately supports process stop/restart only; other response kinds require their own target-resolution review.
func (ResponseCommand) Validate ¶ added in v0.2.0
func (c ResponseCommand) Validate() error
type ResponseCommandSigningKey ¶ added in v0.2.0
type ResponseCommandSigningKey struct {
KeyID string
PublicKey ed25519.PublicKey
NotBefore time.Time
NotAfter time.Time
RevokedAt time.Time
}
ResponseCommandSigningKey is one trusted control-plane command key. KeyID is the public-key fingerprint; bounded validity and monotonic revocation allow explicit overlap during rotation.
func NewResponseCommandSigningKey ¶ added in v0.2.0
func (ResponseCommandSigningKey) UsableAt ¶ added in v0.2.0
func (k ResponseCommandSigningKey) UsableAt(at time.Time) error
func (ResponseCommandSigningKey) Validate ¶ added in v0.2.0
func (k ResponseCommandSigningKey) Validate() error
type ResponseExecutionJournalEntry ¶ added in v0.2.0
type ResponseExecutionJournalEntry struct {
Version int
Command ResponseCommand
CommandDigest string
LeaseID string
LeaseUntil time.Time
State ResponseExecutionState
Result *ResponseExecutionResult
ResultAcknowledged bool
}
func (ResponseExecutionJournalEntry) Validate ¶ added in v0.2.0
func (e ResponseExecutionJournalEntry) Validate() error
type ResponseExecutionResult ¶ added in v0.2.0
type ResponseExecutionResult struct {
AttemptKey string
CommandDigest string
LeaseID string
State ResponseExecutionState
ObservedRadius offensivepolicy.Radius
AffectedCount int
AlreadyApplied bool
CompletedAt time.Time
}
ResponseExecutionResult is the local durable command outcome. It is not verification evidence; independent post-condition telemetry remains authoritative for VerifiedSucceeded.
func (ResponseExecutionResult) Validate ¶ added in v0.2.0
func (r ResponseExecutionResult) Validate() error
type ResponseExecutionState ¶ added in v0.2.0
type ResponseExecutionState string
const ( ResponseExecutionPrepared ResponseExecutionState = "prepared" ResponseExecutionExecuting ResponseExecutionState = "executing" ResponseExecutionApplied ResponseExecutionState = "applied" ResponseExecutionOutcomeUnknown ResponseExecutionState = "outcome_unknown" )
func (ResponseExecutionState) Valid ¶ added in v0.2.0
func (s ResponseExecutionState) Valid() bool
type ResponseHaltCommand ¶ added in v0.2.0
type ResponseHaltCommand struct {
ProtocolVersion int
CommandID shared.ID
TenantID shared.ID
AgentID shared.ID
AssetID shared.ID
Generation int64
AttemptKey string
IssuedAt time.Time
NotAfter time.Time
SigningKeyID string
Signature string
}
ResponseHaltCommand durably raises one endpoint's monotonic response fence. It is separate from effect commands so a halt can carry no action or target that could itself change production state.
func (ResponseHaltCommand) Validate ¶ added in v0.2.0
func (c ResponseHaltCommand) Validate() error
type ResponseObservation ¶ added in v0.2.0
type ResponseObservation struct {
TargetExit BoundedProcessObservation
Replacement *BoundedProcessObservation
}
ResponseObservation is the only process evidence an observer may submit for an addressed request. Forward observations contain an actual target exit. Reversal observations additionally contain the unique actual replacement observed later.
func (ResponseObservation) ValidateFor ¶ added in v0.2.0
func (o ResponseObservation) ValidateFor(request ResponseObservationRequest) error
type ResponseObservationRequest ¶ added in v0.2.0
type ResponseObservationRequest struct {
ProtocolVersion int
RequestID shared.ID
TenantID shared.ID
ObserverAgentID shared.ID
AssetID shared.ID
EngagementID shared.ID
ActionID shared.ID
ActionDigest string
AttemptKey string
VerificationChallenge string
ReceiptID shared.ID
ReceiptDigest string
Target responsesaga.TargetFingerprint
Reversal bool
AttemptedAt time.Time
IssuedAt time.Time
NotAfter time.Time
}
ResponseObservationRequest asks an independently enrolled agent to emit a signed readiness report. It deliberately carries no verdict, process state, or coverage assertion.
func (ResponseObservationRequest) Validate ¶ added in v0.2.0
func (r ResponseObservationRequest) Validate() error
type ResponseObserverBinding ¶ added in v0.2.0
type ResponseObserverBinding struct {
TenantID shared.ID
AgentID shared.ID
AssetID shared.ID
AssignedBy string
AssignedAt time.Time
ExpiresAt time.Time
Version int
}
ResponseObserverBinding is the server-owned authorization for a secondary agent to observe one canonical asset without becoming that asset's primary inventory owner.
func (ResponseObserverBinding) ActiveAt ¶ added in v0.2.0
func (b ResponseObserverBinding) ActiveAt(now time.Time) bool
func (ResponseObserverBinding) Validate ¶ added in v0.2.0
func (b ResponseObserverBinding) Validate() error
type ResponseTargetEvidenceReceipt ¶ added in v0.2.0
type ResponseTargetEvidenceReceipt struct {
Version int `json:"version"`
ReceiptID shared.ID `json:"receipt_id"`
Digest string `json:"digest"`
TenantID shared.ID `json:"tenant_id"`
EngagementID shared.ID `json:"engagement_id"`
ActionID shared.ID `json:"action_id"`
ActionDigest string `json:"action_digest"`
AttemptKey string `json:"attempt_key"`
VerificationChallenge string `json:"verification_challenge"`
Target responsesaga.TargetFingerprint `json:"target"`
Reversal bool `json:"reversal"`
AttemptedAt time.Time `json:"attempted_at"`
WindowUntil time.Time `json:"window_until"`
RecordedAt time.Time `json:"recorded_at"`
SourceAgentID shared.ID `json:"source_agent_id"`
SourceAgentSessionID shared.ID `json:"source_agent_session_id"`
SourceHostID shared.ID `json:"source_host_id"`
Timeline []endpoint.TimelineEntry `json:"timeline"`
Coverage []sensorstate.CoverageWindow `json:"coverage"`
TimelineComplete bool `json:"timeline_complete"`
CoverageComplete bool `json:"coverage_complete"`
Saturated bool `json:"saturated"`
Reasons []string `json:"reasons"`
}
ResponseTargetEvidenceReceipt is the immutable, server-generated snapshot of bounded target-primary telemetry. It is deliberately independent of the observer that later purpose-signs its binding.
func CloneResponseTargetEvidenceReceipt ¶ added in v0.2.0
func CloneResponseTargetEvidenceReceipt(r ResponseTargetEvidenceReceipt) ResponseTargetEvidenceReceipt
CloneResponseTargetEvidenceReceipt returns an independent copy of every mutable receipt field.
func (ResponseTargetEvidenceReceipt) Validate ¶ added in v0.2.0
func (r ResponseTargetEvidenceReceipt) Validate() error
type ResponseVerificationReport ¶ added in v0.2.0
type ResponseVerificationReport struct {
ProtocolVersion int
ReportID shared.ID
AgentID shared.ID
HostID shared.ID
AgentSessionID SessionID
AssetID shared.ID
EngagementID shared.ID
ActionID shared.ID
ActionDigest string
AttemptKey string
VerificationChallenge string
ReceiptID shared.ID
ReceiptDigest string
Target responsesaga.TargetFingerprint
Reversal bool
ObservedAt time.Time
ReplacementProcessEntityID shared.ID
KeyID string
Signature string
}
ResponseVerificationReport is a purpose-signed P0 readiness observation produced by a response observer, never by the command result endpoint. It carries no verdict, process state, or coverage claim: the control plane derives those from accepted endpoint timeline and coverage records. A reversal may nominate the distinct replacement entity whose lifecycle the control plane must independently confirm.
func (ResponseVerificationReport) ObserverIdentity ¶ added in v0.2.0
func (r ResponseVerificationReport) ObserverIdentity() string
ObserverIdentity returns the content-signing principal persisted as source provenance. It is distinct from transport authentication and from the response executor identity.
func (ResponseVerificationReport) Validate ¶ added in v0.2.0
func (r ResponseVerificationReport) Validate() error
type RulepackReference ¶ added in v0.2.0
type RulepackReference struct {
ID string `json:"id"`
Version int `json:"version"`
Digest string `json:"digest"`
}
RulepackReference binds a detection to the exact rulepack artifact that supplied its rule.
func (RulepackReference) Validate ¶ added in v0.2.0
func (r RulepackReference) Validate() error
type SensorStateReport ¶ added in v0.2.0
type SensorStateReport struct {
ProtocolVersion int
ReportID shared.ID
AgentID shared.ID
HostID shared.ID
AgentSessionID SessionID
AssetID shared.ID
// ResponseObservationID is optional and permits target sensor state only for a live,
// addressed response-observation work order; it never changes general host ownership.
ResponseObservationID shared.ID `json:"response_observation_id,omitempty"`
Kind string
ObservedAt time.Time
SchemaVersion int
PayloadDigest string
States []detection.ClassCoverage
KeyID string
Signature string
}
SensorStateReport is a signed, self-contained P0 sensor-state/coverage record. It deliberately uses the existing telemetry signing key, but a separate message context prevents replay as a raw telemetry batch or spool gap.
func (SensorStateReport) Validate ¶ added in v0.2.0
func (r SensorStateReport) Validate() error
type SeqRange ¶ added in v0.2.0
SeqRange is an inclusive range of sequence numbers, used to report gaps compactly.
type SequenceGap ¶
type SequenceGap struct {
// Missing is how many batches are unaccounted for between the last seen and this one. > 0 means a
// potential loss the caller must surface (a batch_gap coverage event), never silently accept.
Missing uint64
// Replay is true when the incoming sequence is not ahead of the last seen (duplicate or out-of-order),
// which is also suspicious and must be reported rather than double-counted.
Replay bool
}
SequenceGap describes what an incoming batch sequence implies against the last one seen for an agent.
func DetectSequenceGap ¶
func DetectSequenceGap(lastSeen, incoming uint64) SequenceGap
DetectSequenceGap compares an incoming batch sequence against the last sequence recorded for that agent (0 = none yet). The expected next sequence is lastSeen+1; anything higher leaves a gap, anything not higher is a replay/out-of-order.
This is the single-counter predecessor of the delivery contract (#609): it cannot tell a legitimate reboot reset-to-1 apart from a replay. It is superseded by ClassifyDelivery (delivery.go), which is incarnation-aware; A3 adopts ClassifyDelivery once the wire envelope carries a StreamPosition.
func (SequenceGap) HasGap ¶
func (g SequenceGap) HasGap() bool
HasGap reports whether this sequence transition is anything other than the expected next-in-line.
type SessionID ¶ added in v0.2.0
type SessionID string
SessionID identifies one agent run/enrolment session; BootID identifies one host boot. Both are opaque (typically random) — they are recorded for attribution and to detect corruption, but ordering is decided by the Epoch, because random ids cannot be compared. A reinstall changes the Session; a reboot changes the Boot; either resets the per-stream Sequence, and the agent signals that reset by advancing Epoch.
func CanonicalSessionID ¶ added in v0.2.0
CanonicalSessionID derives the stable A0.1 AgentSessionID from the server-minted enrolled AgentID. AgentID changes on re-enrol/reinstall, so the derived session changes with the enrollment session while remaining stable across ordinary process restarts. The value is not an authenticator: the control plane always recomputes it from the authenticated AgentID and rejects a wire value that disagrees.
type SigningPurpose ¶ added in v0.2.0
type SigningPurpose string
SigningPurpose is the single content stream a key may sign. A key is minted for exactly one purpose so a signature over one stream can never be replayed as another (domain separation).
const ( PurposeTelemetryBatch SigningPurpose = "telemetry-batch" PurposeDetectionBatch SigningPurpose = "detection-batch" PurposeResponseResult SigningPurpose = "response-result" )
func (SigningPurpose) Valid ¶ added in v0.2.0
func (p SigningPurpose) Valid() bool
Valid reports whether p is one of the known purposes.
type State ¶
type State string
State is the agent lifecycle.
const ( StateActive State = "active" StateStale State = "stale" // last seen longer ago than the freshness threshold (computed by fleet coverage) // StateRevoked is an OPERATOR decision: the credential is killed with attribution. StateRevoked State = "revoked" // StateDecommissioned is a terminal state the AGENT reports when it is cleanly uninstalled (#412). // It is distinct from Revoked (an operator killed it) and from Stale (it silently stopped reporting): // the control plane shows an orderly removal rather than leaving the identity to rot into Stale. A // decommissioned agent can no longer authenticate. StateDecommissioned State = "decommissioned" )
type StreamPosition ¶ added in v0.2.0
type StreamPosition struct {
Priority DeliveryPriority
Epoch uint64 // incarnation counter; increments on every Sequence reset (reboot/reinstall/spool-loss)
Sequence uint64 // monotonic within (Priority, Epoch); restarts at 1 when Epoch increments
Session SessionID
Boot BootID
}
StreamPosition is where an agent is in one priority stream. Sequence is monotonic within an INCARNATION (Epoch); on a reboot/reinstall/spool-loss the agent starts a new incarnation — Epoch increments and Sequence restarts at 1. Carrying a monotonic Epoch (not just the opaque Session/Boot) is what lets the control plane tell a legitimate reset-to-1 apart from a replay of an old sequence: a higher Epoch is a new incarnation, never a replay.
func (StreamPosition) Validate ¶ added in v0.2.0
func (p StreamPosition) Validate() error
Validate checks a position is well-formed: a real priority, an incarnation and sequence that both start at 1, and non-empty session/boot ids (so every accepted signal is attributable to a concrete run+boot).
type TelemetryBatchManifest ¶ added in v0.2.0
type TelemetryBatchManifest struct {
ProtocolVersion int
SchemaVersion int
BatchID shared.ID
AgentID shared.ID
HostID shared.ID
AssetID shared.ID
// ResponseObservationID is optional and, when present, binds target telemetry to one live,
// addressed response-observation work order. It is never used to relabel general host telemetry.
ResponseObservationID shared.ID `json:"response_observation_id,omitempty"`
StreamID shared.ID
// Position carries Priority, Epoch, Sequence, Session (== the AgentSessionID) and Boot — the
// incarnation-aware stream coordinate ClassifyDelivery/AckLedger reason over.
Position StreamPosition
PreviousSequence uint64
EventTimeMin time.Time
EventTimeMax time.Time
// The three loss outcomes are separate buckets; per-event reason lives on the first-class
// TelemetryLoss/TelemetryGap, not here.
ObservedCount int
KeptCount int
SampledOutCount int
TruncatedCount int
DroppedCount int
SamplingPolicyDigest string // digest of {SamplingAlgorithm, SamplingPolicyID, Seed, Version}
// Events are the KeptCount shipped events, each bound by id + content digest.
Events []EventRef
// PayloadDigest commits to the canonical uncompressed transport payload (the events' shipped bytes).
PayloadDigest string
KeyID string // the AgentSigningKey (Purpose=telemetry-batch) this manifest is signed with
Signature string // base64 ed25519 over TelemetryManifestMessage
}
TelemetryBatchManifest is a sequenced, signed manifest for one shipped telemetry batch (A3, #624). It binds the batch to a stream position (incarnation-aware, so a reboot reset-to-1 is not a replay — the A0.4 delivery contract), carries the three DISTINCT loss counts (Sampled ≠ Truncated ≠ Dropped, per D2/A0.6), and commits to the batch payload. AgentID/HostID/AssetID/AgentSessionID are carried so the control plane can compare every claimed identity coordinate with its authenticated/server-side binding. This is the TRANSPORT-integrity + ACK commitment only; the permanent evidence commitment (retention/proof/Merkle) is A5's, not computed here.
func (TelemetryBatchManifest) AgentSessionID ¶ added in v0.2.0
func (m TelemetryBatchManifest) AgentSessionID() SessionID
AgentSessionID returns the incarnation's session id (the manifest's Position.Session).
func (TelemetryBatchManifest) Validate ¶ added in v0.2.0
func (m TelemetryBatchManifest) Validate() error
Validate checks the manifest is well-formed and internally consistent. A malformed manifest — missing identity, an inconsistent kept-vs-event count, or a bad loss arithmetic — is refused before any signature check or ingest.
type TelemetryGapReason ¶ added in v0.2.0
type TelemetryGapReason string
TelemetryGapReason is the stable wire vocabulary for durable A2 loss evidence. It intentionally mirrors the spool reasons without importing usecase ports into domain.
const ( TelemetryGapQuotaEviction TelemetryGapReason = "quota_eviction" TelemetryGapQuotaBackpressure TelemetryGapReason = "quota_backpressure" TelemetryGapCorruptFrame TelemetryGapReason = "corrupt_frame" TelemetryGapTornWrite TelemetryGapReason = "torn_write" TelemetryGapIOFailure TelemetryGapReason = "io_failure" TelemetryGapUnsyncedTail TelemetryGapReason = "unsynced_tail" TelemetryGapStateRecovery TelemetryGapReason = "state_recovery" )
func (TelemetryGapReason) Valid ¶ added in v0.2.0
func (r TelemetryGapReason) Valid() bool
type TelemetryGapReport ¶ added in v0.2.0
type TelemetryGapReport struct {
ProtocolVersion int
GapID shared.ID
AgentID shared.ID
HostID shared.ID
AgentSessionID SessionID
AssetID shared.ID
StreamID shared.ID
Priority DeliveryPriority
Epoch uint64
KnownSequence bool
FromSequence uint64
ToSequence uint64
Count uint64
Reason TelemetryGapReason
FromAt time.Time
ToAt time.Time
KeyID string
Signature string
}
TelemetryGapReport is a signed, idempotent report of durable loss discovered by the agent spool. GapID is stable across retries and coalescing extensions. The server derives/validates AgentID, HostID, AgentSessionID, AssetID and StreamID against authenticated state before persisting it.
func (TelemetryGapReport) Validate ¶ added in v0.2.0
func (g TelemetryGapReport) Validate() error
type TelemetryReference ¶ added in v0.2.0
type TelemetryReference struct {
StreamID shared.ID `json:"stream_id"`
Epoch uint64 `json:"epoch"`
Sequence uint64 `json:"sequence"`
EventID shared.ID `json:"event_id"`
Digest string `json:"digest"`
}
TelemetryReference identifies one durable transport event that causally supports a detection. It is deliberately transport-coordinate based: callers must not synthesize a reference from a host id, timestamp, or rendered evidence.
func (TelemetryReference) Validate ¶ added in v0.2.0
func (r TelemetryReference) Validate() error
Source Files
¶
- batch.go
- delivery.go
- detection_evidence.go
- detection_v2.go
- fleetagent.go
- response_command.go
- response_halt_command.go
- response_observation.go
- response_observer_binding.go
- response_target_evidence_receipt.go
- response_verification.go
- sampling_policy.go
- sensor_state.go
- session.go
- signingkey.go
- telemetry_batch.go
- telemetry_gap.go
- telemetry_stream.go