- 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>
98 lines
3.2 KiB
Go
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)
|
|
}
|