Files
ietf-wimse-ect/refimpl/ect/types.go
Christian Nennemann f9357fdf88 Add WIMSE ECT reference implementation (Go)
- ect library: create, verify, DAG validation, ledger interface
- In-memory ledger and ECTStore for full ledger mode
- Test vectors and unit tests; two-agent demo (cmd/demo)
- README: document refimpl scope and usage

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 22:05:30 +01:00

98 lines
3.2 KiB
Go

// Package ect implements Execution Context Tokens (ECTs) per
// draft-nennemann-wimse-execution-context-00.
package ect
import "time"
// ECTType is the JOSE typ value for ECTs.
const ECTType = "wimse-exec+jwt"
// PolDecision values per Section 4.2.3.
const (
PolDecisionApproved = "approved"
PolDecisionRejected = "rejected"
PolDecisionPendingHumanReview = "pending_human_review"
)
// Payload holds ECT JWT claims per Section 4.2.
type Payload struct {
// Standard JWT claims (required unless noted)
Iss string `json:"iss"` // REQUIRED: issuer, SPIFFE ID
Sub string `json:"sub,omitempty"`
Aud Audience `json:"aud"` // REQUIRED
Iat int64 `json:"iat"` // REQUIRED: NumericDate
Exp int64 `json:"exp"` // REQUIRED
Jti string `json:"jti"` // REQUIRED: UUID
// Execution context (Section 4.2.2)
Wid string `json:"wid,omitempty"` // OPTIONAL: workflow ID, UUID
Tid string `json:"tid"` // REQUIRED: task ID, UUID
ExecAct string `json:"exec_act"` // REQUIRED
Par []string `json:"par"` // REQUIRED: parent task IDs
// Policy evaluation (Section 4.2.3)
Pol string `json:"pol"` // REQUIRED
PolDecision string `json:"pol_decision"` // REQUIRED: approved | rejected | pending_human_review
PolEnforcer string `json:"pol_enforcer,omitempty"` // OPTIONAL
PolTimestamp int64 `json:"pol_timestamp,omitempty"` // OPTIONAL
// Data integrity (Section 4.2.4)
InpHash string `json:"inp_hash,omitempty"`
OutHash string `json:"out_hash,omitempty"`
InpClassification string `json:"inp_classification,omitempty"`
// Task metadata (Section 4.2.5)
ExecTimeMs int `json:"exec_time_ms,omitempty"`
RegulatedDomain string `json:"regulated_domain,omitempty"`
ModelVersion string `json:"model_version,omitempty"`
WitnessedBy []string `json:"witnessed_by,omitempty"`
// Compensation (Section 4.2.6)
CompensationRequired bool `json:"compensation_required,omitempty"`
CompensationReason string `json:"compensation_reason,omitempty"`
// Extensions (Section 4.2.7)
Ext map[string]interface{} `json:"ext,omitempty"`
}
// Audience is aud claim: string or array of strings.
type Audience []string
// MarshalJSON encodes aud as single string if one element, else array.
func (a Audience) MarshalJSON() ([]byte, error) {
if len(a) == 1 {
return marshalJSONString(a[0]), nil
}
return marshalJSONStringArray(a), nil
}
// UnmarshalJSON decodes aud from string or array.
func (a *Audience) UnmarshalJSON(data []byte) error {
return unmarshalAudience(data, a)
}
// ValidPolDecision returns true if s is a registered pol_decision value.
func ValidPolDecision(s string) bool {
return s == PolDecisionApproved || s == PolDecisionRejected || s == PolDecisionPendingHumanReview
}
// ContainsAudience returns true if verifierID is in the audience.
func (p *Payload) ContainsAudience(verifierID string) bool {
for _, id := range p.Aud {
if id == verifierID {
return true
}
}
return false
}
// IATTime returns p.Iat as time.Time.
func (p *Payload) IATTime() time.Time {
return time.Unix(p.Iat, 0)
}
// ExpTime returns p.Exp as time.Time.
func (p *Payload) ExpTime() time.Time {
return time.Unix(p.Exp, 0)
}