cas

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 7, 2026 License: MIT Imports: 3 Imported by: 0

Documentation

Overview

Package cas provides a typed-Go-heavy Protocol primitive for compare-and-swap (CAS) optimistic concurrency control.

Conceptually, CAS is modeled after Kubernetes resourceVersion and etcd compare-then-put: a writer reads the current version, passes it as an expected version on the mutation, and the store rejects the write if the current version has advanced (i.e. a concurrent writer won).

Design contrast with session and ledger

Unlike runtime/auth/session and runtime/audit/ledger, cas does NOT expose a Store interface, mem_store, or storetest suite. CAS is a write-time policy primitive, not an entity lifecycle: each cell owns its own private entities (User, ConfigEntry, FeatureFlag, …) and their schemas cannot be unified into a shared generic Store without leaking cell-private domain types across cell boundaries — a violation of the GoCell cross-cell isolation rule.

Conformance is proved by cell-private repository race-condition integration tests (real DB with concurrent writers), not by a shared storetest suite.

What this package provides

  • ConflictPolicy sealed interface — today only ConflictPolicyStrictReject (HTTP 409, no retry). Future siblings (LastWriteWins, RetryWithMerge) may be added here without breaking callers.
  • Protocol value — bundles the versionField name and a ConflictPolicy. Constructed via NewProtocol (composition root only).
  • CheckVersionMatch — translates UPDATE/DELETE RowsAffected into the standard ErrVersionConflict error vocabulary so cell repos share a single check point.

ref: docs/architecture/202605101200-adr-typed-go-heavy-protocol-primitives.md

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CheckVersionMatch

func CheckVersionMatch(rowsAffected int64, entityDesc, entityKey string) error

CheckVersionMatch translates UPDATE/DELETE ... WHERE version=$expected RowsAffected outcome into the standard CAS error vocabulary.

  • rowsAffected == 1: nil (caller applied successfully)
  • rowsAffected == 0: ErrVersionConflict (KindConflict / HTTP 409) — caller may follow up with a NotFound probe (separate GetByKey) to distinguish "key absent" from "version mismatch"; cas treats both as conflict from CAS perspective and lets the caller decide upstream error mapping.
  • rowsAffected > 1: also returns ErrVersionConflict — the WHERE clause matched more rows than expected, indicating a schema or query error.

entityDesc and entityKey are persisted in the Internal field (server-side slog only) — they are NOT emitted to the HTTP wire as Details. This avoids leaking entity type / record identifiers (userID, config key, etc.) to clients via the standard error envelope. Operators retrieve them from the service log when correlating retries.

Callers MUST distinguish key-absent vs version-mismatch by probing existence first (e.g. via SELECT FOR UPDATE before UPDATE, or a GetByKey probe after UPDATE/DELETE returns no rows). CheckVersionMatch unconditionally translates rowsAffected==0 to ErrVersionConflict; the caller's pre-check provides the NotFound branch. Without a pre-check, a DELETE on a non-existent key returns ErrVersionConflict (409) instead of the correct ErrNotFound (404).

Types

type ConflictPolicy

type ConflictPolicy interface {
	// contains filtered or unexported methods
}

ConflictPolicy is a sealed marker interface — it carries no behavior, only type discrimination. Today only ConflictPolicyStrictReject is implemented; future siblings (LastWriteWins / RetryWithMerge) may be added as additional types in this package. The marker method conflictPolicyOK is unexported, so external packages cannot implement ConflictPolicy — this prevents callers from injecting unsanctioned conflict strategies past the runtime/state/cas API boundary. Matches the sealed-marker pattern in runtime/auth/session (FingerprintMode / OrderingModel) and runtime/audit/ledger (RestartRecoveryMode / IdempotencyMode).

type ConflictPolicyStrictReject

type ConflictPolicyStrictReject struct{}

ConflictPolicyStrictReject means CAS mismatch returns ErrVersionConflict (HTTP 409) without retry. This is the safe default and the only policy implemented today.

type Option

type Option func(*Protocol) error

Option configures a Protocol during construction.

func WithConflictPolicy

func WithConflictPolicy(c ConflictPolicy) Option

WithConflictPolicy sets the policy. Nil interface (bare or typed) is sticky-rejected via sentinel — fail-fast at NewProtocol.

Both bare-nil and typed-nil ConflictPolicy values are rejected by NewProtocol so the conflict policy is never silently absent. Pattern mirrors runtime/auth/session.WithFingerprint (strong-dependency wiring option).

func WithVersionField

func WithVersionField(name string) Option

WithVersionField names the DB column / domain field that carries the monotonic version counter (e.g. "password_version", "version"). Required. Empty string is rejected at Option apply time.

type Protocol

type Protocol struct {
	// contains filtered or unexported fields
}

Protocol bundles CAS protocol decisions for a particular consumer (cell). Construct via NewProtocol (composition root only).

func NewProtocol

func NewProtocol(opts ...Option) (*Protocol, error)

NewProtocol fail-fasts on missing required fields and typed-nil. Defaults conflict policy to ConflictPolicyStrictReject when omitted.

func (*Protocol) Conflict

func (p *Protocol) Conflict() ConflictPolicy

Conflict returns the configured ConflictPolicy.

func (*Protocol) VersionField

func (p *Protocol) VersionField() string

VersionField returns the configured DB column / domain field name that carries the monotonic version counter.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL