Files
Christian Nennemann 884d2dc836 feat: migrate refimpls from draft-00 to draft-01 claim names
- Rename `par` to `pred` (predecessor) in types, serialization, tests
- Remove `pol`, `pol_decision` from core payload; move to `ect_ext`
- Remove `sub` from payload (not part of ECT spec)
- Update `typ` from `wimse-exec+jwt` to `exec+jwt` (accept both)
- Rename MaxParLength to MaxPredLength everywhere
- Update testdata, demos, READMEs with migration table
- All Go tests pass, all 56 Python tests pass (90% coverage)
2026-04-03 10:55:58 +02:00

108 lines
3.1 KiB
Go

package ect
import (
"errors"
"sync"
"time"
)
// LedgerEntry represents a single ECT record in the audit ledger (Section 9.3).
type LedgerEntry struct {
LedgerSequence int64 `json:"ledger_sequence"`
TaskID string `json:"task_id"`
AgentID string `json:"agent_id"`
Action string `json:"action"`
Predecessors []string `json:"predecessors"`
ECTJWS string `json:"ect_jws"`
SignatureVerified bool `json:"signature_verified"`
VerificationTime time.Time `json:"verification_timestamp"`
StoredTime time.Time `json:"stored_timestamp"`
}
// Ledger is the audit ledger interface per Section 9. Append-only; lookup by tid.
type Ledger interface {
// Append records a verified ECT. Returns the new ledger sequence number or error.
Append(ectJWS string, payload *Payload) (seq int64, err error)
// GetByTid returns the payload for the given task ID, or nil.
GetByTid(tid string) *Payload
// Contains returns true if (tid, wid) exists. wid may be empty for global scope.
Contains(tid, wid string) bool
// ECTStore implementation for DAG validation
ECTStore
}
// MemoryLedger is an in-memory, append-only ECT store implementing Ledger and ECTStore.
type MemoryLedger struct {
mu sync.RWMutex
seq int64
byTid map[string]*Payload
bySeq []LedgerEntry
entries []LedgerEntry // full entries for audit
}
// NewMemoryLedger creates an empty in-memory ledger.
func NewMemoryLedger() *MemoryLedger {
return &MemoryLedger{
byTid: make(map[string]*Payload),
bySeq: make([]LedgerEntry, 0),
}
}
// Append implements Ledger.
func (m *MemoryLedger) Append(ectJWS string, payload *Payload) (int64, error) {
if payload == nil {
return 0, nil
}
m.mu.Lock()
defer m.mu.Unlock()
// Uniqueness: jti (task id) must not already exist (scoped by wid when present) per spec
wid := payload.Wid
if m.containsLocked(payload.Jti, wid) {
return 0, ErrTaskIDExists
}
m.seq++
entry := LedgerEntry{
LedgerSequence: m.seq,
TaskID: payload.Jti, // task id = jti per spec
AgentID: payload.Iss,
Action: payload.ExecAct,
Predecessors: append([]string(nil), payload.Pred...),
ECTJWS: ectJWS,
SignatureVerified: true,
VerificationTime: time.Now().UTC(),
StoredTime: time.Now().UTC(),
}
m.byTid[payload.Jti] = payload
m.bySeq = append(m.bySeq, entry)
m.entries = append(m.entries, entry)
return m.seq, nil
}
// GetByTid implements ECTStore and Ledger.
func (m *MemoryLedger) GetByTid(tid string) *Payload {
m.mu.RLock()
defer m.mu.RUnlock()
return m.byTid[tid]
}
// Contains implements ECTStore and Ledger.
func (m *MemoryLedger) Contains(tid, wid string) bool {
m.mu.RLock()
defer m.mu.RUnlock()
return m.containsLocked(tid, wid)
}
func (m *MemoryLedger) containsLocked(tid, wid string) bool {
p, ok := m.byTid[tid]
if !ok {
return false
}
if wid == "" {
return true
}
return p.Wid == wid
}
// ErrTaskIDExists is returned when appending an ECT whose jti already exists.
var ErrTaskIDExists = errors.New("ect: task ID (jti) already exists in ledger")