package release import ( "context" "crypto/ed25519" "math" "github.com/carlosframework/platform/internal/maintenanceguard" ) const maxWorkerControlETagBytes = 1024 func validWorkerControlETag(etag string) bool { return len(etag) > 0 && len(etag) <= maxWorkerControlETagBytes } // WorkerAssignmentSnapshot binds verified bytes to the store version read. // It proves neither worker authentication nor the evidence behind a receipt. // Callers must check current enrollment, registration, expiry and the complete // maintenance protocol before attempting a publication. type WorkerAssignmentSnapshot struct { raw []byte etag string identity WorkerAssignmentIdentity } func (s WorkerAssignmentSnapshot) State(pub ed25519.PublicKey) (WorkerAssignmentState, error) { state, err := VerifyWorkerAssignment(pub, s.raw) if err != nil || state.Identity != s.identity || !validWorkerControlETag(s.etag) { return WorkerAssignmentState{}, ErrWorkerControl } return state, nil } type WorkerControlStore interface { Get(context.Context, string) ([]byte, string, error) Put(context.Context, string, []byte, string) (string, error) } func workerAssignmentKey(identity WorkerAssignmentIdentity) (string, error) { if !identity.Valid() { return "", ErrWorkerControl } return "control/maintenance/assignments/" + identity.Assignment.ID + "/state.json", nil } func ReadWorkerAssignment(ctx context.Context, store WorkerControlStore, pub ed25519.PublicKey, identity WorkerAssignmentIdentity) (WorkerAssignmentSnapshot, error) { key, err := workerAssignmentKey(identity) if err != nil || store == nil || ctx == nil || ctx.Err() != nil { return WorkerAssignmentSnapshot{}, ErrWorkerControl } raw, etag, err := store.Get(ctx, key) if err != nil { return WorkerAssignmentSnapshot{}, err } snapshot := WorkerAssignmentSnapshot{raw: append([]byte(nil), raw...), etag: etag, identity: identity} if _, err := snapshot.State(pub); err != nil { return WorkerAssignmentSnapshot{}, err } return snapshot, nil } // CreateWorkerAssignment writes only an unregistered initial state. The caller // must register its immutable assignment reference on an active worker head // before it can authorize work. A conflict requires reading existing state; // creating again never resets accepted receipts or revives a disabled state. // An invalid returned ETag is an uncertain write: re-read the same identity. func CreateWorkerAssignment(ctx context.Context, store WorkerControlStore, sg Signer, state WorkerAssignmentState) (WorkerAssignmentSnapshot, error) { key, err := workerAssignmentKey(state.Identity) if err != nil || store == nil || ctx == nil || ctx.Err() != nil || state.Revision != 1 || state.Disabled || state.Preflight != nil || state.Compatibility != nil || state.Execution != nil || state.Resume != nil { return WorkerAssignmentSnapshot{}, ErrWorkerControl } raw, err := SignWorkerAssignment(sg, state) if err != nil { return WorkerAssignmentSnapshot{}, err } etag, err := store.Put(ctx, key, raw, "") if err != nil { return WorkerAssignmentSnapshot{}, err } if !validWorkerControlETag(etag) { return WorkerAssignmentSnapshot{}, ErrWorkerControl } return WorkerAssignmentSnapshot{raw: raw, etag: etag, identity: state.Identity}, nil } // AdvanceWorkerAssignment is a single conditional write, with no retry. A // publication and revocation therefore compete on the same storage version. // The storage principal must prevent rollback of these objects: signatures // alone do not stop a writer replaying an older signed enabled state. // An invalid returned ETag is an uncertain write, never evidence of rollback. func AdvanceWorkerAssignment(ctx context.Context, store WorkerControlStore, sg Signer, before WorkerAssignmentSnapshot, after WorkerAssignmentState) (WorkerAssignmentSnapshot, error) { if store == nil || ctx == nil || ctx.Err() != nil || sg.usable() != nil || sg.scope != nil || sg.grant != nil { return WorkerAssignmentSnapshot{}, ErrWorkerControl } previous, err := before.State(sg.verifyPub()) if err != nil || !validWorkerAssignmentAdvance(previous, after) { return WorkerAssignmentSnapshot{}, ErrWorkerControl } key, err := workerAssignmentKey(previous.Identity) if err != nil { return WorkerAssignmentSnapshot{}, err } raw, err := SignWorkerAssignment(sg, after) if err != nil { return WorkerAssignmentSnapshot{}, err } etag, err := store.Put(ctx, key, raw, before.etag) if err != nil { return WorkerAssignmentSnapshot{}, err } if !validWorkerControlETag(etag) { return WorkerAssignmentSnapshot{}, ErrWorkerControl } return WorkerAssignmentSnapshot{raw: raw, etag: etag, identity: after.Identity}, nil } func sameWorkerReference(a, b *maintenanceguard.Reference) bool { if a == nil || b == nil { return a == nil && b == nil } return *a == *b } func validWorkerAssignmentAdvance(before, after WorkerAssignmentState) bool { if !before.Valid() || !after.Valid() || before.Disabled || before.Identity != after.Identity || before.Expires != after.Expires || before.Revision == math.MaxInt64 || after.Revision != before.Revision+1 { return false } oldRefs := []*maintenanceguard.Reference{before.Preflight, before.Compatibility, before.Execution, before.Resume} newRefs := []*maintenanceguard.Reference{after.Preflight, after.Compatibility, after.Execution, after.Resume} added := -1 for i, old := range oldRefs { if sameWorkerReference(old, newRefs[i]) { continue } if after.Disabled || old != nil || newRefs[i] == nil || added != -1 { return false } added = i } if after.Disabled { return added == -1 } switch added { case 0: return before.Compatibility == nil && before.Execution == nil && before.Resume == nil case 1: return before.Preflight != nil && before.Execution == nil && before.Resume == nil case 2: return before.Preflight != nil && before.Resume == nil case 3: return before.Execution != nil default: return false } }