core

package
v1.34.0 Latest Latest
Warning

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

Go to latest
Published: Sep 18, 2026 License: MIT Imports: 26 Imported by: 0

Documentation

Overview

Package core reconciles a tree of declarative files against a remote API: it parses them, resolves the references between them, orders the work, diffs it against what the server holds, and records the outcome in a lockfile.

It knows nothing about any particular API beyond its general shape: JSON bodies, tagged string IDs, optional versions. Everything domain-specific — what kinds exist, which fields the server owns, what the files and lockfile are called, how to talk to it — arrives through a Registry and a Client. The sibling claude package is the implementation for the Claude Developer Platform.

A run is Loader → Planner → Plan → Applier, with the Lockfile carrying state between runs. The files, in that order:

spec.go      KindSpec and Field: what a domain says about each kind
registry.go  Registry and Schema: the set of kinds, and the file layout
client.go    Client, Request and Payload: the API a domain provides
source.go    Source and Candidate: one file read and decoded
loader.go    Loader: paths to a closed set of Sources
resolve.go   following the references a Source makes
refs.go      reference syntax and Target, what a reference resolves to
include.go   splicing data files into list fields at load time
fetch.go     URLFetcher and the GitHub implementation
graph.go     dependency order and cycle reporting
desired.go   rendering the request body with references filled in
plan.go      Planner and Plan: what apply would do, and why
diff.go      field-level Diff, drift comparison, redaction
version.go   versions from the server, into the lockfile, and back out
apply.go     Applier: executing a Plan, saving state after each write
lock.go      Lockfile: the committed state file
hash.go      canonical JSON and fingerprints
bodypath.go  dotted-path helpers over decoded JSON

Index

Constants

View Source
const KnownAfterApply = "(known after apply)"

KnownAfterApply is the placeholder a plan shows where a value will only exist once the resource it depends on has been created.

Variables

View Source
var ErrNotFound = errors.New("resource not found")

ErrNotFound is what a Client returns when a recorded resource is gone from the server — deleted out of band, or belonging to a different account than the credentials in use.

Functions

func EncodeID

func EncodeID(t Target) any

EncodeID renders a reference as a bare ID string.

func EncodeObject

func EncodeObject(typeName, idField string) func(Target) any

EncodeObject renders a reference as a discriminated object: a `type`, the ID under idField, and the pinned version when the target has one. This is the shape most APIs use for a typed reference; a domain names the discriminator and the field.

func Hint

func Hint(err error) string

Hint returns the advice attached to err, if any.

func IsEmptyValue

func IsEmptyValue(v any) bool

IsEmptyValue reports whether v holds nothing: nil, the empty string, or an empty list or map. Diffing and rendering treat such a value the same as an absent one.

func ParseYAMLMap

func ParseYAMLMap(data []byte, what string) (map[string]any, error)

ParseYAMLMap decodes YAML into a map with string keys throughout. Every nested map must come out map[string]any, because request bodies are eventually run through json.Marshal in canonicalJSON, which rejects interface-keyed maps. goccy/go-yaml stringifies even non-string keys, so no normalizing pass is needed — TestYAMLKeysAreAlwaysStrings pins that so a dependency bump fails there rather than as an opaque marshal error while hashing.

func WithHint

func WithHint(err error, hint string) error

WithHint attaches advice to an error without changing what it is: callers that unwrap to the API error underneath still can, and a renderer that asks for the hint gets something to tell the user to do.

Types

type Action

type Action string

Action is what apply will do to one resource.

const (
	ActionCreate  Action = "create"
	ActionUpdate  Action = "update"
	ActionNoop    Action = "no change"
	ActionDestroy Action = "destroy"
)

type Applied

type Applied struct {
	Change *Change
	// ID is the resource's identifier after the operation, when it has one.
	ID string
	// Outcome is what was actually done: "created", "updated", "unchanged",
	// the kind's Destroy.Past, "already gone" — or "failed" or "interrupted",
	// with Err set.
	Outcome string
	Err     error
}

Applied is one resource's outcome, as it happens.

type Applier

type Applier struct {
	Client Client
	Lock   *Lockfile
	// Report is told about each resource as soon as it has been applied, so a
	// long apply shows progress. Optional.
	Report func(Applied)
}

Applier executes a Plan. It persists state after every individual success, because the alternative — writing the lockfile once at the end — loses the IDs of everything created before a mid-run failure, and those resources are then invisible to the next apply and get created a second time.

func (*Applier) Apply

func (a *Applier) Apply(ctx context.Context, plan *Plan) (*Result, error)

Apply executes the plan in order and stops at the first failure. A plan with any blocked change is refused before anything is sent, with a nil Result. Otherwise the Result counts what was done, even alongside an error, and the lockfile has been saved.

type Candidate

type Candidate struct {
	// Path is the file's absolute path.
	Path string
	// Markdown reports whether the file has a prose body as well as fields.
	Markdown bool
	// Prose is the markdown after the frontmatter, empty for a YAML file.
	Prose []byte
	// Fields are the declared fields: the frontmatter of a markdown file, or
	// the whole document of a YAML one. The `type` field is removed once the
	// file is classified.
	Fields map[string]any
	// Name is the default for a resource's `name`: the filename without its
	// extension, unless the Schema refines it while classifying.
	Name string
	// HasFrontmatter tells an empty frontmatter block apart from no
	// frontmatter at all: only the latter means the file is prose.
	HasFrontmatter bool
	// DecodeErr holds a frontmatter or YAML failure. It is fatal for a path the
	// user named but merely disqualifying for one we walked into, so it travels
	// with the Candidate instead of aborting the read.
	DecodeErr error
	// contains filtered or unexported fields
}

Candidate is a file read and decoded once. The sniff that decides whether to take a file and the load that builds its Source look at the same bytes, so they cannot disagree and the file is not parsed twice.

type Change

type Change struct {
	Key    string
	Kind   Kind
	Action Action

	// Source is the local definition. Nil for a destroy, whose file is gone.
	Source *Source
	// Entry is the lockfile record. Nil for a create.
	Entry *LockEntry

	// Desired is the request body apply will send, with references resolved.
	Desired map[string]any
	// Remote is the object as the server currently has it, when known.
	Remote map[string]any

	// Hash is the desired-state fingerprint to record on success.
	Hash string
	// Diff is the field-level detail shown under an update; nil otherwise.
	Diff *Diff
	// Reasons explains why this change exists, beyond the field diff — an
	// out-of-band edit, a dependency version bump, a recreated resource.
	Reasons []string
	// Sensitive marks dotted paths whose values must never be printed.
	Sensitive map[string]bool

	// Replaces is the ID of the tracked resource this create supersedes,
	// when one that was gone or archived has to be made again.
	Replaces string
	// Drift is set when the server's copy no longer matches what we recorded,
	// meaning somebody changed it outside this config.
	Drift bool
	// Blocked carries the reason apply must refuse to proceed.
	Blocked error
	// Unresolved marks a body containing "(known after apply)" placeholders.
	Unresolved bool
	// contains filtered or unexported fields
}

Change is one planned operation.

func (*Change) Destroy

func (c *Change) Destroy() Destroy

Destroy is what removing this resource actually does.

func (*Change) IsSensitive

func (c *Change) IsSensitive(path string) bool

IsSensitive reports whether printing the whole value at path could disclose a secret: one declared at the path, or anywhere beneath it. List markers ("[]") are ignored on both sides, so "resources[].token" shields "resources" and "resources[]" alike.

func (*Change) SummaryFields

func (c *Change) SummaryFields() []string

SummaryFields lists the fields that identify this resource in plan output.

func (*Change) Upload

func (c *Change) Upload() (string, bool)

Upload describes the content this change uploads instead of a JSON body, e.g. "3 files". Returns false when the change is an ordinary body write.

type Client

type Client interface {
	// Get returns the resource as decoded JSON, or an error wrapping
	// ErrNotFound when the server has no such resource.
	Get(ctx context.Context, kind Kind, id string) (map[string]any, error)
	// Create makes a resource and returns the server's answer, which must
	// carry the new resource's string "id". For a payload-backed kind the
	// answer may instead be the version just uploaded, with its identifier
	// under "version".
	Create(ctx context.Context, kind Kind, req Request) (map[string]any, error)
	// Update writes to an existing resource and returns the server's answer,
	// in the same shape Create returns.
	Update(ctx context.Context, kind Kind, id string, req Request) (map[string]any, error)
	// Destroy archives or deletes, per the kind's capabilities. An error
	// wrapping ErrNotFound means the resource was already gone.
	Destroy(ctx context.Context, kind Kind, id string) error
}

Client is the API surface the reconciler needs. It is an interface so the planner and applier can be exercised against a fake without a network.

type Destroy

type Destroy struct {
	Verb string // "archive"
	Past string // "archived"
}

Destroy names how a kind is removed, in the two tenses plan and apply output need.

type Diff

type Diff struct {
	Kind DiffKind
	// Fields holds the differing members of an object, by name.
	Fields map[string]*Diff
	// Items holds the differing elements of a list, in display order.
	Items []ItemDiff
	// Before is the server's value and After the desired one, for the leaf
	// kinds that carry them.
	Before, After any
}

Diff describes how the desired body differs from the server's copy, as a tree that follows the body's own shape. A nil *Diff means no difference.

type DiffKind

type DiffKind int

DiffKind says what sort of node a Diff is.

const (
	// DiffObject and DiffList are interior nodes; see Fields and Items.
	DiffObject DiffKind = iota + 1
	DiffList
	// DiffValue is a changed leaf: Before became After.
	DiffValue
	// DiffText is a changed string, kept apart so a renderer can diff words.
	DiffText
	// DiffAdded is a value the server does not have yet (After only).
	DiffAdded
	// DiffRemoved is a value being cleared or a list element being dropped
	// (Before only).
	DiffRemoved
	// DiffSensitive is a change whose values must not be shown.
	DiffSensitive
	// DiffWriteOnly is a field the server never returns: it will be sent
	// again, and whether that changes anything is unknowable.
	DiffWriteOnly
)

type FetchedURL

type FetchedURL struct {
	// Dir is the local directory the URL was materialized into.
	Dir string
	URLPin
}

FetchedURL is a URL-referenced resource materialized on local disk.

type Field

type Field struct {
	// Computed marks a value the server owns and the config does not set —
	// timestamps, IDs, status. It is stripped before comparing, so a missing
	// Computed marking shows up as drift that never clears.
	Computed bool
	// WriteOnly marks a value the server never returns. It cannot be compared,
	// and it must never be printed. A "[]" segment in the path means "every
	// element of this array".
	WriteOnly bool
	// Immutable marks a value fixed when the resource is created. Changing it
	// needs a replace, which core refuses to do on the user's behalf.
	Immutable bool
	// Clearable marks a value that deleting from the file should actually
	// remove, by sending an explicit null. Without it the field follows the
	// API's usual omit-to-preserve rule, which is the safe default: a server
	// that fills in its own value would otherwise be fought with forever.
	Clearable bool
	// Archived marks the server-owned field whose presence means the resource
	// was archived out of band. Archiving is a one-way door, so the planner
	// offers a replacement rather than an update.
	Archived bool
	// Shorthand names the sub-field a bare scalar stands for when the API
	// accepts either form and always answers with the object:
	// `model: x` and `model: {id: x, …}` then compare equal.
	Shorthand string
	// Summary marks a field worth showing under a create in the plan. The
	// whole body would bury the plan in prose; these identify the resource.
	Summary bool
	// MatchBy names the members that identify an element of this list field,
	// most telling first, so a plan can pair elements up before diffing them.
	// Unset means id, then name; position is always the last resort.
	MatchBy []string

	// Metadata marks a string-to-string bag the server patches key by key, so
	// core sends null for keys the file no longer declares.
	Metadata bool
	// Ref marks a field holding a reference to another resource, written in
	// the config as a file path and sent to the API as an ID.
	Ref *Ref
	// Include marks a list field whose entries may name a data file — YAML or
	// JSON, by path or glob relative to the declaring file — whose contents
	// are spliced into the list where the entry stood. It is for lists of
	// hand-written API objects that some other tool generates (a framework
	// emitting the custom tool specs it executes, say), so the definition
	// stays declarative without being copied in by hand. The file is data,
	// not a resource: it gets no lockfile entry, but what it contributes is
	// part of the body, so editing it plans an update like any other edit.
	Include bool
}

Field describes one field of a resource, after the manner of a Terraform attribute. The zero value is an ordinary field: sent as written, compared as written, preserved when omitted.

type Fields

type Fields map[string]Field

Fields describes a kind's fields, keyed by dotted path.

type Found

type Found struct {
	Key  string
	Kind Kind
}

Found is a resource Discover noticed on disk.

type GitHubFetcher

type GitHubFetcher struct {
	HTTPClient *http.Client
	// Token authenticates against private repositories. Read from GITHUB_TOKEN
	// or GH_TOKEN by NewGitHubFetcher.
	Token string
	// CacheDir holds extracted archives across applies.
	CacheDir string
	// contains filtered or unexported fields
}

GitHubFetcher fetches resources from github.com tree URLs. Other hosts get a clear error rather than a half-working guess. Build one with NewGitHubFetcher; the zero value has no cache to record fetches in.

func NewGitHubFetcher

func NewGitHubFetcher(cacheDir string) *GitHubFetcher

NewGitHubFetcher returns a fetcher that keeps extracted archives under cacheDir and authenticates with GITHUB_TOKEN, or GH_TOKEN when that is unset.

func (*GitHubFetcher) Fetch

func (g *GitHubFetcher) Fetch(ctx context.Context, rawURL string, pin URLPin) (*FetchedURL, error)

Fetch implements URLFetcher. An unpinned URL is resolved against the GitHub API first; a pinned one goes straight to its recorded commit. Every error it returns starts with rawURL, so the user can tell which reference failed.

type ItemDiff

type ItemDiff struct {
	Before, After int
	Diff          *Diff
}

ItemDiff is one element of a list diff. Before and After are the element's index on the server and in the desired body; -1 means absent on that side. A nil Diff with both indices set is an element that only moved.

type Kind

type Kind string

Kind is a resource type. Values are defined by the domain, not here.

type KindSpec

type KindSpec struct {
	// Kind is the kind this spec describes.
	Kind Kind

	// IDPrefix is the leading token of the API's tagged IDs ("agent" in
	// "agent_01…"), used to tell an inline ID reference from a file path.
	IDPrefix string

	// VersionField is the response field holding the resource's version, if it
	// has one. Empty means the kind is unversioned, and drift can only be
	// detected by fingerprinting the whole object.
	VersionField string
	// VersionIsInt renders the version as a number rather than a string when
	// it is sent back to the API.
	VersionIsInt bool
	// UpdateNeedsVersion means update requires the caller to echo the current
	// version back, under `version` in the request body, which is how the API
	// rejects a racing writer.
	UpdateNeedsVersion bool

	// Destroy is what removal actually does, for plan output: most APIs
	// offer archive, delete, or only one of the two.
	Destroy Destroy

	// Fields describes the resource's fields, one entry per field, keyed by a
	// dotted path into the body. Everything the reconciler needs to know about
	// a field lives in its entry rather than being spread across parallel
	// lists, so one line says everything about a field such as `description`.
	Fields Fields

	// Build turns a parsed file into a request body. Required for every kind a
	// Schema can classify from a file. Leave it nil for a kind whose content
	// travels as a Payload: core then refuses to load one from a file, and
	// reads a write's version from the version object the upload answers with.
	Build func(c *Candidate) (map[string]any, error)
	// contains filtered or unexported fields
}

KindSpec is everything core needs to know about one kind. A domain builds one per kind and hands the set over as a Registry; keeping them together means a reader sees every fact about a kind in one place, and adding a kind is one literal rather than an edit in each phase of the engine.

func (KindSpec) Clearable

func (s KindSpec) Clearable() []string

Clearable lists the fields that deleting from a file actually removes.

func (KindSpec) Computed

func (s KindSpec) Computed() []string

Computed lists the fields the server owns.

func (KindSpec) IsArchived

func (s KindSpec) IsArchived(remote map[string]any) bool

IsArchived reports whether the server's copy carries the kind's tombstone. Archiving is a one-way door, so the only way forward is a new resource.

func (KindSpec) RefSlots

func (s KindSpec) RefSlots() []RefSlot

RefSlots lists the reference fields, in a stable order.

func (KindSpec) WriteOnly

func (s KindSpec) WriteOnly() []string

WriteOnly lists the fields the server never returns.

type Loader

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

Loader turns a set of paths into a closed set of Sources: every reference reachable from the named paths is loaded too, so naming one file is enough to apply it together with everything it depends on.

func NewLoader

func NewLoader(registry *Registry, root string, fetcher URLFetcher) *Loader

NewLoader returns a loader for the kinds in registry, keying resources relative to root. A nil fetcher disables URL references.

func (*Loader) Add

func (l *Loader) Add(ctx context.Context, paths []string) error

Add loads the given paths (files, directories or globs) and everything they reference. The context bounds URL fetches.

func (*Loader) AddKeys

func (l *Loader) AddKeys(ctx context.Context, keys []string) error

AddKeys loads resources named by lockfile keys, skipping any whose file has been removed — those become deletions, which the planner handles from the lockfile alone.

func (*Loader) Discover

func (l *Loader) Discover(dir string) ([]Found, error)

Discover lists the resources under dir that this loader has not loaded, by the same conservative rules a directory walk applies — but without building bodies or following references, so one malformed file cannot hide its neighbours. It is for offering untracked files to the user, who decides; nothing here is added to the loader.

func (*Loader) Pin

func (l *Loader) Pin(pins map[string]URLPin)

Pin seeds what a previous apply recorded for each URL. Without this every apply would re-resolve a branch URL and churn the resource on every push to that branch.

func (*Loader) Sources

func (l *Loader) Sources() map[string]*Source

Sources returns everything loaded, keyed by lockfile key.

func (*Loader) TopoOrder

func (l *Loader) TopoOrder() ([]string, error)

TopoOrder returns every loaded resource in dependency order: a resource always appears after everything it references. Ties break by kind then key so plan output is stable run to run.

type LockEntry

type LockEntry struct {
	Kind Kind   `json:"kind"`
	ID   string `json:"id"`
	// Version is the resource version at last apply: an integer for some
	// kinds, an epoch string for others, empty for kinds that don't version.
	// Kept as a string so both round-trip losslessly.
	Version string `json:"version,omitempty"`
	// Hash fingerprints the desired state we last sent. A mismatch means the
	// local files changed.
	Hash string `json:"hash"`
	// RemoteHash fingerprints the normalized remote object as it looked right
	// after that apply. A mismatch means somebody changed it out of band —
	// this is the only drift signal available for kinds with no version field.
	RemoteHash string `json:"remote_hash,omitempty"`
	// Revision and Subpath are the commit a URL-sourced resource was pinned
	// to and where in it the resource lives.
	Revision string `json:"revision,omitempty"`
	Subpath  string `json:"subpath,omitempty"`
	// contains filtered or unexported fields
}

LockEntry is the recorded outcome of the last successful apply of one resource. It is the only thing standing between "this file changed" and "the resource changed", so every field here exists to answer a specific question the next plan has to ask.

func (LockEntry) MarshalJSON

func (e LockEntry) MarshalJSON() ([]byte, error)

MarshalJSON writes the known fields and merges the unknown ones back in. It has a value receiver so that a LockEntry marshals the same way whether or not it is addressable.

func (*LockEntry) UnmarshalJSON

func (e *LockEntry) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes the known fields and keeps any others in unknown.

type Lockfile

type Lockfile struct {
	Path      string
	Resources map[string]*LockEntry
	// Origin is where these resources live; nil until the first apply
	// records it, and in lockfiles written before it existed.
	Origin *Origin
	// contains filtered or unexported fields
}

Lockfile is the on-disk state file.

func FindLockfile

func FindLockfile(r *Registry, start string) (*Lockfile, error)

FindLockfile walks up from start looking for a lockfile, stopping after the first directory that holds .git. When none is found it returns an empty lockfile rooted at start, which is what a first apply wants.

func LoadLockfile

func LoadLockfile(r *Registry, path string) (*Lockfile, error)

LoadLockfile reads a lockfile from an explicit path. A missing file is not an error; a malformed one is.

func (*Lockfile) Existed

func (lf *Lockfile) Existed() bool

Existed reports whether the lockfile was on disk when it was loaded.

func (*Lockfile) Get

func (lf *Lockfile) Get(key string) (*LockEntry, bool)

Get returns the entry recorded for key, if any.

func (*Lockfile) Keys

func (lf *Lockfile) Keys() []string

Keys returns the recorded resource keys in sorted order.

func (*Lockfile) Pins

func (lf *Lockfile) Pins() map[string]URLPin

Pins returns what was recorded per URL key, for seeding the loader.

func (*Lockfile) Root

func (lf *Lockfile) Root() string

Root is the directory resource keys are relative to.

func (*Lockfile) Save

func (lf *Lockfile) Save() error

Save writes the lockfile atomically. encoding/json sorts map keys and emits struct fields in declaration order, so the output is byte-stable across runs — a lockfile that reorders itself produces noisy commits and pointless merge conflicts.

type Origin

type Origin struct {
	BaseURL        string `json:"base_url,omitempty"`
	OrganizationID string `json:"organization_id,omitempty"`
	WorkspaceID    string `json:"workspace_id,omitempty"`
}

Origin says where a lockfile's resources live: which API host and, when the credentials that wrote it knew, which organization and workspace. It is recorded on the first write so a later run with different credentials can be refused before it mistakes "not yours" for "deleted".

type Payload

type Payload interface {
	// Fingerprint identifies the payload's content. Two payloads with the same
	// fingerprint are treated as unchanged.
	Fingerprint() (string, error)
	// Describe is a short human summary for plan output, e.g. "3 files".
	Describe() string
}

Payload is content that is not a JSON body — a bundle of files uploaded as multipart, for instance. Core never inspects it; it only fingerprints it for change detection and hands it back to the Client.

type Plan

type Plan struct {
	Changes  []*Change
	Warnings []string
	// LockfilePath is where state will be written.
	LockfilePath string
	// LockfileExisted reports whether state was found or is being started.
	LockfileExisted bool
	// contains filtered or unexported fields
}

Plan is the full set of operations, in execution order.

func (*Plan) Blocked

func (p *Plan) Blocked() []*Change

Blocked returns the changes apply must refuse to perform.

func (*Plan) Change

func (p *Plan) Change(key string) (*Change, bool)

Change returns the planned change for a resource key, or false when the plan has none.

func (*Plan) Counts

func (p *Plan) Counts() (create, update, destroy, noop int)

Counts summarizes the plan for the confirmation prompt.

func (*Plan) HasWork

func (p *Plan) HasWork() bool

HasWork reports whether anything would actually change.

type Planner

type Planner struct {
	// Registry supplies the kinds this planner understands.
	Registry *Registry
	Client   Client
	Lock     *Lockfile

	// Force proceeds even when a resource changed out of band.
	Force bool
	// Prune destroys resources that are in the lockfile but no longer on disk.
	Prune bool
	// Concurrency bounds parallel reads of remote state. Zero or less means 8.
	Concurrency int
	// contains filtered or unexported fields
}

Planner diffs local definitions against the API and produces a Plan.

func (*Planner) Plan

func (p *Planner) Plan(ctx context.Context, loader *Loader) (*Plan, error)

Plan builds the execution plan for everything the loader has gathered.

type Ref

type Ref struct {
	// To restricts what the reference may name.
	To []Kind
	// List marks a field holding several references rather than one.
	List bool
	// As renders a resolved target into the body. Nil writes the bare ID.
	As func(Target) any
}

Ref describes a reference field: what it may point at, and how the resolved target is written into the request body.

type RefSlot

type RefSlot struct {
	Path string
	Ref
}

RefSlot is a Ref located at a dotted path into the body. NewRegistry derives one for each field in KindSpec.Fields whose Ref is set. References are declared, never sniffed: sniffing would turn a system prompt that mentions "./notes" into a dependency.

type Registry

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

Registry is the set of kinds a reconciler works with, in an order where a kind's dependencies come before it.

func NewRegistry

func NewRegistry(schema Schema, specs ...KindSpec) *Registry

NewRegistry builds a registry. Specs must be given dependencies-first: plan output, tie-breaking and destroy ordering all rely on it, and TestKindOrderIsTopological in the domain package is what keeps it honest.

func (*Registry) KindForID

func (r *Registry) KindForID(id string) (Kind, bool)

KindForID maps a tagged ID back to its kind. It returns false for anything that does not look like an ID, which is how a reference is told apart from a file path. Too strict fails loudly (an ID treated as a path matches nothing on disk); too loose fails silently (a path sent to the API as an ID).

An ID may carry one lowercase label between the prefix and the suffix ("agent_label_0123…"); that is still an ID.

func (*Registry) Kinds

func (r *Registry) Kinds() []Kind

Kinds lists every kind, dependencies first.

func (*Registry) LockfileName

func (r *Registry) LockfileName() string

LockfileName is the state file's conventional name, as the domain has it.

func (*Registry) Schema

func (r *Registry) Schema() Schema

Schema is the domain's description of its file layout.

func (*Registry) Spec

func (r *Registry) Spec(k Kind) (KindSpec, bool)

Spec returns a kind's specification.

func (*Registry) Valid

func (r *Registry) Valid(k Kind) bool

Valid reports whether a kind is registered.

type Request

type Request struct {
	Body    map[string]any
	Payload Payload
}

Request is what the Client is asked to send: a JSON body, a payload, or both.

type Result

type Result struct {
	Created   int
	Updated   int
	Destroyed int
	Unchanged int
}

Result summarizes what an apply actually did.

type Schema

type Schema interface {
	// Classify names the kind a parsed file declares. Returning ("", nil) means
	// "not a resource": a directory walk must tolerate that, since a repository
	// is full of prose and CI config. walked reports whether the file was found
	// by walking rather than named outright, which is usually the difference
	// between skipping something and complaining about it. Classify may read
	// the `type` field to decide; core drops that field from c.Fields
	// afterwards, so a Build never sees it.
	Classify(c *Candidate, walked bool) (Kind, error)
	// IsResourceDir reports whether a directory is itself a resource rather
	// than a folder of them. It is asked often, so it must be cheap — a stat,
	// not a walk.
	IsResourceDir(dir string) (Kind, bool)
	// LoadDir loads a directory IsResourceDir accepted.
	LoadDir(dir string) (Kind, map[string]any, Payload, error)
	// ResourceDirOf maps a file that stands in for a directory-backed resource
	// to that directory — a marker file to its folder.
	ResourceDirOf(path string) (string, bool)
	// LockfileName is the state file's conventional name. A directory walk
	// skips it, and discovery looks for it walking up.
	LockfileName() string
}

Schema is how a domain describes its file layout. Core reads files, resolves references between them and reconciles the result; it has no opinion about which file means what, so every convention — a directory name, an extension, a marker file — lives behind this interface rather than as a flag core has to know the meaning of.

type Source

type Source struct {
	// Key is the stable identity used in the lockfile and in plan output:
	// a root-relative slash path prefixed with "./", or the verbatim URL for
	// a URL-sourced resource.
	Key  string
	Kind Kind

	// Path is the absolute path of the defining file. Empty when the resource
	// came from a URL.
	Path string
	// Dir is set when the resource is declared by a directory rather than a
	// file.
	Dir string
	// Payload is the non-JSON content of a directory-backed resource.
	Payload Payload
	// URL is set when the resource was fetched from a URL rather than read
	// from the working tree, and Pin records what it resolved to.
	URL string
	Pin URLPin

	// Body is the declared request body, minus apply-only directives.
	Body map[string]any
}

Source is one resource as declared on disk, or fetched from a URL. Body holds the declared fields verbatim; references inside it are still paths at this stage and get resolved to IDs later, once the graph is ordered.

type Target

type Target struct {
	Kind Kind
	ID   string
	// Version is the referenced resource's version in its JSON form: an
	// integer for some kinds, an epoch string for others. Nil when there is
	// none to pin yet.
	Version any
	// Pinned is false when the user asked to always track latest, in which
	// case EncodeObject omits the version and lets the server resolve it.
	Pinned bool
	// Versioned reports whether the target's kind has a version at all;
	// pinning is meaningless for one that does not.
	Versioned bool
	// Known is false at plan time for a resource that does not exist yet, so
	// its ID and version are "known after apply".
	Known bool
}

Target is a resolved reference: what the referenced resource became after (or, at plan time, is predicted to become after) apply.

type URLFetcher

type URLFetcher interface {
	// Fetch downloads rawURL. When pin carries a revision the fetcher must
	// return exactly that rather than whatever the URL's branch currently
	// points at.
	Fetch(ctx context.Context, rawURL string, pin URLPin) (*FetchedURL, error)
}

URLFetcher materializes a URL-referenced resource into a local directory and reports the immutable revision it resolved to.

type URLPin

type URLPin struct {
	// Revision is the immutable identifier the URL resolved to — a commit SHA
	// for GitHub.
	Revision string
	// Subpath is where in that revision the resource lives. A tree URL does
	// not say where the branch name ends and the path begins; this records
	// the answer so a pinned fetch need not rediscover it.
	Subpath string
}

URLPin is what the lockfile remembers about a URL so a later apply lands on the same content without asking the host again.

Jump to

Keyboard shortcuts

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