Files
attesto-go/events.go
Codex ce9b8ccfbb sdk(P2.2): typed compliance events + attest/session + Article 12 report
Typed events as SDK-side conventions (no backend change): ModelDecision /
HumanOverride / IncidentReport (NIS2 field names) / DataAccess as Python
dataclasses, TypeScript builders, and Go structs — each serializing to a plain
payload with regulation_refs (EU AI Act Art.12/14, NIS2 Art.23, AI-Act Art.62,
GDPR Art.30/6) and self-validating against the committed-payload number policy.

Python ergonomics: @attest(client, stream_id=...) wraps any function — one
event per call with commitments over args/kwargs and result (raw values never
leave the process), .last_receipt on the wrapper, exceptions log an
IncidentReport-shaped event (commitment over the traceback) and re-raise;
logging failures never break the workload (log-and-continue; strict=True is
the only raising mode — all test-enforced). session(...) groups typed events
under shared session_id/actor_ref metadata.

Evidence report: attesto.reports.article12(...) in Python and
`attesto report article12 --stream ... --output report.md` in the Go CLI —
deterministic templating (never LLM-generated) built only from existing tenant
endpoints: Art.12(2) coverage table, per-type event counts, P1.3 completeness
verdict, checkpoint -> anchor-tx -> block path, and replayable verification
commands. Claims discipline test-enforced in both languages: the words
"compliant"/"compliance guaranteed" never appear — the report states evidence
recorded and independently verifiable. The mock emulators now expose
event_type in tenant listings so report tests run end-to-end against P2.3.

Sweep green: Python 94, TS 59, Go all packages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 23:23:13 +02:00

136 lines
4.0 KiB
Go

package attesto
// [P2.2] Typed compliance events — SDK-side conventions, no backend change.
// Each ToPayload() returns a plain payload map with regulation_refs included,
// validated against the committed-payload number policy. Recording these never
// claims conformity: Attesto attests records.
// Typed event type identifiers.
const (
EventTypeModelDecision = "attesto.model_decision"
EventTypeHumanOverride = "attesto.human_override"
EventTypeIncidentReport = "attesto.incident_report"
EventTypeDataAccess = "attesto.data_access"
)
// Regulation references per typed event (conventions for reports/auditors).
var (
RefsModelDecision = []string{"EU-AI-Act:Art.12", "EU-AI-Act:Art.14"}
RefsHumanOverride = []string{"EU-AI-Act:Art.14"}
RefsIncidentReport = []string{"NIS2:Art.23", "EU-AI-Act:Art.62"}
RefsDataAccess = []string{"GDPR:Art.30", "GDPR:Art.6"}
)
func finishPayload(payload M, refs []string, extra M) (M, error) {
for key, value := range extra {
payload[key] = value
}
out := M{}
for key, value := range payload {
if value != nil && value != "" {
out[key] = value
}
}
out["regulation_refs"] = refs
if err := AssertCommitmentSafeNumbers(map[string]any(out), "$"); err != nil {
return nil, err
}
return out, nil
}
// ModelDecision is one model-driven decision (commitments only; raw inputs and
// outputs never leave your process).
type ModelDecision struct {
Model string
InputCommitment map[string]string
OutputCommitment map[string]string
Decision string
ConfidenceBp int // basis points keep integers commitment-safe
HumanInLoop bool
OperatorRef string
Extra M
}
func (e ModelDecision) EventType() string { return EventTypeModelDecision }
func (e ModelDecision) ToPayload() (M, error) {
return finishPayload(M{
"model": e.Model,
"input_commitment": orNil(e.InputCommitment),
"output_commitment": orNil(e.OutputCommitment),
"decision": e.Decision,
"confidence_bp": e.ConfidenceBp,
"human_in_loop": e.HumanInLoop,
"operator_ref": e.OperatorRef,
}, RefsModelDecision, e.Extra)
}
// HumanOverride records a human overriding a model decision (Art. 14).
type HumanOverride struct {
OriginalEventRef string
OperatorRef string
JustificationCommitment map[string]string
NewDecision string
Extra M
}
func (e HumanOverride) EventType() string { return EventTypeHumanOverride }
func (e HumanOverride) ToPayload() (M, error) {
return finishPayload(M{
"original_event_ref": e.OriginalEventRef,
"operator_ref": e.OperatorRef,
"justification_commitment": orNil(e.JustificationCommitment),
"new_decision": e.NewDecision,
}, RefsHumanOverride, e.Extra)
}
// IncidentReport is a reportable incident with NIS2-style field names.
type IncidentReport struct {
Severity string
Category string
DetectedAt string
SummaryCommitment map[string]string
AffectedService string
Extra M
}
func (e IncidentReport) EventType() string { return EventTypeIncidentReport }
func (e IncidentReport) ToPayload() (M, error) {
return finishPayload(M{
"severity": e.Severity,
"category": e.Category,
"detected_at": e.DetectedAt,
"summary_commitment": orNil(e.SummaryCommitment),
"affected_service": e.AffectedService,
}, RefsIncidentReport, e.Extra)
}
// DataAccess records an access to personal or regulated data.
type DataAccess struct {
SubjectRefCommitment map[string]string
Purpose string
LegalBasis string
AccessorRef string
Extra M
}
func (e DataAccess) EventType() string { return EventTypeDataAccess }
func (e DataAccess) ToPayload() (M, error) {
return finishPayload(M{
"subject_ref_commitment": orNil(e.SubjectRefCommitment),
"purpose": e.Purpose,
"legal_basis": e.LegalBasis,
"accessor_ref": e.AccessorRef,
}, RefsDataAccess, e.Extra)
}
func orNil(m map[string]string) any {
if len(m) == 0 {
return nil
}
return m
}