projection

package
v1.0.0-beta.159 Latest Latest
Warning

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

Go to latest
Published: Jul 31, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package projection implements the ADR-056 Decision 6 graph projection contract — the missing middle layer between "what type is flowing" (the payload registry) and "who may author these facts at runtime" (pkg/ownership).

A Contract is declared ONCE, beside the type / Graphable / gateway-resource projection code, and answers: "what graph facts does this projection emit, and in what write mode is each predicate group?" Ownership claims are then DERIVED from the contract and bound to an owner id at boot — they are not hand-maintained as a parallel registry that drifts from the projection.

payload type registration
  └─ optionally declares a projection.Contract
       (entity pattern · predicates × write mode · foreign-edge claims · indexing profile)
component / gateway boot
  └─ projection.Bind(ctx, ownerRegistry, ownerID, contracts...)
       derives the ownership.OwnerClaim/ForeignEdgeClaim set, registers it, and
       returns an ownership.OwnerToken only when replace/CAS claims make the
       atomic registration an owning lease; non-owning binds return zero
graph-ingest
  └─ enforces the registered claims at the write boundary (a later increment)

This package is the DERIVATION + DECLARATION layer. It does not enforce anything — pkg/ownership is the enforcement substrate, and graph-ingest is the write-boundary enforcer. Manual ownership.RegisterOwner remains the low-level escape hatch for owners whose pattern is not derivable from a single payload type (the lifecycle Manager) or for migration scaffolding.

Contract-derived append-mode and foreign-edge-only registrations are durable declarations: they persist in the registry without presence, heartbeats, revival monitoring, or a lease token. If a derived registration also contains any replace/CAS claim, the whole claim set is one atomic owning lease: presence and revival monitoring apply to the entry, and stale compaction removes all of its owning and non-owning claims together.

A Contract carries no CoordinationWaiver: a legitimately-overlapping projection (the cross-product / mutual-consent case) is an explicit NON-GOAL of the contract layer and drops to manual ownership.RegisterOwner. If contract-declared overlaps ever become common, that is a signal to add waiver support here rather than let owners drift off the derivation path.

See docs/adr/056-authoritative-semantic-state.md Decision 6.

Index

Constants

This section is empty.

Variables

View Source
var ErrInvalidContract = errors.New("projection: invalid contract")

ErrInvalidContract is returned when a projection contract is malformed, or when deriving claims from it produces an inconsistent registration. It wraps the underlying ownership error (errors.Is still matches ownership.ErrInvalidClaim / ownership.ErrOwnershipOverlap) so callers can branch on the precise cause.

Functions

func Bind

func Bind(ctx context.Context, ownerReg *ownership.Registry, owner string, contracts ...Contract) (ownership.OwnerToken, error)

Bind is the component/gateway boot step: derive the owner's claims from its contracts and register them with the ownership substrate. On success it returns the owner's typed OwnerToken (ADR-056 PR-3.5) — the write-lease credential the bound owner stamps on its mutation requests. Non-owning append/foreign registrations still persist but return the zero token. Returns the zero token and ownership.ErrOwnershipOverlap (wrapped) when another owner already holds a derived cell, or the zero token and a derive/validation error.

func BindAndHeartbeat

func BindAndHeartbeat(ctx context.Context, ownerReg *ownership.Registry, hb *ownership.Heartbeater, owner string, contracts ...Contract) (ownership.OwnerToken, error)

BindAndHeartbeat is Bind plus liveness enrollment for a STATIC projection owner — one registered once at boot for the whole process lifetime (a graph-writer, a rule pack), as opposed to a lifecycle.Manager workflow owner (which the Manager enrolls into its own heartbeater). It returns the bound owner's typed OwnerToken on success (the same credential Bind surfaces; the zero token on failure). A static owner that derives an owning replace/CAS OwnerClaim MUST heartbeat: RegisterOwner creates its OWNER_PRESENCE key at registration, but without ongoing heartbeats that key ages out after ownership.PresenceTTL and the next registrant compacts the owning entry out of the epoch. Non-owning append/foreign registrations create no presence and are compaction-exempt.

Enrollment happens only for a successful owning Bind: rejected owners and non-owning append/foreign registrations have no lease to keep alive. A nil hb binds without enrolling. The caller owns the Heartbeater's lifetime — build it once at the composition root (ownerReg.NewHeartbeater), run it on a shutdown-cancelled context (go hb.Run(ctx)), and pass it here for every static owning owner it binds.

func Derive

func Derive(owner string, contracts ...Contract) (ownership.Registration, error)

Derive binds one or more contracts to an owner id and returns the aggregated ownership.Registration — the claims that owner registers (one OwnerClaim per group across all contracts, one ForeignEdgeClaim per foreign edge). Every contract is validated, and the AGGREGATE is checked for self-overlap (two of the owner's own contracts claiming the same cell — a config bug). Cross-OWNER overlap is left to ownership.RegisterOwner against the live epoch.

func MustRegister

func MustRegister(c Contract)

MustRegister is Register that panics on error — for init()-time registration where a malformed or duplicate contract is a programming error.

func Register

func Register(c Contract) error

Register adds a contract to the global registry, keyed by Name. Validates the contract and rejects a duplicate name. Co-locate the call with the payload type's registration so the projection and the type are declared together.

Types

type AppendEvidenceMutation

type AppendEvidenceMutation struct {
	Contract string
	EntityID string
	Evidence []message.Triple
	Metadata MutationMetadata
}

AppendEvidenceMutation appends evidence for exactly one existing entity.

type AuthoritativeReader

type AuthoritativeReader interface {
	ReadAuthoritative(context.Context, string) (*graph.EntityState, error)
}

AuthoritativeReader is the least-privilege graph-ingest source-of-truth read.

type CommitState

type CommitState string

CommitState reports what the client can prove about the authoritative write.

const (
	CommitNotCommitted CommitState = "not-committed"
	CommitUnknown      CommitState = "unknown"
	CommitCommitted    CommitState = "committed"
	CommitVerified     CommitState = "verified"
)

Commit states describe progressively stronger knowledge about whether the authoritative mutation was applied.

type Contract

type Contract struct {
	// Name identifies the projection — typically the payload MessageType it is
	// co-located with, or a logical projection name. Used as the registry key
	// and in error messages.
	Name string `json:"name"`
	// MessageType is the payload type whose Graphable this contract projects for.
	// Stamped onto every derived ForeignEdgeClaim as its Producer so the T2-seam
	// reject can key on (message_type, predicate) (ADR-056 Decision 4). Optional:
	// empty derives Producer-empty ("any producer") foreign edges — the
	// transitional shape. A contract WITH foreign edges should name its type.
	MessageType string `json:"message_type,omitempty"`
	// EntityPattern is the 6-part entity-ID glob the projection writes (the
	// entity it owns).
	EntityPattern string `json:"entity_pattern"`
	// Groups are the owned/append predicate groups by write mode.
	Groups []PredicateGroup `json:"groups,omitempty"`
	// BirthPredicates are a MutationClient convention: they authorize
	// primary-subject facts only on CreateWithTriples and derive no ownership
	// claim. The graph mutation service does not independently make these
	// predicates immutable; writers outside this client contract can still
	// mutate them.
	BirthPredicates []string `json:"birth_predicates,omitempty"`
	// ForeignEdges are the relationship edges the projection writes onto other
	// entities.
	ForeignEdges []ForeignEdge `json:"foreign_edges,omitempty"`
	// IndexingProfile (ADR-054, optional): one of content|control|signal|trace.
	IndexingProfile string `json:"indexing_profile,omitempty"`
}

Contract is the graph projection contract for one entity type / Graphable / gateway resource (ADR-056 Decision 6). It is OWNER-LESS — it declares the SHAPE of what a projection emits; the owner id is bound at Derive/Bind time, because the same projection shape may be emitted by different owners in different deployments.

func Lookup

func Lookup(name string) (Contract, bool)

Lookup returns a registered contract by name.

func Registered

func Registered() []Contract

Registered returns every registered contract, name-sorted for deterministic enumeration.

func (Contract) Validate

func (c Contract) Validate() error

Validate checks the contract is well-formed. It runs the contract-specific checks (name, one-write-mode-per-predicate, indexing profile) and then defers pattern / predicate / mode / foreign-edge / self-overlap validation to the ownership validators by deriving claims under a placeholder owner — so the projection layer never re-implements (and never drifts from) ownership's rules.

type CreateMutation

type CreateMutation struct {
	Contract string
	Entity   *graph.EntityState
	Triples  []message.Triple
	Metadata MutationMetadata
}

CreateMutation creates one authoritative entity with its complete initial projection facts.

type EntityCreator

type EntityCreator interface {
	CreateWithTriples(context.Context, CreateMutation) (MutationReceipt, error)
}

EntityCreator is the least-privilege atomic entity-birth capability.

type EvidenceAppender

type EvidenceAppender interface {
	AppendEvidence(context.Context, AppendEvidenceMutation) (MutationReceipt, error)
}

EvidenceAppender is the least-privilege append-only evidence capability.

type ForeignEdge

type ForeignEdge struct {
	Predicate     string             `json:"predicate"`
	Mode          ownership.EdgeMode `json:"mode"`
	TargetPattern string             `json:"target_pattern,omitempty"`
}

ForeignEdge is a relationship edge the projection writes onto a DIFFERENT entity than the one it owns (ADR-056 Decision 4). TargetPattern is the 6-part glob of entities the edge lands on (empty = match-any).

type MutationClient

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

MutationClient is an immutable, concurrency-safe contract-bound graph writer.

func BindMutationClient

func BindMutationClient(ctx context.Context, cfg MutationClientConfig) (*MutationClient, error)

BindMutationClient validates and copies the complete contract set before registering its ownership and liveness. The returned client never exposes or refreshes the minted owner token.

func (*MutationClient) AppendEvidence

func (c *MutationClient) AppendEvidence(
	ctx context.Context,
	req AppendEvidenceMutation,
) (MutationReceipt, error)

AppendEvidence appends contract-authorized evidence to an existing entity with ambiguity-safe authoritative readback.

func (*MutationClient) CreateWithTriples

func (c *MutationClient) CreateWithTriples(
	ctx context.Context,
	req CreateMutation,
) (MutationReceipt, error)

CreateWithTriples atomically creates an entity with the complete initial facts authorized by the named projection contract.

func (*MutationClient) ReadAuthoritative

func (c *MutationClient) ReadAuthoritative(ctx context.Context, entityID string) (*graph.EntityState, error)

ReadAuthoritative returns the current graph-ingest source-of-truth state for entityID.

func (*MutationClient) ReplaceOwned

func (c *MutationClient) ReplaceOwned(
	ctx context.Context,
	req ReplaceOwnedMutation,
) (MutationReceipt, error)

ReplaceOwned reconciles one complete replace-owned predicate group on an existing entity while preserving facts outside that group.

type MutationClientConfig

type MutationClientConfig struct {
	NATS        *natsclient.Client
	Registry    *ownership.Registry
	Heartbeater *ownership.Heartbeater
	Owner       string
	Contracts   []Contract
	Timeout     time.Duration
	Retry       natsclient.RetryConfig
}

MutationClientConfig binds one immutable mutation client to an owner and its complete projection-contract set.

type MutationError

type MutationError struct {
	Operation MutationOperation
	Kind      MutationErrorKind
	Code      string
	Class     errs.ErrorClass
	Commit    CommitState
	Detail    map[string]any
	Err       error
}

MutationError preserves the existing classified or sentinel cause while adding operation and commit knowledge.

func (*MutationError) Error

func (e *MutationError) Error() string

func (*MutationError) Unwrap

func (e *MutationError) Unwrap() error

Unwrap preserves existing errors.As and errors.Is behavior.

type MutationErrorKind

type MutationErrorKind string

MutationErrorKind is the stable caller-facing mutation failure taxonomy.

const (
	MutationInvalid             MutationErrorKind = "invalid"
	MutationNotFound            MutationErrorKind = "not-found"
	MutationConflict            MutationErrorKind = "conflict"
	MutationRevisionConflict    MutationErrorKind = "revision-conflict"
	MutationStaleOwnerToken     MutationErrorKind = "stale-owner-token"
	MutationUnavailable         MutationErrorKind = "unavailable"
	MutationCommitUnknown       MutationErrorKind = "commit-unknown"
	MutationCommittedUnverified MutationErrorKind = "committed-unverified"
	MutationInternal            MutationErrorKind = "internal"
)

Mutation error kinds form the stable caller-facing failure taxonomy.

type MutationMetadata

type MutationMetadata struct {
	RequestID string
	TraceID   string
	Source    string
	Timestamp time.Time
}

MutationMetadata carries stable correlation and provenance for one logical mutation across every transport attempt and authoritative verification.

type MutationOperation

type MutationOperation string

MutationOperation identifies the public capability that produced an outcome.

const (
	MutationOperationBind              MutationOperation = "bind"
	MutationOperationCreate            MutationOperation = "create-with-triples"
	MutationOperationReplaceOwned      MutationOperation = "replace-owned"
	MutationOperationAppendEvidence    MutationOperation = "append-evidence"
	MutationOperationReadAuthoritative MutationOperation = "read-authoritative"
)

Mutation operations identify each public mutation-client capability.

type MutationReceipt

type MutationReceipt struct {
	Entity     *graph.EntityState
	KVRevision uint64
	Commit     CommitState
	Degraded   bool
}

MutationReceipt is returned on both successful and commit-aware error paths.

type OwnedReplacer

type OwnedReplacer interface {
	ReplaceOwned(context.Context, ReplaceOwnedMutation) (MutationReceipt, error)
}

OwnedReplacer is the least-privilege owned-state reconciliation capability.

type PredicateGroup

type PredicateGroup struct {
	Name       string              `json:"name,omitempty"`
	Mode       ownership.WriteMode `json:"mode"`
	Predicates []string            `json:"predicates"`
}

PredicateGroup is a set of predicates a projection emits in ONE write mode (ADR-056 Decision 1). A predicate appears in exactly one group per contract — one predicate, one write mode per owner.

type ReplaceOwnedMutation

type ReplaceOwnedMutation struct {
	Contract string
	Group    string
	EntityID string
	Desired  []message.Triple
	Metadata MutationMetadata
}

ReplaceOwnedMutation reconciles the complete replace-owned predicate set declared by one contract.

Jump to

Keyboard shortcuts

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