Normalize bounded APT agent clock skew

This commit is contained in:
rcourtman
2026-07-12 20:44:21 +01:00
parent 9981e370bb
commit 8b2eef10b4
2 changed files with 25 additions and 1 deletions
+10 -1
View File
@@ -39,10 +39,19 @@ func hostAPTExecutionResult(resourceID, agentID, operation, output string, succe
verificationTruth.ReasonCode = "stale_agent_readback"
verificationTruth.Summary = "The agent readback was stale, skewed, or had invalid mutation chronology."
} else {
evidenceObservedAt := afterObservedAt.UTC()
evidenceReceivedAt := receivedAt.UTC()
// Validation permits the agent clock to be slightly ahead of the
// server. Canonical evidence cannot claim an observation after its
// receipt, so conservatively bind bounded positive skew to the
// server receipt boundary.
if evidenceObservedAt.After(evidenceReceivedAt) {
evidenceObservedAt = evidenceReceivedAt
}
evidence, err := unified.NormalizeActionEvidence(unified.ActionEvidence{
Version: unified.ActionEvidenceVersion, ID: operation + "-agent-readback", ObserverID: agentID,
ObserverKind: "unified_agent", ObserverTrustDomain: "agent:" + agentID, ExecutorTrustDomain: "agent:" + agentID,
Method: "typed_read_after_write", SubjectID: resourceID, ObservedAt: afterObservedAt.UTC(), ReceivedAt: receivedAt.UTC(),
Method: "typed_read_after_write", SubjectID: resourceID, ObservedAt: evidenceObservedAt, ReceivedAt: evidenceReceivedAt,
Summary: output,
})
if err == nil {
@@ -52,6 +52,21 @@ func TestHostAPTActionResultFutureVerifiedClaimFailsClosed(t *testing.T) {
}
}
func TestHostAPTActionResultBoundsPermittedPositiveClockSkewToReceipt(t *testing.T) {
now := time.Date(2026, 7, 12, 9, 0, 0, 0, time.UTC)
result, err := hostAPTExecutionResult("agent:host-1", "host-1", agentexec.HostStorageCleanupOperationPackageCache, "cleanup complete", true, true, agentexec.HostStorageCleanupVerificationVerified, true, false, false, false, false, now.Add(-time.Second), now.Add(time.Second), now, now)
if err != nil {
t.Fatal(err)
}
truth := result.ActionResultV2.Verification
if truth.Status != unified.ActionVerificationConfirmed || truth.EvidenceClass != unified.ActionEvidenceAgentAttested || len(truth.Evidence) != 1 {
t.Fatalf("bounded clock skew lost verified readback: %#v", truth)
}
if !truth.Evidence[0].ObservedAt.Equal(now) || !truth.Evidence[0].ReceivedAt.Equal(now) {
t.Fatalf("bounded clock skew was not conservatively normalized: %#v", truth.Evidence[0])
}
}
func TestHostAPTActionResultStaleReadbackFailsClosed(t *testing.T) {
now := time.Date(2026, 7, 12, 9, 0, 0, 0, time.UTC)
result, err := hostAPTExecutionResult("agent:host-1", "host-1", agentexec.HostUpdateOperationInstall, "updates complete", true, true, agentexec.HostUpdateVerificationVerified, true, true, true, true, false, now.Add(-time.Hour-time.Minute), now.Add(-time.Hour), now, now)