mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-10 05:01:28 -10:00
* feat: add runtime permission event contract * feat: stream permission events from exec * feat: add runtime command snapshots * feat: add session rewind compaction plans * feat: add verification event contracts * fix: harden runtime contract review gaps * test: assert approved exec stderr contract * fix: address CodeRabbit CHANGES_REQUESTED items for #82 - Make OnSandboxDecision callback async + panic-safe (non-blocking, recovered) in tools/registry.go per CR. - Add explicit regression test for ConfigSnapshot when ResolveRuntimeMetadata errors (no secret leak in Message/BaseURL). - (Many prior items like shapedPayloadPreview for permission/tool events, dual-target conflict test, stderr empty on approved path, Kind/Framework preservation, redactCommand empty slice, etc. were already landed on this branch.) All relevant tests now pass. * fix(agent): make sandbox decision available synchronously on Result for permission events The recent change to invoke OnSandboxDecision asynchronously (to satisfy non-blocking requirement) broke the agent's immediate capture of the decision for building PermissionEvent after registry.RunWithOptions. - Add SandboxDecision *sandbox.Decision to tools.Result (not serialized). - Populate it on all sandbox-involved return paths in registry.RunWithOptions (sync, before/while firing async callback for observers). - In agent executeToolCall, use result.SandboxDecision for buildPermissionEvent (reliable ordering) instead of side-effect capture via the callback. - The async callback is still supported for any external OnSandboxDecision consumers. This fixes the two failing agent tests that were causing the cross-platform 'Smoke' CI jobs to fail on this PR: - TestRunEmitsPermissionEventForPersistentSandboxGrant - TestRunAppliesSandboxEvenInUnsafeMode All go tests now pass. --------- Co-authored-by: KRATOS <[email protected]>
140 lines
4.5 KiB
Go
140 lines
4.5 KiB
Go
package selfverify
|
|
|
|
import (
|
|
"github.com/Gitlawb/zero/internal/redaction"
|
|
"github.com/Gitlawb/zero/internal/verify"
|
|
)
|
|
|
|
const LoopContractVersion = "zero.selfverify.report.v1"
|
|
|
|
type EventType string
|
|
|
|
const (
|
|
EventLoopStarted EventType = "selfverify_started"
|
|
EventAttemptFinished EventType = "selfverify_attempt_finished"
|
|
EventLoopFinished EventType = "selfverify_finished"
|
|
)
|
|
|
|
type LoopSnapshot struct {
|
|
Contract string `json:"contract"`
|
|
Runtime string `json:"runtime"`
|
|
Root string `json:"root,omitempty"`
|
|
StartedAt string `json:"startedAt"`
|
|
EndedAt string `json:"endedAt"`
|
|
OK bool `json:"ok"`
|
|
StopReason StopReason `json:"stopReason"`
|
|
Summary verify.Summary `json:"summary"`
|
|
Attempts []AttemptSnapshot `json:"attempts"`
|
|
Error string `json:"error,omitempty"`
|
|
Events []Event `json:"events"`
|
|
}
|
|
|
|
type AttemptSnapshot struct {
|
|
Number int `json:"number"`
|
|
Report verify.ReportSnapshot `json:"report"`
|
|
Remediation *Remediation `json:"remediation,omitempty"`
|
|
}
|
|
|
|
type Event struct {
|
|
Type EventType `json:"type"`
|
|
Runtime string `json:"runtime,omitempty"`
|
|
Contract string `json:"contract,omitempty"`
|
|
Root string `json:"root,omitempty"`
|
|
Attempt int `json:"attempt,omitempty"`
|
|
Status verify.Status `json:"status,omitempty"`
|
|
OK bool `json:"ok,omitempty"`
|
|
StopReason StopReason `json:"stopReason,omitempty"`
|
|
Summary verify.Summary `json:"summary,omitempty"`
|
|
StartedAt string `json:"startedAt,omitempty"`
|
|
EndedAt string `json:"endedAt,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
Remediation *Remediation `json:"remediation,omitempty"`
|
|
}
|
|
|
|
func SnapshotFromReport(report Report) LoopSnapshot {
|
|
attempts := make([]AttemptSnapshot, 0, len(report.Attempts))
|
|
for _, attempt := range report.Attempts {
|
|
attempts = append(attempts, AttemptSnapshot{
|
|
Number: attempt.Number,
|
|
Report: verify.SnapshotFromReport(attempt.Report),
|
|
Remediation: redactRemediationPointer(attempt.Remediation),
|
|
})
|
|
}
|
|
return LoopSnapshot{
|
|
Contract: LoopContractVersion,
|
|
Runtime: verify.RuntimeGo,
|
|
Root: redactContractString(report.Root),
|
|
StartedAt: redactContractString(report.StartedAt),
|
|
EndedAt: redactContractString(report.EndedAt),
|
|
OK: report.OK,
|
|
StopReason: report.StopReason,
|
|
Summary: report.Summary,
|
|
Attempts: attempts,
|
|
Error: redactContractString(report.Error),
|
|
Events: EventsFromReport(report),
|
|
}
|
|
}
|
|
|
|
func EventsFromReport(report Report) []Event {
|
|
events := []Event{
|
|
{
|
|
Type: EventLoopStarted,
|
|
Runtime: verify.RuntimeGo,
|
|
Contract: LoopContractVersion,
|
|
Root: redactContractString(report.Root),
|
|
StartedAt: redactContractString(report.StartedAt),
|
|
Summary: report.Summary,
|
|
},
|
|
}
|
|
for _, attempt := range report.Attempts {
|
|
status := verify.StatusFail
|
|
if attempt.Report.OK {
|
|
status = verify.StatusPass
|
|
} else if attempt.Report.Summary.Errors > 0 {
|
|
status = verify.StatusError
|
|
}
|
|
events = append(events, Event{
|
|
Type: EventAttemptFinished,
|
|
Runtime: verify.RuntimeGo,
|
|
Contract: LoopContractVersion,
|
|
Root: redactContractString(report.Root),
|
|
Attempt: attempt.Number,
|
|
Status: status,
|
|
OK: attempt.Report.OK,
|
|
Summary: attempt.Report.Summary,
|
|
StartedAt: redactContractString(attempt.Report.StartedAt),
|
|
EndedAt: redactContractString(attempt.Report.EndedAt),
|
|
Remediation: redactRemediationPointer(attempt.Remediation),
|
|
})
|
|
}
|
|
events = append(events, Event{
|
|
Type: EventLoopFinished,
|
|
Runtime: verify.RuntimeGo,
|
|
Contract: LoopContractVersion,
|
|
Root: redactContractString(report.Root),
|
|
OK: report.OK,
|
|
StopReason: report.StopReason,
|
|
Summary: report.Summary,
|
|
StartedAt: redactContractString(report.StartedAt),
|
|
EndedAt: redactContractString(report.EndedAt),
|
|
Error: redactContractString(report.Error),
|
|
})
|
|
return events
|
|
}
|
|
|
|
func redactRemediationPointer(remediation *Remediation) *Remediation {
|
|
if remediation == nil {
|
|
return nil
|
|
}
|
|
next := *remediation
|
|
next.StartedAt = redactContractString(next.StartedAt)
|
|
next.EndedAt = redactContractString(next.EndedAt)
|
|
next.Message = redactContractString(next.Message)
|
|
next.Error = redactContractString(next.Error)
|
|
return &next
|
|
}
|
|
|
|
func redactContractString(value string) string {
|
|
return redaction.RedactString(value, redaction.Options{})
|
|
}
|