projection

package
v1.0.0-beta.112 Latest Latest
Warning

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

Go to latest
Published: Jun 19, 2026 License: MIT Imports: 7 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 the owner's typed ownership.OwnerToken to stamp on its writes
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.

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, surfaced on the bind result so the right path is the easy path (producers never hand-compose "<owner>#<incarnation>"). 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 a real OwnerClaim MUST heartbeat: RegisterOwner bumps its OWNER_PRESENCE key once at registration, but without ongoing heartbeats that key ages out after ownership.PresenceTTL and the next registrant compacts the claim out of the epoch — the FE-only compaction exemption (epoch.go) does NOT cover an owning claim.

Enrollment happens ONLY on a successful Bind: a rejected/overlapping owner holds no recorded claim to keep alive. A nil hb binds without enrolling (caller opted out of liveness, e.g. FE-only owners). 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 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 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"`
	// 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 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 PredicateGroup

type PredicateGroup struct {
	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.

Jump to

Keyboard shortcuts

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