mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 18:45:53 +00:00
Require independent evidence for verified findings
This commit is contained in:
@@ -4480,3 +4480,12 @@ capabilities and readiness. The fake-only code/test floor for claims 16 and 17
|
||||
is implemented; both claims and workflow scorecards remain below operational
|
||||
completion until browser proof, disposable Debian/Ubuntu lab proof, and Task 12
|
||||
certification are complete.
|
||||
|
||||
RG-06 and RG-09 keep the executing agent's fresh typed readback classified as
|
||||
`agent_attested`. A separate Colima control-plane observation proves the lab
|
||||
fixture changed, but it does not enter the authenticated product action result
|
||||
and therefore cannot upgrade the product finding to `fix_verified`. Until a
|
||||
distinct-trust-domain observation is ingested into `ActionResultV2`, those
|
||||
findings remain `fix_verification_unknown` and unresolved. The Docker restart
|
||||
journey, which does ingest its direct daemon observation as independent
|
||||
evidence, remains the positive `fix_verified` control.
|
||||
|
||||
@@ -5905,3 +5905,11 @@ to-reconciliation code/test floor only. Claims 16/17 and both APT scorecards
|
||||
remain below operational completion because browser workflow evidence,
|
||||
disposable Debian/Ubuntu real-lab evidence, and Task 12 final-SHA certification
|
||||
remain explicit residuals; no tier-5 or tier-6 evidence is claimed here.
|
||||
|
||||
`fix_verified` is reserved for a canonical action result whose successful
|
||||
execution has a confirmed independent postcondition. Agent-attested
|
||||
confirmation remains useful evidence but projects to
|
||||
`fix_verification_unknown`; investigation and finding records remain unresolved
|
||||
and terminal push copy stays explicitly inconclusive. This keeps legacy
|
||||
single-outcome consumers conservative while `ActionDisposition.ActionResultV2`
|
||||
retains both truth axes and the evidence source.
|
||||
|
||||
@@ -7543,3 +7543,13 @@ workflow. Action list/detail continues to carry policy provenance,
|
||||
`ActionResultV2`, durable dispatch attempt, and receipt fields unchanged. Unit
|
||||
guards pin those projections and verify that APT presentation reads the shared
|
||||
`ActionAuditRecord`/`ActionResultV2` types directly.
|
||||
|
||||
Patrol finding reconciliation treats `fix_verified` as an independently
|
||||
confirmed postcondition, not a synonym for agent readback success. It requires
|
||||
canonical execution `succeeded`, verification `confirmed`, and
|
||||
`evidenceClass=independent`; normalization already requires bounded durable
|
||||
evidence from a trust domain distinct from the executor. Confirmed
|
||||
`agent_attested` truth remains visible in `ActionResultV2` and its compatibility
|
||||
verification projection, but the finding maps to `fix_verification_unknown`
|
||||
and remains unresolved. Malformed digests, missing evidence, and false
|
||||
independence fail closed through canonical result normalization.
|
||||
|
||||
@@ -2079,6 +2079,13 @@ while storage detail drawers and filter controls must route summary series IDs,
|
||||
source tones, and disk metrics through the shared storage helpers instead of
|
||||
reconstructing them from local table state.
|
||||
|
||||
Storage and recovery consumers must not treat confirmed agent-attested action
|
||||
readback as independently verified restoration. The canonical result retains
|
||||
its confirmation and evidence class, while the legacy Patrol finding outcome
|
||||
remains `fix_verification_unknown` unless bounded distinct-trust-domain
|
||||
evidence is present. Only independently confirmed successful execution may
|
||||
auto-resolve a finding as `fix_verified`.
|
||||
|
||||
Task 09 package-cache cleanup remains a bounded typed action over the fixed APT
|
||||
cache provider and requires fresh canonical reclaimable-byte and containing-
|
||||
filesystem pressure evidence. It accepts no path, package selector,
|
||||
|
||||
@@ -85,7 +85,7 @@ func TestActionResultV2EventFindingContextTelemetryProjectionMatrix(t *testing.T
|
||||
expectedOutcome := aicontracts.OutcomeFixVerificationUnknown
|
||||
if execution == unified.ActionExecutionFailed || execution == unified.ActionExecutionNotRun {
|
||||
expectedOutcome = aicontracts.OutcomeFixFailed
|
||||
} else if execution == unified.ActionExecutionSucceeded && verification == unified.ActionVerificationConfirmed {
|
||||
} else if execution == unified.ActionExecutionSucceeded && verification == unified.ActionVerificationConfirmed && class == unified.ActionEvidenceIndependent {
|
||||
expectedOutcome = aicontracts.OutcomeFixVerified
|
||||
} else if execution == unified.ActionExecutionSucceeded && verification == unified.ActionVerificationContradicted {
|
||||
expectedOutcome = aicontracts.OutcomeFixVerificationFailed
|
||||
@@ -114,6 +114,36 @@ func TestActionResultV2EventFindingContextTelemetryProjectionMatrix(t *testing.T
|
||||
}
|
||||
}
|
||||
|
||||
func TestPatrolOutcomeRequiresIndependentEvidenceForFixVerified(t *testing.T) {
|
||||
agentAttested := apiActionResultTruth(t, unified.ActionExecutionSucceeded, unified.ActionVerificationConfirmed, unified.ActionEvidenceAgentAttested)
|
||||
independent := apiActionResultTruth(t, unified.ActionExecutionSucceeded, unified.ActionVerificationConfirmed, unified.ActionEvidenceIndependent)
|
||||
falseIndependent := independent
|
||||
falseIndependent.Verification.Evidence = append([]unified.ActionEvidence(nil), independent.Verification.Evidence...)
|
||||
falseIndependent.Verification.Evidence[0].ObserverTrustDomain = falseIndependent.Verification.Evidence[0].ExecutorTrustDomain
|
||||
badDigest := independent
|
||||
badDigest.Verification.Evidence = append([]unified.ActionEvidence(nil), independent.Verification.Evidence...)
|
||||
badDigest.Verification.Evidence[0].Digest = "sha256:" + strings.Repeat("0", 64)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
truth unified.ActionResultV2
|
||||
want aicontracts.InvestigationOutcome
|
||||
}{
|
||||
{name: "agent_attested", truth: agentAttested, want: aicontracts.OutcomeFixVerificationUnknown},
|
||||
{name: "independent", truth: independent, want: aicontracts.OutcomeFixVerified},
|
||||
{name: "false_independence", truth: falseIndependent, want: aicontracts.OutcomeFixVerificationUnknown},
|
||||
{name: "bad_digest", truth: badDigest, want: aicontracts.OutcomeFixVerificationUnknown},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
record := unified.ActionAuditRecord{State: unified.ActionStateCompleted, Result: &unified.ExecutionResult{ActionResultV2: &test.truth}}
|
||||
if got := patrolOutcomeForActionAudit(record); got != test.want {
|
||||
t.Fatalf("outcome=%q want=%q truth=%#v", got, test.want, test.truth)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionResultV2JSONContractSnapshot(t *testing.T) {
|
||||
truth := apiActionResultTruth(t, unified.ActionExecutionSucceeded, unified.ActionVerificationConfirmed, unified.ActionEvidenceIndependent)
|
||||
payload, err := json.Marshal(AgentEventActionCompletedPayload{ActionID: "action-1", State: "completed", ActionResultV2: &truth})
|
||||
|
||||
@@ -253,8 +253,8 @@ func TestPatrolActionReconciliationCannotRegressFromOutOfOrderCallback(t *testin
|
||||
if got.Action == nil || got.Action.ActionID != "act-1" || got.Action.State != string(unifiedresources.ActionStateCompleted) {
|
||||
t.Fatalf("reconciled action = %#v, want authoritative completed action", got.Action)
|
||||
}
|
||||
if got.Outcome != aicontracts.OutcomeFixVerified {
|
||||
t.Fatalf("outcome = %q, want %q", got.Outcome, aicontracts.OutcomeFixVerified)
|
||||
if got.Outcome != aicontracts.OutcomeFixVerificationUnknown {
|
||||
t.Fatalf("outcome = %q, want %q", got.Outcome, aicontracts.OutcomeFixVerificationUnknown)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -289,7 +289,7 @@ func runAPTWorkflowFindingJourney(t *testing.T, resources *ResourceHandlers, fin
|
||||
t.Fatalf("terminal action truth=%#v", audit.Result)
|
||||
}
|
||||
reconciled := patrol.GetFindings().Get(finding.ID)
|
||||
if reconciled == nil || reconciled.ResolvedAt == nil || reconciled.InvestigationOutcome != string(aicontracts.OutcomeFixVerified) {
|
||||
if reconciled == nil || reconciled.ResolvedAt != nil || reconciled.InvestigationOutcome != string(aicontracts.OutcomeFixVerificationUnknown) {
|
||||
t.Fatalf("reconciled finding=%#v", reconciled)
|
||||
}
|
||||
var notification relay.PushNotificationPayload
|
||||
@@ -371,7 +371,7 @@ func runAPTWorkflowReceiptRecoveryJourney(t *testing.T, resource unified.Resourc
|
||||
t.Fatalf("requests=%d queries=%d; recovery must query once without resend", requests, queries)
|
||||
}
|
||||
reconciled := patrol.GetFindings().Get(finding.ID)
|
||||
if reconciled == nil || reconciled.ResolvedAt == nil || reconciled.InvestigationOutcome != string(aicontracts.OutcomeFixVerified) {
|
||||
if reconciled == nil || reconciled.ResolvedAt != nil || reconciled.InvestigationOutcome != string(aicontracts.OutcomeFixVerificationUnknown) {
|
||||
t.Fatalf("reconciled finding=%#v", reconciled)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,7 +372,7 @@ func runRG09PositiveWorkflow(t *testing.T, dataPath string, resource unified.Res
|
||||
t.Fatalf("dispatch receipt found=%v err=%v receipt=%#v", found, err, receipt)
|
||||
}
|
||||
reconciled := patrol.GetFindings().Get(finding.ID)
|
||||
if reconciled == nil || reconciled.ResolvedAt == nil || reconciled.InvestigationOutcome != string(aicontracts.OutcomeFixVerified) {
|
||||
if reconciled == nil || reconciled.ResolvedAt != nil || reconciled.InvestigationOutcome != string(aicontracts.OutcomeFixVerificationUnknown) {
|
||||
t.Fatalf("finding reconciliation=%#v", reconciled)
|
||||
}
|
||||
projection, err := json.Marshal(struct {
|
||||
@@ -387,7 +387,7 @@ func runRG09PositiveWorkflow(t *testing.T, dataPath string, resource unified.Res
|
||||
t.Fatalf("raw APT detail %q escaped audit/finding projection: %s", forbidden, projection)
|
||||
}
|
||||
}
|
||||
return rg09WorkflowProof{ActionID: audit.ID, AttemptID: attempt.ID, DispatchCount: attempt.DispatchCount, EvidenceClass: truth.Verification.EvidenceClass, FindingID: finding.ID, FindingOutcome: reconciled.InvestigationOutcome, Resolved: true}
|
||||
return rg09WorkflowProof{ActionID: audit.ID, AttemptID: attempt.ID, DispatchCount: attempt.DispatchCount, EvidenceClass: truth.Verification.EvidenceClass, FindingID: finding.ID, FindingOutcome: reconciled.InvestigationOutcome, Resolved: false}
|
||||
}
|
||||
|
||||
func rg09UpdateDriftBarrier(t *testing.T, server *rg09Server, agentID, containerID, expectedHash, distro string) map[string]any {
|
||||
|
||||
@@ -20852,3 +20852,14 @@ func TestContract_PatrolActionBrokerKeepsPolicyExecutionCoreOwned(t *testing.T)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestContract_PatrolFixVerifiedRequiresIndependentEvidence(t *testing.T) {
|
||||
source, err := os.ReadFile("patrol_action_reconciliation.go")
|
||||
if err != nil {
|
||||
t.Fatalf("read patrol_action_reconciliation.go: %v", err)
|
||||
}
|
||||
pattern := regexp.MustCompile(`(?s)case unifiedresources\.ActionVerificationConfirmed:\s+if truth\.Verification\.EvidenceClass == unifiedresources\.ActionEvidenceIndependent \{\s+return aicontracts\.OutcomeFixVerified\s+\}\s+return aicontracts\.OutcomeFixVerificationUnknown`)
|
||||
if !pattern.Match(source) {
|
||||
t.Fatal("fix_verified must require canonical independent verification; agent-attested confirmation must remain verification unknown")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,11 +628,11 @@ func TestPatrolTypedActionJourneyDetectPlanApproveExecuteVerifyAndReconcile(t *t
|
||||
if completed.Action == nil || completed.Action.State != string(unified.ActionStateCompleted) {
|
||||
t.Fatalf("completed investigation action = %#v", completed.Action)
|
||||
}
|
||||
if completed.Outcome != aicontracts.OutcomeFixVerified {
|
||||
t.Fatalf("completed outcome = %q, want verified", completed.Outcome)
|
||||
if completed.Outcome != aicontracts.OutcomeFixVerificationUnknown {
|
||||
t.Fatalf("completed outcome = %q, want verification unknown", completed.Outcome)
|
||||
}
|
||||
updatedFinding := patrol.GetFindings().Get(finding.ID)
|
||||
if updatedFinding == nil || updatedFinding.InvestigationOutcome != string(aicontracts.OutcomeFixVerified) || updatedFinding.ResolvedAt == nil {
|
||||
if updatedFinding == nil || updatedFinding.InvestigationOutcome != string(aicontracts.OutcomeFixVerificationUnknown) || updatedFinding.ResolvedAt != nil {
|
||||
t.Fatalf("reconciled finding = %#v", updatedFinding)
|
||||
}
|
||||
store, err := resources.getStore("default")
|
||||
@@ -648,10 +648,10 @@ func TestPatrolTypedActionJourneyDetectPlanApproveExecuteVerifyAndReconcile(t *t
|
||||
}
|
||||
select {
|
||||
case push := <-pushes:
|
||||
if push.ActionType != relay.PushActionViewFixResult || push.ActionID != finding.ID || push.Body != "Action completed and verified" {
|
||||
if push.ActionType != relay.PushActionViewFixResult || push.ActionID != finding.ID || push.Body != "Action completed; verification was inconclusive" {
|
||||
t.Fatalf("terminal push = %#v", push)
|
||||
}
|
||||
default:
|
||||
t.Fatal("verified lifecycle did not publish a terminal mobile notification")
|
||||
t.Fatal("agent-attested lifecycle did not publish an honest terminal mobile notification")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +158,10 @@ func patrolOutcomeForActionAudit(audit unifiedresources.ActionAuditRecord) aicon
|
||||
}
|
||||
switch truth.Verification.Status {
|
||||
case unifiedresources.ActionVerificationConfirmed:
|
||||
return aicontracts.OutcomeFixVerified
|
||||
if truth.Verification.EvidenceClass == unifiedresources.ActionEvidenceIndependent {
|
||||
return aicontracts.OutcomeFixVerified
|
||||
}
|
||||
return aicontracts.OutcomeFixVerificationUnknown
|
||||
case unifiedresources.ActionVerificationContradicted:
|
||||
return aicontracts.OutcomeFixVerificationFailed
|
||||
default:
|
||||
|
||||
@@ -312,7 +312,7 @@ reportReady:
|
||||
t.Fatalf("positive authority binding audit=%#v", audit)
|
||||
}
|
||||
reconciled := patrol.GetFindings().Get(positiveFinding.ID)
|
||||
if reconciled == nil || reconciled.ResolvedAt == nil || reconciled.InvestigationOutcome != string(aicontracts.OutcomeFixVerified) {
|
||||
if reconciled == nil || reconciled.ResolvedAt != nil || reconciled.InvestigationOutcome != string(aicontracts.OutcomeFixVerificationUnknown) {
|
||||
t.Fatalf("positive finding reconciliation=%#v", reconciled)
|
||||
}
|
||||
cacheAfterPositive := rg06CacheState(t, containerID)
|
||||
|
||||
Reference in New Issue
Block a user