Documentation
¶
Overview ¶
Package k8slease is the Kubernetes-backed port.SessionLease: cross-process, cross-HOST single-writer enforcement for the multi-replica cloud-native posture (ADR 0027 Phase 4), backed by a coordination.k8s.io/v1 Lease object per session id. It is the multi-host story the single-host flock lease cannot give: an API-server-coordinated lease survives a replica moving between nodes.
It is BUILT but UNWIRED by default: composition constructs it ONLY when an operator selects it with --session-lease-k8s-namespace, so the default path is byte-identical with no lease. k8slease never returns ErrLeaseUnsupported — it is a fully-supporting adapter; an RBAC Forbidden surfaces as a hard infrastructure error (the operator fixed the wrong thing), not a sticky disable.
Object naming: a session id is arbitrary text (a team/subagent id, a UUID, a user string) and need not be a valid RFC-1123 object name, so the Lease object is named "mecatl-lease-" + hex(sha256(id))[:40] — always ≤253 chars, always RFC-1123-valid, and collision-free (one-way hash). The raw id is preserved in an annotation for operators eyeballing `kubectl get leases`.
Fencing: spec.leaseTransitions is the canonical fencing counter — it advances on every takeover and is the port's Token. spec.renewTime + spec.leaseDurationSeconds is the expiry; a Get-then-Update with the read resourceVersion gives optimistic-concurrency CAS, so a lost race surfaces as a 409 Conflict → ErrLeaseHeld.
RBAC: this adapter only ever calls Get/Create/Update/Delete (never List or Watch), so it needs get,create,update,delete on `leases` in the `coordination.k8s.io` API group, namespace-scoped (a Role + RoleBinding on the configured namespace). See docs/usage.md for the manifest.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Lease ¶
type Lease struct {
// contains filtered or unexported fields
}
Lease is a port.SessionLease over coordination.k8s.io Lease objects in one namespace.
func New ¶
func New(clientset kubernetes.Interface, namespace string, ttl time.Duration, clock port.Clock) *Lease
New constructs a k8s-backed lease over clientset in namespace, with the given TTL and clock. A non-positive ttl defaults to 30s.
func (*Lease) Acquire ¶
func (l *Lease) Acquire(ctx context.Context, id session.SessionID, owner string) (port.Lease, error)
Acquire grants the lease when the object is absent, expired, or already held by owner; otherwise ErrLeaseHeld. A takeover bumps leaseTransitions (the token) and uses the read resourceVersion as a CAS guard so a concurrent takeover loses with a 409 Conflict → ErrLeaseHeld.
func (*Lease) Release ¶
Release relinquishes a lease the caller still holds (holder + token match) by writing a TOMBSTONE: the holder is cleared and the renewTime is wound back into the past so the object reads as already-expired, while leaseTransitions (the fencing token) is RETAINED. This keeps the per-id token monotone across release — the port.SessionLease contract a successful takeover after a release returns a strictly-greater token (pinned by the shared leaseconformance suite). Deleting the object instead would reset leaseTransitions to 1 on the next create and break that contract. Idempotent: a NotFound, a holder/token mismatch, or a concurrent change is a no-op success — Release only drops the caller's OWN hold.