keg

package
v0.41.0 Latest Latest
Warning

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

Go to latest
Published: Sep 9, 2026 License: Apache-2.0 Imports: 49 Imported by: 0

Documentation

Index

Constants

View Source
const (
	SnapshotModeAuto = "auto"
	SnapshotModeOff  = "off"

	DefaultSnapshotIdleAfter = time.Hour
)
View Source
const (
	RemoteCodeNotFound                   = "NOT_FOUND"
	RemoteCodeExist                      = "EXIST"
	RemoteCodeDestExists                 = "DEST_EXISTS"
	RemoteCodeConflict                   = "CONFLICT"
	RemoteCodePreconditionRequired       = "PRECONDITION_REQUIRED"
	RemoteCodeInvalid                    = "INVALID"
	RemoteCodeSchemaInvalid              = "SCHEMA_INVALID"
	RemoteCodeInvalidImage               = "INVALID_IMAGE"
	RemoteCodeLockMismatch               = "LOCK_MISMATCH"
	RemoteCodeNotLocked                  = "NOT_LOCKED"
	RemoteCodeLock                       = "LOCK"
	RemoteCodeLockTimeout                = "LOCK_TIMEOUT"
	RemoteCodeNotSupported               = "NOT_SUPPORTED"
	RemoteCodeUnauthorized               = "UNAUTHORIZED"
	RemoteCodeForbidden                  = "FORBIDDEN"
	RemoteCodeBadRequest                 = "BAD_REQUEST"
	RemoteCodeOrientationStale           = "ORIENTATION_STALE"
	RemoteCodeOrientationDenied          = "ORIENTATION_DENIED"
	RemoteCodeOrientationUnavailable     = "ORIENTATION_UNAVAILABLE"
	RemoteCodeOrientationRootUnavailable = "ORIENTATION_ROOT_UNAVAILABLE"
	RemoteCodeInternal                   = "INTERNAL"
)

Remote error codes carried in the hub's JSON error envelope ({"error": msg, "code": CODE}). The table below is the single source of truth for both directions: the hub maps sentinel errors to (code, status) when writing a response, and RemoteKeg maps (code, status) back to the sentinel when decoding one. Keep the two sides symmetric.

View Source
const (
	SchemasDir       = "schemas"
	SchemaFileSuffix = ".schema.yaml"

	// KegSchemaDefinitionSchemaURL is the published JSON Schema for keg schema
	// definition YAML. Editor modelines prefer the local copy materialized by
	// pkg/schemas and fall back to this.
	KegSchemaDefinitionSchemaURL = schemas.KegSchemaDefinitionURL
)
View Source
const (
	RelationDirectionLinks     = "links"
	RelationDirectionBacklinks = "backlinks"
)
View Source
const (
	TimelineIndexName = "timeline"
	DirtyIndexName    = "dirty"
)
View Source
const (
	SchemeHTTP  = "http"
	SchemeHTTPs = "https"
	SchemeAlias = "keg"
)
View Source
const DefaultLockTTL = 5 * time.Minute

DefaultLockTTL is the default time-to-live for a cross-process lock.

View Source
const FormatVocabularyDescription = "" /* 490-byte string literal not displayed */

FormatVocabularyDescription is the one-line summary of the listing field vocabulary. It is duplicated as a literal in the MCP tool schemas, which require a constant struct tag; a test holds the two in agreement.

View Source
const KegSettingsSchemaURL = schemas.KegSettingsURL

KegSettingsSchemaURL is the published JSON Schema for keg settings YAML. It is the $id of the schema and the fallback modeline target; the modeline itself is added only when keg settings are opened in an editor (see Tap.KegSettingsEdit), never by the serializers below — keg settings are persisted, and on a hub they are shared, so a modeline naming one machine's filesystem has no business in the stored document.

View Source
const MaxMutationBatchSize = 100
View Source
const OrientationHeaderName = "Tapper-Orientation"

OrientationHeaderName carries trusted Tapper session state between Tapper's RemoteKeg client and a Hub. It is internal protocol state, never a model tool argument and never authorization by itself.

Variables

View Source
var (
	// SettingsV1VersionString is the initial KEG settings version identifier.
	SettingsV1VersionString = "2023-01"

	// SettingsV2VersionString is the current KEG settings version identifier.
	SettingsV2VersionString = "2025-07"

	// FormatMarkdown is the short format identifier for Markdown content.
	FormatMarkdown = "markdown"

	// MarkdownContentFilename is the canonical filename hint used by KEG
	// archives and content parsers. It does not identify a storage backend.
	MarkdownContentFilename = "README.md"

	// FormatRST is the short format identifier for reStructuredText content.
	FormatRST = "rst"
)
View Source
var (
	ErrInvalid              = os.ErrInvalid    // invalid argument
	ErrExist                = os.ErrExist      // file already exists
	ErrNotExist             = os.ErrNotExist   // file does not exist
	ErrPermission           = os.ErrPermission // permission denied
	ErrParse                = errors.New("unable to parse")
	ErrConflict             = errors.New("conflict")
	ErrPreconditionRequired = errors.New("precondition required")
	ErrQuotaExceeded        = errors.New("quota exceeded")
	ErrRateLimited          = errors.New("rate limited")
	ErrNotSupported         = errors.New("not supported")
	ErrSchemaInvalid        = errors.New("schema validation failed")

	// ErrInvalidAssetName is returned when a node asset name is not a single safe
	// path component (empty, ".", "..", contains a path separator, or absolute).
	// Such names could let filepath.Join resolve outside the keg root, so they
	// are rejected at the repository boundary.
	ErrInvalidAssetName = errors.New("invalid asset name")

	// ErrDestinationExists is returned when a move/rename cannot proceed because
	// the destination node id already exists. Prefer returning a typed
	// DestinationExistsError that unwraps to this sentinel when callers may need
	// structured information.
	ErrDestinationExists = errors.New("destination already exists")

	// ErrLockTimeout indicates acquiring a repository or node lock timed out or
	// was canceled. Lock-acquiring helpers should wrap context/cancellation
	// information while preserving this sentinel for callers that need to detect
	// timeout semantics via errors.Is.
	ErrLockTimeout = errors.New("lock acquire timeout")

	// ErrLock indicates a generic failure to acquire a repository or node
	// lock. Use errors.Is(err, ErrLock) to detect non-timeout lock acquisition
	// failures.
	ErrLock = errors.New("cannot acquire lock")

	// ErrKegLockUpgrade reports an attempted read-to-write operation-boundary
	// upgrade. Callers must leave the read boundary before starting a write.
	ErrKegLockUpgrade = errors.New("cannot upgrade keg read boundary to write")
)

Sentinel errors used for simple equality-style checks.

View Source
var (
	// ErrUnauthorized indicates the API request lacked valid authentication
	// credentials (HTTP 401).
	ErrUnauthorized = errors.New("unauthorized")

	// ErrForbidden indicates the authenticated user lacks permission for the
	// requested operation (HTTP 403).
	ErrForbidden = errors.New("forbidden")
)

Sentinel errors for remote-API-specific failure conditions.

View Source
var (
	ErrOrientationStale           = errors.New("orientation stale")
	ErrOrientationDenied          = errors.New("orientation denied")
	ErrOrientationUnavailable     = errors.New("orientation unavailable")
	ErrOrientationRootUnavailable = errors.New("orientation root unavailable")
)
View Source
var ErrInvalidImage = errors.New("invalid image")
View Source
var ErrListViewUnsupported = errors.New("hub list view API is unavailable")

ErrListViewUnsupported reports that the hub predates the server-resolved listing endpoint. Callers degrade to assembling the listing client-side.

View Source
var ErrLockTokenMismatch = errors.New("lock token mismatch")

ErrLockTokenMismatch indicates the provided token does not match the held lock.

View Source
var ErrNotLocked = errors.New("node is not locked")

ErrNotLocked indicates no lock is held on the node.

View Source
var IndexTimeFieldNames = []string{"updated", "created", "accessed"}

IndexTimeFieldNames lists the statistics fields that resolve from the node index rather than from stats.json. These mirror resolveStatsCompare's no-I/O branch so a displayed value always agrees with the same predicate in a query expression.

View Source
var LegacyFormatVerbs = map[byte]string{
	'i': "id",
	't': "title",
	'd': ".updated",
	'c': ".created",
	'a': ".accessed",
}

LegacyFormatVerbs maps the historical single-letter format verbs onto selector text. These remain supported as aliases; no new letters are added, because a single letter cannot address an arbitrary metadata key.

View Source
var RawZeroNodeContent = `` /* 229-byte string literal not displayed */

RawZeroNodeContent is the fallback content used when a node has no content. It serves as a friendly placeholder indicating the content is planned but not yet available. Callers may display this as the node README. If you want the content created sooner, open an issue describing the request.

View Source
var ReservedFieldNames = []string{"id", "title", "tags"}

ReservedFieldNames lists the bare words that do not name a metadata key. A node carrying metadata under one of these keys cannot address it in field position; the intrinsic wins.

View Source
var StatsFieldNames = []string{
	"updated",
	"created",
	"accessed",
	"hash",
	"accessCount",
	"lead",
	"omega",
}

StatsFieldNames lists the dot-prefix stats field names recognized by the query expression parser. These correspond to fields in stats.json and NodeIndexEntry.

Functions

func AutoSnapshotMessage added in v0.28.0

func AutoSnapshotMessage(idleAfter time.Duration) string

AutoSnapshotMessage returns the deterministic message used for policy snapshots at a given idle window.

func DocumentHash added in v0.39.0

func DocumentHash(data []byte) string

DocumentHash returns the precondition token for a whole-document keg resource — a schema definition or the settings file. A caller echoes the token it read back on its next write, so a write is rejected when the document changed in between rather than silently overwriting the change.

Nodes have their own token (NodeView.Hash) derived from content and metadata together; this is the equivalent for resources that are a single opaque YAML document.

SHA-256 is deliberately fixed here rather than supplied by Runtime. These tokens cross local, browser, REST, and remote-client boundaries, so the same document must have the same token in every process.

func EncodeOrientationState added in v0.39.0

func EncodeOrientationState(state OrientationState) (string, error)

EncodeOrientationState returns the versioned header value.

func EvaluateQueryExpression added in v0.18.0

func EvaluateQueryExpression(
	expr QueryExpr,
	universe map[string]struct{},
	resolve func(tag string) map[string]struct{},
) map[string]struct{}

EvaluateQueryExpression evaluates expr against a universe of string identifiers. universe is the full candidate set (e.g. node paths). resolve maps a tag name to the subset of universe that carries that tag. Returns the subset of universe that satisfies the expression.

func EvaluateQueryExpressionWithCompare added in v0.18.0

func EvaluateQueryExpressionWithCompare(
	expr QueryExpr,
	universe map[string]struct{},
	resolve func(tag string) map[string]struct{},
	resolveCompare CompareResolver,
) map[string]struct{}

EvaluateQueryExpressionWithCompare evaluates expr with full support for dot-prefix stats comparisons. resolveCompare handles ".field op value" predicates. When resolveCompare is nil, dot-prefix comparisons match nothing.

func ExplicitMarkdownTitle added in v0.39.0

func ExplicitMarkdownTitle(data []byte) string

ExplicitMarkdownTitle returns the first explicit Markdown H1 after optional YAML frontmatter. It deliberately does not use ParseContent's fallback to a first non-empty line: create APIs use this helper when their contract requires the caller to supply an actual "# Title" heading.

func FieldValue added in v0.36.0

func FieldValue(sel FieldSelector, entry NodeIndexEntry, meta *NodeMeta, stats *NodeStats) string

FieldValue resolves one selector against a node's index entry and, when the selector requires them, its metadata and statistics. meta and stats may be nil; an unresolvable value renders empty so a tabular listing keeps a stable column count.

Intrinsics and index timestamps come from the entry, never from stats.json, so a displayed value always agrees with the same predicate in a query expression and the default listing stays free of per-node reads.

func FormatSelectorSuggestions added in v0.36.0

func FormatSelectorSuggestions() []string

FormatSelectorSuggestions returns the closed part of the field vocabulary as ready-to-type format tokens, for shell completion. Metadata keys are open-ended and therefore absent.

func IsBackendError

func IsBackendError(err error) bool

IsBackendError reports whether err is (or wraps) a BackendError.

func IsConflict

func IsConflict(err error) bool

IsConflict returns true if err is a conflict error.

func IsCoreIndex added in v0.2.0

func IsCoreIndex(name string) bool

IsCoreIndex reports whether the given index file name is one of the built-in protected index names (e.g. "changes.md").

func IsDestinationExists

func IsDestinationExists(err error) bool

IsDestinationExists returns true if err represents a destination-exists condition.

func IsInvalidSettings added in v0.39.0

func IsInvalidSettings(err error) bool

IsInvalidSettings reports whether err is (or wraps) an invalid-settings condition.

func IsPermissionDenied

func IsPermissionDenied(err error) bool

IsPermissionDenied returns true if err indicates a permission problem.

func IsRetryable

func IsRetryable(err error) bool

IsRetryable inspects the error chain for a Retryable() bool implementation and returns its result (false if none found).

func IsSchemaInvalid added in v0.25.0

func IsSchemaInvalid(err error) bool

func IsSystemIndex added in v0.23.0

func IsSystemIndex(name string) bool

IsSystemIndex reports whether name is a required generated index.

func IsTemporary

func IsTemporary(err error) bool

IsTemporary inspects the error chain for a Temporary() bool implementation and returns its result (false if none found).

func NewAliasNotFoundError

func NewAliasNotFoundError(alias string) error

NewAliasNotFoundError constructs a typed AliasNotFoundError.

func NewBackendError

func NewBackendError(backend, op string, status int, cause error, transient bool) error

NewBackendError constructs a *BackendError describing an operation against a backend.

func NewInvalidSettingsError added in v0.39.0

func NewInvalidSettingsError(msg string) error

NewInvalidSettingsError creates an InvalidSettingsError with a human message.

func NewRateLimitError

func NewRateLimitError(retryAfter time.Duration, msg string, cause error) error

NewRateLimitError constructs a *RateLimitError with a suggested retry duration.

func NewTransientError

func NewTransientError(cause error) error

NewTransientError constructs a *TransientError wrapping the provided cause.

func NormalizeTag

func NormalizeTag(s string) string

NormalizeTag normalizeTag lowercases, trims, and tokenizes a tag string into a hyphen-separated token.

func NormalizeTags

func NormalizeTags(tags []string) []string

func OrientationHeaderForURL added in v0.41.0

func OrientationHeaderForURL(ctx context.Context, target string) (string, bool)

OrientationHeaderForURL sends a proof only to the Hub that can validate it.

func OrientationHeaderValue added in v0.39.0

func OrientationHeaderValue(ctx context.Context) (string, bool)

OrientationHeaderValue returns the header for trusted context state.

func ParseStatsTime added in v0.18.0

func ParseStatsTime(raw string) time.Time

ParseStatsTime parses a time string using the same format layouts accepted by stats.json timestamps: RFC3339Nano, RFC3339, and several date-only and datetime variants. Returns the zero time if raw is empty or unparseable.

func ParseTags

func ParseTags(raw string) []string

ParseTags accepts a comma/semicolon/newline separated list of tags (or a whitespace-separated string when no explicit separators are present) and returns a normalized, deduplicated, sorted slice of tags.

Behavior: - Trims whitespace around tokens. - Lowercases tokens and converts internal whitespace to hyphens via NormalizeTag. - Splits on commas, semicolons, CR/LF, or newlines when present; otherwise splits on whitespace. - Deduplicates tokens and returns them in lexicographic order.

func RandomCode

func RandomCode(context.Context) string

func RejectFrontmatter added in v0.40.0

func RejectFrontmatter(content []byte) error

RejectFrontmatter refuses content that opens with a YAML frontmatter delimiter. A node is built from two separate inputs — content, the markdown body opening with its H1 title, and meta, the complete metadata document — so a frontmatter block is a second, silent way to write metadata. The failure mode is severe: a body that legitimately begins with a horizontal rule would either consume the following lines as metadata or fail deep in the parser with an unrelated message.

This lives here rather than in the tool layer so every writer reaches the same rule: the REST handlers, the MCP tools, the web UI, and the tap CLI all pass through create and update below.

func RelationshipTarget added in v0.41.0

func RelationshipTarget(raw string) (string, bool, error)

RelationshipTarget returns the canonical same-Hub KEG reference of a settings relationship. Ordinary external URLs are metadata only.

func RemoteErrorCode added in v0.23.0

func RemoteErrorCode(err error) (code string, status int)

RemoteErrorCode maps err to its wire (code, status). Unrecognized errors map to (INTERNAL, 500).

func RemoteErrorFromCode added in v0.23.0

func RemoteErrorFromCode(code string, status int, msg string) error

RemoteErrorFromCode maps a wire (code, status, message) back to an error wrapping the matching sentinel. Codes without a sentinel (UNAUTHORIZED, FORBIDDEN, BAD_REQUEST, INTERNAL, unknown) map by status: 401→ ErrUnauthorized, 403→ErrForbidden, 429→RateLimitError, 5xx→ transient BackendError, anything else → a plain error with the message.

func RenderMarkdown added in v0.11.0

func RenderMarkdown(src []byte, opts RenderOptions) ([]byte, error)

RenderMarkdown converts raw markdown bytes to HTML, rewriting node-relative link and image destinations to site paths per opts. The returned bytes are the inner HTML content (no <html> or <body> wrapper).

func RepoContainsKeg

func RepoContainsKeg(ctx context.Context, repo Repository) (bool, error)

RepoContainsKeg checks if a keg has been properly initialized within a repository. It verifies both that a keg settings exists and that a zero node (node ID 0) is present. Returns true only if both conditions are met, indicating a fully initialized keg.

func ResolveNodeLink(dest string, opts RenderOptions) (string, bool)

ResolveNodeLink resolves one raw markdown destination against opts. It returns the rewritten destination and true, or ("", false) when the destination should be left unchanged (absolute URLs, fragments, unparseable input, keg: links with no resolver, ...).

func ResolveRelationshipAlias added in v0.41.0

func ResolveRelationshipAlias(links []LinkEntry, alias string) (namespace, name string, err error)

ResolveRelationshipAlias resolves the explicit keg:~alias/node form using only the source KEG's settings, preserving canonical keg:@namespace/keg/node.

func SchemaFilename added in v0.25.0

func SchemaFilename(typeName string) (string, error)

func SelectorNeeds added in v0.36.0

func SelectorNeeds(selectors []FieldSelector) (meta, stats bool)

SelectorNeeds reports whether a set of selectors requires reading node metadata or statistics. Callers use it to skip per-node reads entirely for listings that name only intrinsics and index timestamps.

func StatsFieldValue added in v0.36.0

func StatsFieldValue(s *NodeStats, name string) (string, bool)

StatsFieldValue renders the named statistics field for display. known reports whether name is a recognized statistics field; an empty value with known true means the field is absent or unset on this node.

Absent values render empty rather than as a placeholder so a tabular format keeps a stable column count. accessCount is the one exception: it always renders its integer, including zero, because stats.json omits the key when it is zero and so absent and zero are indistinguishable on disk.

func UpdateMeta added in v0.23.0

func UpdateMeta(ctx context.Context, k Keg, id NodeId, f func(*NodeMeta)) error

UpdateMeta applies f to a node's metadata via read-then-set over the Keg interface. Not atomic across concurrent writers; see UpdateSettings.

func UpdateSettings added in v0.39.0

func UpdateSettings(ctx context.Context, k Keg, f func(*Settings)) error

UpdateSettings applies f to the keg's configuration via a read-then-set over the Keg interface. Unlike LocalKeg.UpdateSettings this is not atomic — a concurrent writer between the read and the set is lost — which is an accepted trade-off for rare admin operations over remote kegs.

func ValidSchemaTypeName added in v0.25.0

func ValidSchemaTypeName(typeName string) error

func ValidateAssetName added in v0.23.0

func ValidateAssetName(name string) error

ValidateAssetName reports whether name is a single safe node asset filename.

func ValidateImage added in v0.24.0

func ValidateImage(data []byte) (string, error)

ValidateImage decodes data enough to prove it is one of Tapper's supported image formats. It preserves caller bytes; it does not transcode or normalize.

func ValidateOrientation added in v0.39.0

func ValidateOrientation(ctx context.Context) error

ValidateOrientation runs the Hub-side validator, when one is installed.

func ValidateOrientationTarget added in v0.41.0

func ValidateOrientationTarget(ctx context.Context, target string) error

ValidateOrientationTarget refuses unresolved or mismatched routing before dispatch.

func ValidateRelationships added in v0.41.0

func ValidateRelationships(links []LinkEntry) error

ValidateRelationships requires unique aliases and canonical KEG targets.

func ValidateVideo added in v0.41.0

func ValidateVideo(data []byte) error

ValidateVideo rejects content that cannot be served as a supported video container.

func ValidationContextFromHeaders added in v0.25.0

func ValidationContextFromHeaders(ctx context.Context, header func(string) string) context.Context

func ValidationHeaderValues added in v0.25.0

func ValidationHeaderValues(ctx context.Context) map[string]string

func VideoContentType added in v0.41.0

func VideoContentType(data []byte) string

VideoContentType identifies supported original video containers from their bytes. Unknown content is served as a download, never as an active browser document.

func WithDefaultValidationActor added in v0.38.0

func WithDefaultValidationActor(ctx context.Context, actor ValidationActor) context.Context

WithDefaultValidationActor records actor only when the caller has not already supplied one. Shared Tap methods use this to give CLI invocations a human default without erasing the agent actor installed by MCP.

func WithOrientationState added in v0.39.0

func WithOrientationState(ctx context.Context, state OrientationState) context.Context

WithOrientationState binds trusted session orientation to an internal call context. RemoteKeg serializes it into OrientationHeaderName.

func WithOrientationValidator added in v0.39.0

func WithOrientationValidator(ctx context.Context, validate OrientationValidator) context.Context

WithOrientationValidator installs the Hub-side validation callback used by durable mutation transactions after acquiring their locks.

func WithPageScope added in v0.41.0

func WithPageScope(ctx context.Context, scope string) context.Context

WithPageScope binds opaque pagination cursors to the caller's authority scope.

func WithReadBoundary added in v0.36.0

func WithReadBoundary(ctx context.Context, k Keg, fn func(context.Context) error) error

WithReadBoundary runs fn inside a single keg read boundary when k has one.

A repository-backed LocalKeg's read boundary is an exclusive lock, and every per-node read takes it. Batches of reads must therefore share one boundary rather than acquiring it per call: the boundary is re-entrant through the context, so nested reads inside fn short-circuit instead of relocking. Without this, a listing that reads metadata for N nodes performs 2N exclusive lock cycles and blocks every other process on the keg for the duration.

A RemoteKeg has no client-side boundary to hold, so fn runs directly.

func WithValidationActor added in v0.25.0

func WithValidationActor(ctx context.Context, actor ValidationActor) context.Context

func WithValidationMode added in v0.25.0

func WithValidationMode(ctx context.Context, mode ValidationMode) context.Context

Types

type AliasNotFoundError

type AliasNotFoundError struct {
	Alias string
}

AliasNotFoundError is a typed error that carries the missing alias for callers that need richer diagnostic information.

func (*AliasNotFoundError) Error

func (e *AliasNotFoundError) Error() string

type AssetKind

type AssetKind string

AssetKind identifies an asset namespace for a node.

const (
	AssetKindImage AssetKind = "image"
	AssetKindItem  AssetKind = "item"
)

type AssetSummary added in v0.23.0

type AssetSummary struct {
	Supported       bool `json:"supported" yaml:"supported"`
	NodesWithAssets int  `json:"nodes_with_assets" yaml:"nodes_with_assets"`
	TotalAssets     int  `json:"total_assets" yaml:"total_assets"`
}

AssetSummary aggregates one asset kind for KegSummary.

type Attachment added in v0.41.0

type Attachment struct {
	ID   int            `json:"id"`
	Kind AttachmentKind `json:"kind"`
	Name string         `json:"name"`
	Size int64          `json:"size"`
}

Attachment identifies one original attachment, including its byte length.

type AttachmentKind added in v0.41.0

type AttachmentKind string

AttachmentKind keeps independent filename namespaces for original media.

const (
	AttachmentFile  AttachmentKind = "file"
	AttachmentImage AttachmentKind = "image"
	AttachmentVideo AttachmentKind = "video"
)

type AttachmentListRequest added in v0.41.0

type AttachmentListRequest struct {
	IDs    []int  `json:"ids"`
	Cursor string `json:"cursor,omitempty"`
	Limit  int    `json:"limit,omitempty"`
}

AttachmentListRequest selects 1–100 nodes and a bounded page of their attachments.

type AttachmentPage added in v0.41.0

type AttachmentPage struct {
	Results []Attachment `json:"results"`
	Cursor  string       `json:"cursor,omitempty"`
}

AttachmentPage is a deterministic page of kind-qualified attachments.

type BackendError

type BackendError struct {
	Backend    string // e.g. "s3", "http", "postgres", "fs"
	Op         string // operation, e.g. "WriteContent", "GetMeta"
	StatusCode int    // optional HTTP / backend status
	Cause      error
	Transient  bool // whether this is a transient error (retryable)
}

BackendError wraps errors coming from an external backend (API, DB, object store). It exposes Retryable() to indicate transient failures.

func ParseBackendError

func ParseBackendError(err error) *BackendError

func (*BackendError) Error

func (e *BackendError) Error() string

func (*BackendError) Retryable

func (e *BackendError) Retryable() bool

Retryable reports whether the backend error is transient.

func (*BackendError) Unwrap

func (e *BackendError) Unwrap() error

Unwrap returns the wrapped cause.

type BacklinkIndex

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

BacklinkIndex maps a destination node path to the list of source nodes that link to that destination. The underlying map keys are node.Path() values (string). The index is used to construct the "backlinks" index artifact.

The type is intended for in-memory, single-process use. Concurrency control is the caller's responsibility.

func ParseBacklinksIndex

func ParseBacklinksIndex(ctx context.Context, data []byte) (*BacklinkIndex, error)

ParseBacklinksIndex parses the raw bytes of a backlinks index into a BacklinkIndex.

Expected on-disk format is one line per destination:

"<dst>\t<src1> <src2> ...\n"

Behavior:

  • Empty or nil input yields an empty BacklinkIndex with no error.
  • Lines are split on tab to separate destination from space-separated sources.
  • Duplicate sources for a destination are tolerated and may be deduped by callers of Data.
  • This function does not modify any external state.

func (*BacklinkIndex) Add

func (idx *BacklinkIndex) Add(ctx context.Context, data *NodeData) error

Add incorporates backlink information derived from the provided NodeData. For each outgoing link listed in data.Links the function will add the source node (data.ID) to the corresponding destination entry in the index.

Behavior expectations:

  • If idx is nil the call is a no-op and returns nil.
  • If idx.data is nil it will be initialized.
  • The method should avoid introducing duplicate source entries for a given destination when possible.

This method only mutates in-memory state and does not perform I/O.

func (*BacklinkIndex) Data

func (idx *BacklinkIndex) Data(ctx context.Context) ([]byte, error)

Data serializes the index into the canonical on-disk format.

Serialization rules:

  • Each non-empty destination produces a line: "<dst>\t<src1> <src2> ...\n"
  • Source lists are deduplicated and sorted in a deterministic order.
  • Destination keys are emitted in a deterministic, parse-aware order (numeric node ids sorted numerically when possible, otherwise lexicographic).
  • An empty index returns an empty byte slice.

The returned bytes are owned by the caller and may be written atomically by the repository layer.

func (*BacklinkIndex) Rm

func (idx *BacklinkIndex) Rm(ctx context.Context, node NodeId) error

Rm removes any backlink references introduced by the given node. It removes the node as a source from any destination lists and may remove the entry for a destination if it ends up with no sources.

Behavior expectations:

  • If idx is nil the call is a no-op and returns nil.
  • If idx.data is nil it will be initialized to an empty map.
  • After removal, entries with no sources may either remain as empty slices or be deleted; callers should tolerate either representation.

This method only mutates in-memory state and does not perform I/O.

type BatchFailure added in v0.33.0

type BatchFailure struct {
	NodeID         NodeId `json:"node_id"`
	Code           string `json:"code"`
	Status         int    `json:"status"`
	Message        string `json:"message"`
	CurrentHash    string `json:"current_hash,omitempty"`
	CurrentContent []byte `json:"current_content,omitempty"`
}

func (*BatchFailure) Err added in v0.33.0

func (f *BatchFailure) Err() error

type BatchMutationError added in v0.38.0

type BatchMutationError struct {
	Index  int
	Key    string
	NodeID NodeId
	Err    error
}

func (*BatchMutationError) Error added in v0.38.0

func (e *BatchMutationError) Error() string

func (*BatchMutationError) Unwrap added in v0.38.0

func (e *BatchMutationError) Unwrap() error

type ChangesIndex added in v0.2.0

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

ChangesIndex is an in-memory index of all nodes sorted by updated time in reverse-chronological order (newest first). It is used to build the dex/changes.md index artifact.

Concurrency note: ChangesIndex does not perform internal synchronization. Callers that require concurrent access should guard an instance with a mutex.

func ParseChangesIndex added in v0.2.0

func ParseChangesIndex(ctx context.Context, data []byte) (ChangesIndex, error)

ParseChangesIndex parses the serialized dex/changes.md bytes into a ChangesIndex. Each non-empty line must be in the format:

  • YYYY-MM-DD HH:MM:SSZ [TITLE](../ID)

Malformed lines are silently skipped. An empty input yields an empty ChangesIndex with no error.

func (*ChangesIndex) Add added in v0.2.0

func (idx *ChangesIndex) Add(ctx context.Context, data *NodeData) error

Add inserts or updates the node in the index, maintaining reverse- chronological sort order (newest Updated first). If a node with the same ID already exists it is replaced.

func (*ChangesIndex) Clear added in v0.2.0

func (idx *ChangesIndex) Clear(ctx context.Context) error

Clear resets the index to an empty state.

func (*ChangesIndex) Data added in v0.2.0

func (idx *ChangesIndex) Data(ctx context.Context) ([]byte, error)

Data serializes the ChangesIndex to the canonical dex/changes.md format. Each entry is emitted as:

  • YYYY-MM-DD HH:MM:SSZ [TITLE](../ID)

Entries are in reverse-chronological order (newest first). An empty index returns an empty byte slice.

func (ChangesIndex) Entries added in v0.23.0

func (idx ChangesIndex) Entries(ctx context.Context) []NodeIndexEntry

Entries returns a copy of the parsed node-list entries.

func (*ChangesIndex) Rm added in v0.2.0

func (idx *ChangesIndex) Rm(ctx context.Context, node NodeId) error

Rm removes the node identified by node from the index. If the node is not present the call is a no-op.

type CompareResolver added in v0.18.0

type CompareResolver func(dotPrefix bool, field, op, value string) map[string]struct{}

CompareResolver is an optional callback for evaluating comparison predicates (e.g., ".created>2026-01-01" or "entity!=plan"). When set, the evaluator calls it with dotPrefix, field, op, and value and expects the set of matching identifiers. dotPrefix is true for dot-prefix stats fields (e.g., ".created>2026-01-01") and false for plain attribute comparisons (e.g., "entity!=plan"). When nil, comparisons match nothing.

type CreateNodeResult added in v0.38.0

type CreateNodeResult struct {
	Key        string                  `json:"key"`
	ID         NodeId                  `json:"id"`
	Hash       string                  `json:"hash"`
	Validation *SchemaValidationResult `json:"validation,omitempty"`
}

type CreateOptions

type CreateOptions struct {
	// Schema is the explicitly selected schema for this write.
	Schema string
	// Body is the raw markdown content; its H1 is the node's title. When
	// empty, a placeholder heading is generated from the allocated node id.
	Body []byte
	// Meta is the node's complete metadata document.
	Meta []byte
}

CreateOptions specifies parameters for creating a new node

type CreateResult added in v0.33.0

type CreateResult struct {
	ID         NodeId                  `json:"id"`
	Validation *SchemaValidationResult `json:"validation,omitempty"`
}

type Dex

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

Dex provides a high-level, in-memory view of the repository's generated dex indices: nodes, tags, links, backlinks, and changes. It is a convenience wrapper used by index builders and other tooling to read or inspect index data without dealing directly with repository I/O. Dex does not perform any I/O itself; callers are responsible for providing a Repository when writing indices.

func NewDexFromRepo

func NewDexFromRepo(ctx context.Context, repo Repository, opts ...DexOption) (*Dex, error)

NewDexFromRepo loads available index artifacts ("nodes.tsv", "tags", "links", "backlinks", "changes.md") from the provided repository and returns a Dex populated with parsed indexes. Missing or empty index files are treated as empty datasets and do not cause an error. Additional DexOptions (e.g. WithSettings) can be supplied to configure optional behaviour such as tag-filtered custom indexes.

All 5 index files are read and parsed concurrently for faster loading.

func (*Dex) Add

func (dex *Dex) Add(ctx context.Context, data *NodeData) error

Add adds the provided node to all managed indexes. This implements the IndexBuilder contract for convenience when using Dex as an aggregated builder.

func (dex *Dex) Backlinks(ctx context.Context, node NodeId) ([]NodeId, bool)

Backlinks returns the parsed backlinks index (map[dst] -> []src). NOTE: not intended to be mutated

func (*Dex) Changes added in v0.23.0

func (dex *Dex) Changes(ctx context.Context) []NodeIndexEntry

Changes returns a copy of the parsed changes index, newest entry first.

func (*Dex) Clear

func (dex *Dex) Clear(ctx context.Context)

Clear resets all in-memory index data held by the Dex instance.

func (*Dex) GetRef

func (dex *Dex) GetRef(ctx context.Context, id NodeId) *NodeIndexEntry
func (dex *Dex) Links(ctx context.Context, node NodeId) ([]NodeId, bool)

Links returns the parsed outgoing links index (map[src] -> []dst).

func (*Dex) NextNode

func (dex *Dex) NextNode(ctx context.Context) NodeId

func (*Dex) NodeListIndex added in v0.23.0

func (dex *Dex) NodeListIndex(ctx context.Context, name string) ([]NodeIndexEntry, bool)

NodeListIndex returns a node-list-compatible system index by filename.

func (*Dex) Nodes

func (dex *Dex) Nodes(ctx context.Context) []NodeIndexEntry

Nodes returns a copy of the parsed nodes index (slice of NodeRef).

func (*Dex) Remove

func (dex *Dex) Remove(ctx context.Context, node NodeId) error

Remove removes the node identified by id from all managed indexes. This implements the IndexBuilder contract for convenience when using Dex.

func (dex *Dex) TagLinks(ctx context.Context, node NodeId) ([]NodeId, bool)

TagLinks Tags returns the parsed tags index (map[tag] -> []NodeID).

func (*Dex) TagList

func (dex *Dex) TagList(ctx context.Context) []string

func (*Dex) TagNodes

func (dex *Dex) TagNodes(ctx context.Context, tag string) ([]NodeId, bool)

TagNodes returns the parsed tags index entry for tag (map[tag] -> []NodeID).

func (*Dex) Write

func (dex *Dex) Write(ctx context.Context, repo Repository) error

Write serializes the in-memory indexes and writes them atomically to the provided repository using WriteIndex. If any write operation fails the error chain is returned (errors.Join is used to aggregate multiple errors).

Serialization is performed under a read lock so concurrent readers are not blocked. The actual file writes happen after the lock is released, since they operate on independent byte buffers and repository WriteIndex calls are self-synchronizing (atomic file writes).

type DexArtifacts added in v0.33.0

type DexArtifacts struct {
	Indexes map[string][]byte `json:"indexes"`
}

type DexOption added in v0.2.0

type DexOption func(*Dex) error

DexOption is a functional option for NewDexFromRepo.

func WithQueryResolver added in v0.15.0

func WithQueryResolver(resolve func(term string, data *NodeData) bool) DexOption

WithQueryResolver sets a custom query term resolver for settings-driven custom indexes. When set, each term in a query expression is resolved by calling resolve(term, data) for each node, instead of the default tag-only resolver. This enables key=value attribute predicates and other term types defined in higher-level packages (e.g. pkg/tapper).

func WithSettings added in v0.39.0

func WithSettings(cfg *Settings) DexOption

WithSettings builds DexOptions from a keg Settings. It iterates cfg.Indexes and creates a QueryFilteredIndex for each entry that:

  • has a non-empty Query field, and
  • is not one of the core protected index names.

IndexEntry.File is the bare filename (e.g. "concepts.md"); the on-disk path is under the keg's dex/ directory.

By default, the index evaluates tag expressions against node tag sets. To support richer query terms (e.g. key=value attribute predicates), pass WithQueryResolver to inject a custom resolver callback.

type DoctorIssue added in v0.33.0

type DoctorIssue struct {
	Level   string `json:"level" yaml:"level"`
	Kind    string `json:"kind" yaml:"kind"`
	NodeID  string `json:"node_id,omitempty" yaml:"node_id,omitempty"`
	Message string `json:"message" yaml:"message"`
}

type ExportNodesOptions added in v0.23.0

type ExportNodesOptions struct {
	// NodeIDs selects nodes to export; empty exports every node.
	NodeIDs []NodeId
	// Query selects additional nodes as a union with NodeIDs.
	Query string
	// SkipZeroNode excludes the keg root node from the selection.
	SkipZeroNode bool
	// WithHistory includes snapshot revisions.
	WithHistory bool
	// HistoryIfSupported omits history instead of failing when snapshots are unavailable.
	HistoryIfSupported bool
	// WithAssets includes per-node files and images.
	WithAssets bool
	// Source labels the archive manifest with the origin keg reference.
	Source string
}

ExportNodesOptions configures Keg.ExportNodes.

type FieldKind added in v0.36.0

type FieldKind int

FieldKind classifies a listing field selector by where its value comes from. The kind determines whether rendering a selector costs any I/O: intrinsics and index timestamps are served from the node index entry already in hand, while metadata and the remaining statistics fields require a per-node read.

const (
	// FieldUnknown is the zero value and names no field.
	FieldUnknown FieldKind = iota
	// FieldID is the intrinsic node id, from NodeIndexEntry.
	FieldID
	// FieldTitle is the intrinsic node title, from NodeIndexEntry.
	FieldTitle
	// FieldIndexTime is a timestamp served from NodeIndexEntry without I/O.
	FieldIndexTime
	// FieldStat is a statistics field requiring a NodeStats read.
	FieldStat
	// FieldTags is the reserved tag-list selector, requiring a NodeMeta read.
	FieldTags
	// FieldMetaKey is an arbitrary metadata key, requiring a NodeMeta read.
	FieldMetaKey
)

type FieldSelector added in v0.36.0

type FieldSelector struct {
	Text string
	Kind FieldKind
	Key  string
}

FieldSelector is one parsed listing field selector. Text is the selector as written; Key is the bare lookup name with any leading dot stripped.

func ParseFieldSelector added in v0.36.0

func ParseFieldSelector(raw string) (FieldSelector, error)

ParseFieldSelector classifies raw as a listing field selector. Surrounding ASCII spaces are ignored so "%{ type }" behaves like "%{type}"; interior spaces are preserved because a YAML key may contain them.

A bare word is always accepted, because metadata keys are open-ended and cannot be validated against a fixed list. A dotted name is rejected unless it is a known statistics field, since that vocabulary is closed.

func ParseFieldSelectors added in v0.36.0

func ParseFieldSelectors(raw []string) ([]FieldSelector, error)

ParseFieldSelectors classifies a list of selectors, rejecting the whole list if any entry is invalid so a listing never silently drops a column.

func ParseSortSelector added in v0.36.0

func ParseSortSelector(raw string) (FieldSelector, error)

ParseSortSelector classifies a sort key. An empty key yields the zero selector, meaning "leave the listing in its natural index order".

func (FieldSelector) NeedsMeta added in v0.36.0

func (f FieldSelector) NeedsMeta() bool

NeedsMeta reports whether rendering this selector requires the node's metadata, which costs one read per node.

func (FieldSelector) NeedsStats added in v0.36.0

func (f FieldSelector) NeedsStats() bool

NeedsStats reports whether rendering this selector requires the node's statistics, which costs one read per node. Index timestamps do not, even though they are spelled like statistics fields.

type GrepMatch added in v0.23.0

type GrepMatch struct {
	Entry NodeIndexEntry
	// Lines are rendered "lineno:text" match lines.
	Lines []string
}

GrepMatch reports one node's content matches for Keg.Grep.

type GrepOptions added in v0.23.0

type GrepOptions struct {
	// Pattern is a Go regular expression matched against content lines.
	Pattern string
	// IgnoreCase makes the match case-insensitive.
	IgnoreCase bool
	// MaxLines caps matched lines per node. 0 means no cap.
	MaxLines int
}

GrepOptions configures Keg.Grep.

type HTTPOption added in v0.20.0

type HTTPOption = func(t *Target)

func WithBasicAuth added in v0.20.0

func WithBasicAuth(user, pass string) HTTPOption

func WithToken added in v0.20.0

func WithToken(token string) HTTPOption

type ImportNodesOptions added in v0.23.0

type ImportNodesOptions struct {
	// AssignNewIDs allocates fresh sequential node ids for the archive's
	// nodes instead of landing them on their archive ids. Links between
	// imported nodes are rewritten to the new ids.
	AssignNewIDs bool
	// HistoryIfSupported omits archived history instead of failing when the
	// destination repository does not implement snapshots.
	HistoryIfSupported bool
	// SourceAlias, when set, rewrites relative links that point at
	// un-imported source nodes to keg:SourceAlias/N cross-keg links.
	SourceAlias string
	// TargetAlias, when set, rewrites keg:TargetAlias/N links in imported
	// content to relative ../N links.
	TargetAlias string
}

ImportNodesOptions configures Keg.ImportNodes.

type ImportedNode added in v0.23.0

type ImportedNode struct {
	SourceID   string
	SourceHash string
	ID         NodeId
}

ImportedNode maps an archive source id to the node id it landed on.

type IndexBuilder

type IndexBuilder interface {
	// Name returns the bare index filename used with repo.WriteIndex
	// (for example "tags" or "concepts.md"). The "dex/" directory prefix
	// is implicit and applied by the repository at write time.
	Name() string

	// Add incorporates information from a node into the index's in-memory state.
	Add(ctx context.Context, node *NodeData) error

	// Remove deletes node-related state from the index.
	Remove(ctx context.Context, node NodeId) error

	// Clear resets the index to an empty state.
	Clear(ctx context.Context) error

	// Data returns the serialized index bytes to be written to storage.
	Data(ctx context.Context) ([]byte, error)
}

IndexBuilder is an interface for constructing a single index artifact (for example: nodes.tsv, tags, links, backlinks). Implementations maintain in-memory state via Add / Remove / Clear and produce the serialized bytes to write via Data.

type IndexEntry

type IndexEntry struct {
	File    string `yaml:"file" json:"file"`
	Summary string `yaml:"summary" json:"summary"`
	Query   string `yaml:"query,omitempty" json:"query,omitempty"` // boolean query expression; omit for core/unfiltered indexes
	Sort    string `yaml:"sort,omitempty" json:"sort,omitempty"`   // sort order for query-filtered indexes: "updated" (default), "id", "created", "accessed"
}

IndexEntry represents an entry in the indexes list in the KEG settings.

File is the bare filename of the generated index artifact, e.g. "backlinks" or "concepts.md". The on-disk path is always under the keg's dex/ directory (the prefix is implicit and applied at write time).

The Query field holds a boolean query expression used to filter index contents (tag names, key=value attribute predicates, boolean operators).

func SystemIndexEntries added in v0.23.0

func SystemIndexEntries() []IndexEntry

SystemIndexEntries returns the required indexes that every keg has at runtime. Callers receive a fresh slice so entries can be appended safely.

type IndexOptions

type IndexOptions struct {
	NoUpdate bool
}

type InvalidSettingsError added in v0.39.0

type InvalidSettingsError struct {
	Msg string
}

InvalidSettingsError represents a validation or parse failure for keg settings.

func (*InvalidSettingsError) Error added in v0.39.0

func (e *InvalidSettingsError) Error() string

func (*InvalidSettingsError) Unwrap added in v0.39.0

func (e *InvalidSettingsError) Unwrap() error

type Keg

type Keg interface {
	// ListAttachments returns a scoped page for 1–100 node ids.
	ListAttachments(context.Context, AttachmentListRequest) (*AttachmentPage, error)
	// ReadAttachment reads original bytes from a kind-qualified filename.
	ReadAttachment(context.Context, NodeId, AttachmentKind, string) ([]byte, error)
	// WriteAttachment stores original bytes without altering the filename.
	WriteAttachment(context.Context, NodeId, AttachmentKind, string, []byte) error
	// DeleteAttachment removes only the kind-qualified filename.
	DeleteAttachment(context.Context, NodeId, AttachmentKind, string) error

	// MoveBatch atomically relocates 1–100 guarded nodes, without swaps.
	MoveBatch(context.Context, []MoveItem) ([]MutationResult, error)
	// RemoveBatch atomically removes 1–100 guarded nodes.
	RemoveBatch(context.Context, []RemoveItem) ([]MutationResult, error)
	// RestoreBatch atomically restores 1–100 guarded live nodes from snapshots.
	RestoreBatch(context.Context, []RestoreItem) ([]MutationResult, error)
	// RenewLock extends a live advisory lease using its existing token.
	RenewLock(context.Context, NodeId, LockToken) (LockInfo, error)

	// Target returns the keg's resolved location, or nil when no target was set.
	Target() *Target

	// Init bootstraps an empty keg: settings file plus zero node. Remote kegs
	// are created through the hub's keg-creation endpoint instead and return
	// ErrNotSupported.
	Init(ctx context.Context) error

	// Settings returns the keg-level configuration (the `keg` file).
	Settings(ctx context.Context) (*Settings, error)

	// SetSettings replaces the keg settings with the supplied raw YAML.
	// Raw bytes preserve user formatting for round-trip editing.
	SetSettings(ctx context.Context, data []byte, opts SettingsWriteOptions) error

	// Info returns the keg settings and summary from one coherent
	// keg-wide read snapshot.
	Info(ctx context.Context) (*KegInfo, error)

	// ListSchemas returns the defined schema type names in lexicographic order.
	ListSchemas(ctx context.Context) ([]string, error)

	// ReadSchema returns the raw YAML definition for typeName.
	ReadSchema(ctx context.Context, typeName string) ([]byte, error)

	// WriteSchema validates and updates the existing YAML definition for
	// typeName. It returns ErrNotExist when the schema does not exist; use
	// CreateSchema for creation.
	WriteSchema(ctx context.Context, typeName string, data []byte, opts SchemaWriteOptions) error

	// CreateSchema validates and stores the YAML definition for typeName only
	// when it does not exist. Concurrent creators are serialized so exactly one
	// succeeds and the others return ErrExist.
	CreateSchema(ctx context.Context, typeName string, data []byte) error

	// DeleteSchema removes the definition for typeName.
	DeleteSchema(ctx context.Context, typeName string, opts SchemaWriteOptions) error

	// ValidateNode validates the stored content and metadata for id against its
	// declared schema without changing the node.
	ValidateNode(ctx context.Context, id NodeId) (*SchemaValidationResult, error)

	// ValidateNodePayload validates a proposed content/meta overlay for an
	// existing node without writing it. Fields not marked present are read from
	// the stored node.
	ValidateNodePayload(ctx context.Context, payload NodeValidationPayload) (*SchemaValidationResult, error)

	// Create allocates a node id and writes initial content, meta, and stats.
	Create(ctx context.Context, opts *CreateOptions) (CreateResult, error)

	// CreateNodes atomically creates 1-100 nodes in caller order. Keys must be
	// unique and may be referenced from bodies as {{node:key}}.
	CreateNodes(ctx context.Context, nodes []NodeCreate) ([]CreateNodeResult, error)

	// ListNodes returns all node ids present in the keg.
	ListNodes(ctx context.Context) ([]NodeId, error)

	// NodeExists reports whether id is a fully written node (content
	// present), as opposed to a bare reservation directory.
	NodeExists(ctx context.Context, id NodeId) (bool, error)

	// Move relocates src to dst and rewrites inbound links. It returns the
	// ids of nodes whose content was rewritten to follow the move.
	Move(ctx context.Context, opts NodeMoveOptions) ([]NodeId, error)

	// Remove deletes a node and rewrites or drops inbound links. It returns
	// the ids of nodes whose content was rewritten.
	Remove(ctx context.Context, opts NodeRemoveOptions) ([]NodeId, error)

	// Commit promotes a temporary, code-backed node to a permanent numeric id.
	// It is a no-op for an already-permanent node.
	Commit(ctx context.Context, id NodeId) error

	// ReadNode returns the node's full state in one operation: content, raw
	// meta, stats, and asset name lists from one coherent read snapshot.
	ReadNode(ctx context.Context, id NodeId) (*NodeView, error)

	// OpenNode validates any held advisory lock against opts.LockToken,
	// optionally records an access touch, and returns one coherent node view.
	// If the operation fails after touching, the touch is rolled back.
	OpenNode(ctx context.Context, opts NodeOpenOptions) (*NodeView, error)

	// ReadNodes reads either opts.NodeIDs in caller order or the nodes selected
	// by opts.Query in dex order; the selectors are mutually exclusive. All
	// views come from one coherent keg snapshot. When Touch is set, either every
	// selected node is touched or all touch side effects are rolled back.
	ReadNodes(ctx context.Context, opts ReadNodesOptions) ([]NodeView, error)

	// UpdateNode validates advisory-lock ownership and an optional expected
	// content hash, then commits content, optional metadata, derived stats, and
	// dex state as one node update. It returns the resulting validation and hash.
	UpdateNode(ctx context.Context, opts NodeUpdateOptions) (*NodeUpdateResult, error)

	// UpdateNodes atomically applies 1-100 content and/or metadata replacements
	// after preflighting every lock, hash, payload, and schema result.
	UpdateNodes(ctx context.Context, updates []NodeUpdateOptions) ([]NodeUpdateResult, error)

	// GetContent returns the node's primary content (README.md).
	GetContent(ctx context.Context, id NodeId) ([]byte, error)

	// SetContent replaces the node's primary content and refreshes derived
	// state (stats, dex) as the implementation requires.
	SetContent(ctx context.Context, id NodeId, data []byte) error

	// GetMeta returns the node's parsed metadata.
	GetMeta(ctx context.Context, id NodeId) (*NodeMeta, error)

	// GetMetaRaw returns the node's metadata bytes exactly as stored,
	// preserving formatting for round-trip editing.
	GetMetaRaw(ctx context.Context, id NodeId) ([]byte, error)

	// SetMeta replaces the node's metadata.
	SetMeta(ctx context.Context, id NodeId, meta *NodeMeta) error

	// GetStats returns the node's programmatic stats.
	GetStats(ctx context.Context, id NodeId) (*NodeStats, error)

	// Touch marks the node accessed, updating access stats.
	Touch(ctx context.Context, id NodeId) error

	// Dex returns the keg's current index aggregate. The returned dex
	// reflects committed state at call time (always-fresh semantics).
	Dex(ctx context.Context) (*Dex, error)

	// DexArtifacts returns every raw dex artifact from one coherent generation,
	// materializing snapshot-derived indexes first when necessary.
	DexArtifacts(ctx context.Context) (*DexArtifacts, error)

	// ListEntries returns dex entries, lexicographically sorted tags, and index
	// and repository counts from one coherent snapshot. A non-empty query filters
	// entries in dex order without changing the aggregate counts.
	ListEntries(ctx context.Context, opts ListEntriesOptions) (*ListEntriesResult, error)

	// ListView returns one fully resolved listing page: filtered by the query,
	// ordered, paged, and projected onto the requested field selectors. The
	// server owns the whole projection so a caller displaying metadata does not
	// read each node individually. Field resolution is best-effort: a node whose
	// metadata or stats cannot be read yields empty values rather than failing
	// the listing, because listings render from an index that may be stale.
	ListView(ctx context.Context, opts ListViewOptions) (*ListViewResult, error)

	// RelatedNodes returns the deduplicated union of links or backlinks for the
	// supplied nodes, ordered by node id. It fails if no ids are supplied, an id
	// is missing, or the direction is invalid.
	RelatedNodes(ctx context.Context, opts RelatedNodesOptions) ([]NodeIndexEntry, error)

	// Doctor inspects configuration, content, links, metadata, stats, and schema
	// validation and returns deterministic diagnostic issues without mutating the
	// keg.
	Doctor(ctx context.Context) ([]DoctorIssue, error)

	// RemoveNodes removes the deduplicated union of explicit ids and query
	// matches in ascending node-id order. It stops at the first failure and
	// returns the successful prefix plus a Failure; completed removals and their
	// inbound-link rewrites are not rolled back.
	RemoveNodes(ctx context.Context, opts RemoveNodesOptions) (RemoveNodesResult, error)

	// ValidateNodes validates opts.NodeIDs in caller order, or every node in
	// repository order when none are supplied. It stops at the first operational
	// error and returns results only after every selected node is validated.
	ValidateNodes(ctx context.Context, opts ValidateNodesOptions) ([]SchemaValidationResult, error)

	// Query evaluates a boolean query expression (tags, key=value attribute
	// predicates, .field stats predicates) and returns matching index
	// entries in dex order.
	Query(ctx context.Context, opts QueryOptions) ([]NodeIndexEntry, error)

	// Grep scans node content for a regular expression and returns per-node
	// line matches in dex order.
	Grep(ctx context.Context, opts GrepOptions) ([]GrepMatch, error)

	// Index rebuilds all dex indexes from node state.
	Index(ctx context.Context, opts IndexOptions) error

	// ListIndexes returns available index artifact names.
	ListIndexes(ctx context.Context) ([]string, error)

	// ReadIndex returns a raw index artifact by name (e.g. "nodes.tsv").
	ReadIndex(ctx context.Context, name string) ([]byte, error)

	// Summary returns keg-level diagnostics: node count and asset totals.
	Summary(ctx context.Context) (*KegSummary, error)

	// ListFiles returns file attachment names in lexicographic order.
	ListFiles(ctx context.Context, id NodeId) ([]string, error)

	// ReadFile returns the named file attachment bytes.
	ReadFile(ctx context.Context, id NodeId, name string) ([]byte, error)

	// WriteFile stores or replaces a named file attachment.
	WriteFile(ctx context.Context, id NodeId, name string, data []byte) error

	// DeleteFile removes a named file attachment.
	DeleteFile(ctx context.Context, id NodeId, name string) error

	// ListImages returns image attachment names in lexicographic order.
	ListImages(ctx context.Context, id NodeId) ([]string, error)

	// ReadImage returns the named image attachment bytes.
	ReadImage(ctx context.Context, id NodeId, name string) ([]byte, error)

	// WriteImage validates and stores or replaces a named image attachment.
	WriteImage(ctx context.Context, id NodeId, name string, data []byte) error

	// DeleteImage removes a named image attachment.
	DeleteImage(ctx context.Context, id NodeId, name string) error

	// AppendSnapshot records the node's current state as a new revision.
	AppendSnapshot(ctx context.Context, id NodeId, msg string) (Snapshot, error)

	// AppendSnapshots atomically records 1-100 independent save points.
	AppendSnapshots(ctx context.Context, nodes []NodeSnapshotRequest) ([]Snapshot, error)

	// ListSnapshots returns the node's revisions in order.
	ListSnapshots(ctx context.Context, id NodeId) ([]Snapshot, error)

	// GetSnapshot returns revision metadata and, per opts, resolved payloads.
	GetSnapshot(ctx context.Context, id NodeId, rev RevisionID, opts SnapshotReadOptions) (Snapshot, []byte, []byte, *NodeStats, error)

	// ReadContentAt reconstructs node content at a revision.
	ReadContentAt(ctx context.Context, id NodeId, rev RevisionID) ([]byte, error)

	// RestoreSnapshot restores live node state to a revision.
	RestoreSnapshot(ctx context.Context, id NodeId, rev RevisionID) error

	// ExportNodes returns a reader for a gzip-tar keg archive. Query matches are
	// unioned with explicit ids, and selected nodes are written in node-id order;
	// an empty selection exports every node. The archive reflects one coherent
	// read snapshot, and the caller must Close the returned reader.
	ExportNodes(ctx context.Context, opts ExportNodesOptions) (io.ReadCloser, error)

	// ImportNodes loads a keg-archive stream into the keg, replacing
	// existing nodes with matching ids, and rebuilds derived state.
	ImportNodes(ctx context.Context, r io.Reader, opts ImportNodesOptions) ([]ImportedNode, error)

	// Lock acquires a cross-process advisory lock on a node.
	Lock(ctx context.Context, id NodeId) (LockInfo, error)

	// Unlock releases a lock acquired by Lock; the token must match.
	Unlock(ctx context.Context, id NodeId, token LockToken) error

	// LockStatus reports the node's current lock state; a zero LockInfo
	// means unheld.
	LockStatus(ctx context.Context, id NodeId) (LockInfo, error)

	// ForceUnlock removes a lock regardless of token ownership.
	ForceUnlock(ctx context.Context, id NodeId) error

	// Watch streams node change events until ctx is canceled. With no ids,
	// all nodes are watched.
	Watch(ctx context.Context, ids ...NodeId) (<-chan NodeEvent, error)
}

Keg is the single-keg business API. It is the abstraction boundary between callers (the Tap layer, the hub's HTTP handlers) and keg storage: every method is one logical operation, and implementations own their orchestration internally (locking discipline, dex/index maintenance, stats touching).

Two implementations exist:

  • LocalKeg orchestrates a Repository (MemoryRepository or the hub's PgRepo) and maintains derived state itself.
  • RemoteKeg speaks the tapper-hub operation API; each method is a single HTTP round trip and all orchestration happens server-side.

Capability errors: methods backed by optional storage features (schemas, files, images, snapshots, locks, and events) return ErrNotSupported when the backend lacks the capability.

func NewKegFromTarget

func NewKegFromTarget(ctx context.Context, target Target, rt *toolkit.Runtime, opts ...KegOption) (Keg, error)

NewKegFromTarget constructs a Keg implementation from a Target. It automatically selects the appropriate remote implementation based on the target's scheme:

  • http:// and https:// targets use a RemoteKeg speaking the hub's operation API
  • hub targets use a RemoteKeg resolved from repo/user/keg fields

Returns an error if the target scheme is not supported.

type KegInfo added in v0.33.0

type KegInfo struct {
	Settings *Settings   `json:"settings"`
	Summary  *KegSummary `json:"summary"`
}

type KegOption added in v0.20.0

type KegOption func(*kegOptions)

KegOption customises NewKegFromTarget without breaking existing callers. Variadic options keep the common case (no resolver, no extras) a zero-cost invocation.

func WithTokenResolver added in v0.20.0

func WithTokenResolver(r TokenResolver) KegOption

WithTokenResolver installs a TokenResolver consulted as the third and final fallback when a remote target has neither a TokenEnv-sourced value nor an inline Token. Pass a nil resolver to explicitly opt out of fallback; the option itself is a no-op in that case.

type KegSummary added in v0.23.0

type KegSummary struct {
	NodeCount int          `json:"node_count" yaml:"node_count"`
	Files     AssetSummary `json:"assets" yaml:"assets"`
	Images    AssetSummary `json:"images" yaml:"images"`
}

KegSummary is keg-level diagnostic data returned by Keg.Summary.

type LinkEntry

type LinkEntry struct {
	Alias string `yaml:"alias" json:"alias"` // Alias for the link
	URL   string `yaml:"url" json:"url"`     // URL of the link
}

LinkEntry represents a named link in the KEG settings.

type LinkIndex

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

LinkIndex maps a source node path to the list of destination nodes that the source links to. It is used to construct the "links" index artifact.

The underlying map keys are node.Path() values (string). The index is expected to be small enough to be kept in memory for index-building tooling.

The type has unexported fields and is safe for in-memory, single-process use. Concurrency control is the caller's responsibility.

func ParseLinkIndex

func ParseLinkIndex(ctx context.Context, data []byte) (LinkIndex, error)

ParseLinkIndex parses the raw bytes of a links index into a LinkIndex. The expected on-disk format is one line per source:

"<src>\t<dst1> <dst2> ...\n"

Behavior:

  • Empty or nil input yields an empty LinkIndex with no error.
  • Lines are split on tab to separate source from space-separated destinations.
  • Duplicate destinations for a source are tolerated and may be deduped by callers of Data.

This function does not modify any external state.

func (*LinkIndex) Add

func (idx *LinkIndex) Add(ctx context.Context, data *NodeData) error

Add incorporates link information from the provided NodeData into the index. The NodeData.ID value is used as the source key (via NodeId.Path semantics) and NodeData.Links is treated as the list of destination nodes.

Behavior expectations (not enforced here but callers may rely on them):

  • If idx is nil the call is a no-op and returns nil.
  • If idx.data is nil it will be initialized.
  • The method should avoid introducing duplicate destination entries for a given source when possible.

This method only mutates in-memory state and does not perform I/O.

func (*LinkIndex) Data

func (idx *LinkIndex) Data(ctx context.Context) ([]byte, error)

Data serializes the index into the canonical on-disk format.

Serialization rules:

  • Each non-empty source produces a line: "<src>\t<dst1> <dst2> ...\n"
  • Destination lists are deduplicated and sorted in a deterministic order.
  • Source keys are emitted in a deterministic, parse-aware order (numeric node ids sorted numerically when possible, otherwise lexicographic).
  • An empty index returns an empty byte slice.

The returned bytes are owned by the caller and may be written atomically by the repository layer.

func (*LinkIndex) Rm

func (idx *LinkIndex) Rm(ctx context.Context, node NodeId) error

Rm removes any references introduced by the given node as a source and removes the node from any destination lists where it appears.

Behavior expectations:

  • If idx is nil the call is a no-op and returns nil.
  • If idx.data is nil it will be initialized to an empty map.
  • After removal, entries with no destinations may either remain as empty slices or be deleted; callers should tolerate either representation.

This method only mutates in-memory state and does not perform I/O.

type ListEntriesOptions added in v0.33.0

type ListEntriesOptions struct {
	Query string `json:"query,omitempty"`
}

ListEntriesOptions configures the server-owned listing projection.

type ListEntriesResult added in v0.33.0

type ListEntriesResult struct {
	Query        string           `json:"query,omitempty"`
	Entries      []NodeIndexEntry `json:"entries"`
	Tags         []string         `json:"tags"`
	IndexedCount int              `json:"indexed_count"`
	NodeCount    int              `json:"node_count"`
}

ListEntriesResult contains every value needed by list and tags without requiring callers to assemble multiple primitive reads.

type ListViewOptions added in v0.36.0

type ListViewOptions struct {
	// Query is an optional boolean query expression filtering the nodes.
	Query string `json:"query,omitempty"`

	// TitleContains further narrows the result to titles containing this
	// text, case-insensitively. It is a plain substring rather than an
	// expression, for the common "I half-remember the name" search, and it
	// costs nothing because titles are already carried by the index. Applying
	// it here rather than in the caller keeps paging and TotalMatches correct.
	TitleContains string `json:"title_contains,omitempty"`

	// Fields are field selectors to resolve per row, in the vocabulary of
	// ParseFieldSelector ("type", ".omega", "tags"). Intrinsics and index
	// timestamps cost nothing; other selectors are read per returned row.
	Fields []string `json:"fields,omitempty"`

	// Sort is the field selector to order by. Empty orders by node id.
	Sort string `json:"sort,omitempty"`

	// Desc reverses the sort order.
	Desc bool `json:"desc,omitempty"`

	// Limit caps the returned rows. 0 means no limit.
	Limit int `json:"limit,omitempty"`

	// Offset skips the first N matching rows before applying Limit.
	Offset int `json:"offset,omitempty"`
}

ListViewOptions configures a fully server-resolved listing page.

Filtering, ordering, and paging all happen before field projection, so a listing that displays metadata reads only the rows it returns rather than every node in the keg.

type ListViewResult added in v0.36.0

type ListViewResult struct {
	Query        string        `json:"query,omitempty"`
	Rows         []ListViewRow `json:"rows"`
	Tags         []string      `json:"tags"`
	TotalMatches int           `json:"total_matches"`
	IndexedCount int           `json:"indexed_count"`
	NodeCount    int           `json:"node_count"`
}

ListViewResult is a resolved listing page. TotalMatches counts the rows the query selected before Limit and Offset were applied, so callers can page without re-running the query.

type ListViewRow added in v0.36.0

type ListViewRow struct {
	Entry  NodeIndexEntry    `json:"entry"`
	Fields map[string]string `json:"fields,omitempty"`
}

ListViewRow is one resolved listing row: its index entry plus the values of the requested field selectors, keyed by selector text.

type LocalKeg added in v0.23.0

type LocalKeg struct {

	// Repo is the storage backend implementation
	Repo Repository
	// Runtime provides clock/hash/fs helpers used by high-level keg operations.
	Runtime *toolkit.Runtime
	// contains filtered or unexported fields
}

LocalKeg is the concrete high-level service providing KEG node operations backed by a Repository. It abstracts storage implementation details, allowing operations over nodes to work uniformly across repository backends. LocalKeg delegates low-level storage operations to its underlying repository and maintains an in-memory dex for indexing.

func NewLocalKeg added in v0.23.0

func NewLocalKeg(repo Repository, rt *toolkit.Runtime, opts ...Option) *LocalKeg

NewLocalKeg returns a LocalKeg service backed by the provided repository. Functional options can be provided to customize LocalKeg behavior.

func (*LocalKeg) AppendSnapshot added in v0.23.0

func (k *LocalKeg) AppendSnapshot(ctx context.Context, id NodeId, msg string) (Snapshot, error)

func (*LocalKeg) AppendSnapshots added in v0.38.0

func (k *LocalKeg) AppendSnapshots(ctx context.Context, nodes []NodeSnapshotRequest) ([]Snapshot, error)

func (*LocalKeg) Commit added in v0.23.0

func (k *LocalKeg) Commit(ctx context.Context, id NodeId) error

Commit finalizes a temporary node by allocating a permanent ID and moving it from its temporary location (with Code suffix) to the canonical numeric ID. For nodes without a Code (already permanent), Commit is a no-op.

func (*LocalKeg) Create added in v0.23.0

func (k *LocalKeg) Create(ctx context.Context, opts *CreateOptions) (CreateResult, error)

Create creates a new node: allocates an ID, parses content, generates metadata, and indexes the node in the dex. The node is immediately persisted to the repository. If Body is empty, a placeholder heading is generated from the allocated node id.

func (*LocalKeg) CreateNodes added in v0.38.0

func (k *LocalKeg) CreateNodes(ctx context.Context, nodes []NodeCreate) ([]CreateNodeResult, error)

func (*LocalKeg) CreateSchema added in v0.33.0

func (k *LocalKeg) CreateSchema(ctx context.Context, typeName string, data []byte) error

func (*LocalKeg) DeleteAttachment added in v0.41.0

func (k *LocalKeg) DeleteAttachment(ctx context.Context, id NodeId, kind AttachmentKind, name string) error

DeleteAttachment removes only the requested kind and filename.

func (*LocalKeg) DeleteFile added in v0.23.0

func (k *LocalKeg) DeleteFile(ctx context.Context, id NodeId, name string) error

DeleteFile removes a file attachment from a node.

func (*LocalKeg) DeleteImage added in v0.23.0

func (k *LocalKeg) DeleteImage(ctx context.Context, id NodeId, name string) error

DeleteImage removes an image from a node.

func (*LocalKeg) DeleteSchema added in v0.25.0

func (k *LocalKeg) DeleteSchema(ctx context.Context, typeName string, opts SchemaWriteOptions) error

func (*LocalKeg) Dex added in v0.23.0

func (k *LocalKeg) Dex(ctx context.Context) (*Dex, error)

Dex returns the keg's current index. Repository-backed indexes are reloaded for every aggregate read so a long-lived hub process does not serve a stale view after another process or replica updates the repository.

func (*LocalKeg) DexArtifacts added in v0.33.0

func (k *LocalKeg) DexArtifacts(ctx context.Context) (*DexArtifacts, error)

func (*LocalKeg) Doctor added in v0.33.0

func (k *LocalKeg) Doctor(ctx context.Context) ([]DoctorIssue, error)

func (*LocalKeg) ExportNodes added in v0.23.0

func (k *LocalKeg) ExportNodes(ctx context.Context, opts ExportNodesOptions) (io.ReadCloser, error)

ExportNodes returns a reader for a keg-archive (gzip tar) captured from one coherent read snapshot. LocalKeg materializes the archive before returning, so the operation boundary is released while the caller reads the artifact and a slow download does not block same-keg writers.

func (*LocalKeg) ForceUnlock added in v0.23.0

func (k *LocalKeg) ForceUnlock(ctx context.Context, id NodeId) error

ForceUnlock unconditionally removes a cross-process lock regardless of token ownership. Escape hatch for stuck or stale locks.

func (*LocalKeg) GetContent added in v0.23.0

func (k *LocalKeg) GetContent(ctx context.Context, id NodeId) ([]byte, error)

GetContent retrieves the raw markdown content for a node.

func (*LocalKeg) GetMeta added in v0.23.0

func (k *LocalKeg) GetMeta(ctx context.Context, id NodeId) (*NodeMeta, error)

GetMeta retrieves the parsed metadata for a node.

func (*LocalKeg) GetMetaRaw added in v0.23.0

func (k *LocalKeg) GetMetaRaw(ctx context.Context, id NodeId) ([]byte, error)

GetMetaRaw returns the node's metadata bytes exactly as stored, preserving formatting for round-trip editing. A missing meta file returns ErrNotExist.

func (*LocalKeg) GetSnapshot added in v0.23.0

func (k *LocalKeg) GetSnapshot(ctx context.Context, id NodeId, rev RevisionID, opts SnapshotReadOptions) (Snapshot, []byte, []byte, *NodeStats, error)

GetSnapshot returns revision metadata and, per opts, resolved content, meta, and stats payloads.

func (*LocalKeg) GetStats added in v0.23.0

func (k *LocalKeg) GetStats(ctx context.Context, id NodeId) (*NodeStats, error)

GetStats retrieves programmatic node stats for a node.

func (*LocalKeg) Grep added in v0.23.0

func (k *LocalKeg) Grep(ctx context.Context, opts GrepOptions) ([]GrepMatch, error)

Grep scans node content for a regular expression and returns per-node line matches in dex order. Nodes without matches are omitted.

func (*LocalKeg) ImportNodes added in v0.23.0

func (k *LocalKeg) ImportNodes(ctx context.Context, r io.Reader, opts ImportNodesOptions) ([]ImportedNode, error)

ImportNodes loads a keg-archive stream into the keg. Nodes land on their archive ids, replacing existing nodes (whose assets are preserved unless the archive carries its own). Derived state (dex, settings updated stamp) is rebuilt once after all nodes import.

func (*LocalKeg) Index added in v0.23.0

func (k *LocalKeg) Index(ctx context.Context, opts IndexOptions) error

Index rebuilds all keg indices from scratch. Every node is scanned, metadata and stats are refreshed (unless NoUpdate is set), and the full dex is regenerated.

func (*LocalKeg) IndexNode added in v0.23.0

func (k *LocalKeg) IndexNode(ctx context.Context, id NodeId) error

IndexNode updates a node's metadata by re-parsing its content and extracting properties like title, lead, and content hash. The dex is also updated to reflect any changes. If content hasn't changed, this is a no-op.

func (*LocalKeg) Info added in v0.33.0

func (k *LocalKeg) Info(ctx context.Context) (*KegInfo, error)

func (*LocalKeg) Init added in v0.23.0

func (k *LocalKeg) Init(ctx context.Context) error

Init initializes a new keg by creating the settings file, zero node with default content, and updating the dex. It returns an error if the keg already exists. Init is idempotent in the sense that it checks for existing kegs first.

func (*LocalKeg) InvalidateDex added in v0.23.0

func (k *LocalKeg) InvalidateDex()

InvalidateDex clears the cached dex so the next Dex() call reloads from the repository. This is useful when external processes may have modified the index files.

func (*LocalKeg) ListAttachments added in v0.41.0

func (k *LocalKeg) ListAttachments(ctx context.Context, in AttachmentListRequest) (*AttachmentPage, error)

ListAttachments lists originals with kind and byte length in a scoped page.

func (*LocalKeg) ListEntries added in v0.33.0

func (k *LocalKeg) ListEntries(ctx context.Context, opts ListEntriesOptions) (*ListEntriesResult, error)

func (*LocalKeg) ListFiles added in v0.23.0

func (k *LocalKeg) ListFiles(ctx context.Context, id NodeId) ([]string, error)

ListFiles lists file attachment names for a node. Returns ErrNotSupported when the backend lacks file storage.

func (*LocalKeg) ListImages added in v0.23.0

func (k *LocalKeg) ListImages(ctx context.Context, id NodeId) ([]string, error)

ListImages lists image names for a node. Returns ErrNotSupported when the backend lacks image storage.

func (*LocalKeg) ListIndexes added in v0.23.0

func (k *LocalKeg) ListIndexes(ctx context.Context) ([]string, error)

ListIndexes returns available index artifact names.

func (*LocalKeg) ListNodes added in v0.23.0

func (k *LocalKeg) ListNodes(ctx context.Context) ([]NodeId, error)

ListNodes returns all node ids present in the keg.

func (*LocalKeg) ListSchemas added in v0.25.0

func (k *LocalKeg) ListSchemas(ctx context.Context) ([]string, error)

func (*LocalKeg) ListSnapshots added in v0.23.0

func (k *LocalKeg) ListSnapshots(ctx context.Context, id NodeId) ([]Snapshot, error)

func (*LocalKeg) ListView added in v0.36.0

func (k *LocalKeg) ListView(ctx context.Context, opts ListViewOptions) (*ListViewResult, error)

func (*LocalKeg) Lock added in v0.23.0

func (k *LocalKeg) Lock(ctx context.Context, id NodeId) (LockInfo, error)

Lock acquires a cross-process advisory lock on a node. Returns ErrNotSupported when the backend lacks cross-process locking.

func (*LocalKeg) LockStatus added in v0.23.0

func (k *LocalKeg) LockStatus(ctx context.Context, id NodeId) (LockInfo, error)

LockStatus reports the node's current cross-process lock state. A zero LockInfo means no live lock is held.

func (*LocalKeg) Move added in v0.23.0

func (k *LocalKeg) Move(ctx context.Context, opts NodeMoveOptions) ([]NodeId, error)

Move renames a node from src to dst and rewrites in-content links that target src (../N) across the keg. It returns the ids of nodes whose content was rewritten to follow the move.

func (*LocalKeg) MoveBatch added in v0.41.0

func (k *LocalKeg) MoveBatch(ctx context.Context, items []MoveItem) ([]MutationResult, error)

MoveBatch applies the complete relocation batch in one atomic operation.

func (*LocalKeg) Node added in v0.23.0

func (k *LocalKeg) Node(id NodeId) *Node

Node returns a Node handle bound to this keg's repository and runtime for the given id. It performs no I/O; content, metadata, and stats are loaded lazily by the Node's own methods.

func (*LocalKeg) NodeExists added in v0.23.0

func (k *LocalKeg) NodeExists(ctx context.Context, id NodeId) (bool, error)

NodeExists reports whether id is a fully written node (content present), as opposed to a bare reservation directory left behind by MemoryRepository.Next() or MemoryRepository.WithNodeLock(). It holds no node lock; mutating operations re-check under lock.

func (*LocalKeg) OpenNode added in v0.33.0

func (k *LocalKeg) OpenNode(ctx context.Context, opts NodeOpenOptions) (*NodeView, error)

func (*LocalKeg) Query added in v0.23.0

func (k *LocalKeg) Query(ctx context.Context, opts QueryOptions) ([]NodeIndexEntry, error)

Query evaluates a boolean query expression against the keg's index entries and returns the matching entries in dex order. The expression grammar supports plain tag names, key=value attribute predicates, attribute comparisons (omega>=0.5), and .field stats predicates (.updated>2026-01-01); see ParseQueryExpression.

func (*LocalKeg) ReadAttachment added in v0.41.0

func (k *LocalKeg) ReadAttachment(ctx context.Context, id NodeId, kind AttachmentKind, name string) ([]byte, error)

ReadAttachment returns original bytes from the selected kind's namespace.

func (*LocalKeg) ReadContentAt added in v0.23.0

func (k *LocalKeg) ReadContentAt(ctx context.Context, id NodeId, rev RevisionID) ([]byte, error)

func (*LocalKeg) ReadFile added in v0.23.0

func (k *LocalKeg) ReadFile(ctx context.Context, id NodeId, name string) ([]byte, error)

ReadFile reads a file attachment for a node.

func (*LocalKeg) ReadImage added in v0.23.0

func (k *LocalKeg) ReadImage(ctx context.Context, id NodeId, name string) ([]byte, error)

ReadImage reads an image payload for a node.

func (*LocalKeg) ReadIndex added in v0.23.0

func (k *LocalKeg) ReadIndex(ctx context.Context, name string) ([]byte, error)

ReadIndex returns a raw index artifact by name (e.g. "nodes.tsv").

func (*LocalKeg) ReadNode added in v0.23.0

func (k *LocalKeg) ReadNode(ctx context.Context, id NodeId) (*NodeView, error)

ReadNode assembles the node's full state: content (required), raw meta and stats (optional, zero-valued when absent), and asset name lists (nil when the backend lacks the capability).

func (*LocalKeg) ReadNodes added in v0.33.0

func (k *LocalKeg) ReadNodes(ctx context.Context, opts ReadNodesOptions) ([]NodeView, error)

func (*LocalKeg) ReadSchema added in v0.25.0

func (k *LocalKeg) ReadSchema(ctx context.Context, typeName string) ([]byte, error)

func (*LocalKeg) RelatedNodes added in v0.33.0

func (k *LocalKeg) RelatedNodes(ctx context.Context, opts RelatedNodesOptions) ([]NodeIndexEntry, error)

func (*LocalKeg) Remove added in v0.23.0

func (k *LocalKeg) Remove(ctx context.Context, opts NodeRemoveOptions) ([]NodeId, error)

Remove deletes a node from the repository and updates dex/settings artifacts. It returns the ids of nodes whose content was rewritten to drop links to the removed node.

func (*LocalKeg) RemoveBatch added in v0.41.0

func (k *LocalKeg) RemoveBatch(ctx context.Context, items []RemoveItem) ([]MutationResult, error)

RemoveBatch removes every requested node or rolls back the whole operation.

func (*LocalKeg) RemoveNodes added in v0.33.0

func (k *LocalKeg) RemoveNodes(ctx context.Context, opts RemoveNodesOptions) (RemoveNodesResult, error)

func (*LocalKeg) RenewLock added in v0.41.0

func (k *LocalKeg) RenewLock(ctx context.Context, id NodeId, token LockToken) (LockInfo, error)

RenewLock extends an existing lease using its current token.

func (*LocalKeg) RestoreBatch added in v0.41.0

func (k *LocalKeg) RestoreBatch(ctx context.Context, items []RestoreItem) ([]MutationResult, error)

RestoreBatch restores guarded snapshots atomically.

func (*LocalKeg) RestoreSnapshot added in v0.23.0

func (k *LocalKeg) RestoreSnapshot(ctx context.Context, id NodeId, rev RevisionID) error

func (*LocalKeg) RunSnapshotPolicy added in v0.28.0

func (k *LocalKeg) RunSnapshotPolicy(ctx context.Context) (SnapshotPolicyResult, error)

RunSnapshotPolicy scans all nodes in deterministic order and appends automatic snapshots for nodes whose live content has drifted from the latest snapshot after the configured idle window.

func (*LocalKeg) SetContent added in v0.23.0

func (k *LocalKeg) SetContent(ctx context.Context, id NodeId, data []byte) error

SetContent writes content for a node and updates its metadata by re-indexing. This ensures the node's title, lead, and other metadata are kept in sync with content changes.

func (*LocalKeg) SetContentWithOptions added in v0.38.0

func (k *LocalKeg) SetContentWithOptions(ctx context.Context, id NodeId, data []byte, opts NodeWriteOptions) error

SetContentWithOptions replaces content while applying an explicit schema to persisted meta.type before validating the completed node.

func (*LocalKeg) SetMeta added in v0.23.0

func (k *LocalKeg) SetMeta(ctx context.Context, id NodeId, meta *NodeMeta) error

SetMeta writes metadata for a node and updates the dex. If the new meta bytes are identical to the existing on-disk meta, the write and dex/settings update are skipped entirely.

func (*LocalKeg) SetMetaWithOptions added in v0.38.0

func (k *LocalKeg) SetMetaWithOptions(ctx context.Context, id NodeId, meta *NodeMeta, opts NodeWriteOptions) error

SetMetaWithOptions replaces metadata while applying an explicit schema to persisted meta.type before validating the completed node.

func (*LocalKeg) SetSettings added in v0.39.0

func (k *LocalKeg) SetSettings(ctx context.Context, data []byte, opts SettingsWriteOptions) error

SetSettings parses and writes keg settings from raw bytes. Prefer UpdateSettings for most use cases as it handles read-modify-write atomically.

func (*LocalKeg) SetTarget added in v0.23.0

func (k *LocalKeg) SetTarget(target *Target)

SetTarget records the keg's resolved location. Construction paths that cannot pass the target through a literal (e.g. the hub opening a keg by namespace/alias) use this to label the keg after the fact.

func (*LocalKeg) Settings added in v0.39.0

func (k *LocalKeg) Settings(ctx context.Context) (*Settings, error)

Settings returns the keg's configuration.

func (*LocalKeg) Summary added in v0.23.0

func (k *LocalKeg) Summary(ctx context.Context) (*KegSummary, error)

Summary returns keg-level diagnostics: node count plus per-kind asset totals. Asset kinds report Supported=false when the backend lacks the capability.

func (*LocalKeg) Target added in v0.23.0

func (k *LocalKeg) Target() *Target

Target returns the keg's resolved location, or nil when no target was set.

func (*LocalKeg) Touch added in v0.23.0

func (k *LocalKeg) Touch(ctx context.Context, id NodeId) error

Touch updates the access time of a node to the current time.

func (*LocalKeg) Unlock added in v0.23.0

func (k *LocalKeg) Unlock(ctx context.Context, id NodeId, token LockToken) error

Unlock releases a cross-process lock; the token must match the holder's.

func (*LocalKeg) UpdateMeta added in v0.23.0

func (k *LocalKeg) UpdateMeta(ctx context.Context, id NodeId, f func(*NodeMeta)) error

UpdateMeta reads the node's metadata, applies the provided mutation function, and writes the result back to the repository with dex updates.

func (*LocalKeg) UpdateMetaWithOptions added in v0.38.0

func (k *LocalKeg) UpdateMetaWithOptions(ctx context.Context, id NodeId, f func(*NodeMeta), opts NodeWriteOptions) error

UpdateMetaWithOptions atomically reads, mutates, validates, and writes metadata while applying an explicit schema to persisted meta.type.

func (*LocalKeg) UpdateNode added in v0.33.0

func (k *LocalKeg) UpdateNode(ctx context.Context, opts NodeUpdateOptions) (*NodeUpdateResult, error)

func (*LocalKeg) UpdateNodes added in v0.38.0

func (k *LocalKeg) UpdateNodes(ctx context.Context, updates []NodeUpdateOptions) ([]NodeUpdateResult, error)

func (*LocalKeg) UpdateSettings added in v0.39.0

func (k *LocalKeg) UpdateSettings(ctx context.Context, f func(*Settings)) error

UpdateSettings reads the keg settings, applies the provided mutation function, and writes the result back to the repository. This is the preferred way to modify keg settings to ensure updates are atomically persisted.

func (*LocalKeg) ValidateNode added in v0.25.0

func (k *LocalKeg) ValidateNode(ctx context.Context, id NodeId) (*SchemaValidationResult, error)

func (*LocalKeg) ValidateNodePayload added in v0.25.0

func (k *LocalKeg) ValidateNodePayload(ctx context.Context, payload NodeValidationPayload) (*SchemaValidationResult, error)

func (*LocalKeg) ValidateNodes added in v0.33.0

func (k *LocalKeg) ValidateNodes(ctx context.Context, opts ValidateNodesOptions) ([]SchemaValidationResult, error)

func (*LocalKeg) Watch added in v0.23.0

func (k *LocalKeg) Watch(ctx context.Context, ids ...NodeId) (<-chan NodeEvent, error)

Watch streams node change events until ctx is canceled. With no ids, all nodes are watched. Returns ErrNotSupported when the backend cannot emit events.

func (*LocalKeg) WriteAttachment added in v0.41.0

func (k *LocalKeg) WriteAttachment(ctx context.Context, id NodeId, kind AttachmentKind, name string, data []byte) error

WriteAttachment preserves the filename and storage layout of each media kind.

func (*LocalKeg) WriteFile added in v0.23.0

func (k *LocalKeg) WriteFile(ctx context.Context, id NodeId, name string, data []byte) error

WriteFile stores a file attachment for a node.

func (*LocalKeg) WriteImage added in v0.23.0

func (k *LocalKeg) WriteImage(ctx context.Context, id NodeId, name string, data []byte) error

WriteImage stores an image payload for a node.

func (*LocalKeg) WriteSchema added in v0.25.0

func (k *LocalKeg) WriteSchema(ctx context.Context, typeName string, data []byte, opts SchemaWriteOptions) error

type LockInfo added in v0.11.0

type LockInfo struct {
	Token      LockToken `json:"token"`
	AcquiredAt time.Time `json:"acquired_at"`
	TTLSeconds int       `json:"ttl_seconds"`
	Holder     string    `json:"holder"`
}

LockInfo describes the current state of a cross-process node lock.

func (LockInfo) IsStale added in v0.11.0

func (li LockInfo) IsStale(now time.Time) bool

IsStale reports whether the lock has expired based on its TTL.

type LockToken added in v0.11.0

type LockToken string

LockToken is an opaque string identifying lock ownership.

type MarkdownSchema added in v0.25.0

type MarkdownSchema struct {
	RequireTitle bool              `yaml:"requireTitle,omitempty" json:"requireTitle,omitempty"`
	Ordered      bool              `yaml:"ordered,omitempty" json:"ordered,omitempty"`
	Sections     []MarkdownSection `yaml:"sections,omitempty" json:"sections,omitempty"`
}

type MarkdownSection added in v0.25.0

type MarkdownSection struct {
	Heading  string `yaml:"heading" json:"heading"`
	Level    int    `yaml:"level,omitempty" json:"level,omitempty"`
	Required bool   `yaml:"required,omitempty" json:"required,omitempty"`
}

type MaturityWeightSchema added in v0.28.0

type MaturityWeightSchema struct {
	Direction string             `yaml:"direction,omitempty" json:"direction,omitempty"`
	Attribute string             `yaml:"attribute,omitempty" json:"attribute,omitempty"`
	Weight    float64            `yaml:"weight,omitempty" json:"weight,omitempty"`
	Enum      map[string]float64 `yaml:"enum,omitempty" json:"enum,omitempty"`
}

type MetadataMaturitySchema added in v0.28.0

type MetadataMaturitySchema struct {
	Attribute string             `yaml:"attribute,omitempty" json:"attribute,omitempty"`
	Weight    float64            `yaml:"weight,omitempty" json:"weight,omitempty"`
	Enum      map[string]float64 `yaml:"enum,omitempty" json:"enum,omitempty"`
}

type MoveItem added in v0.41.0

type MoveItem struct {
	Source       int    `json:"source"`
	Destination  int    `json:"destination"`
	ExpectedHash string `json:"expected_hash"`
}

MoveItem is one guarded relocation; occupied destinations are never swaps.

type MutationResult added in v0.41.0

type MutationResult struct {
	ID        int      `json:"id"`
	Hash      string   `json:"hash,omitempty"`
	Rewritten []NodeId `json:"rewritten,omitempty"`
}

MutationResult records the final version and affected link rewrites.

type Node

type Node struct {
	ID      NodeId
	Repo    Repository
	Runtime *toolkit.Runtime
	// contains filtered or unexported fields
}

Node provides operations and lifecycle management for a single KEG node. It holds the node identifier, repository reference, and lazily-loaded node data.

func (*Node) Accessed

func (n *Node) Accessed(ctx context.Context) (time.Time, error)

func (*Node) Changed

func (n *Node) Changed(ctx context.Context) (bool, error)

func (*Node) ClearCache

func (n *Node) ClearCache()

func (*Node) Created

func (n *Node) Created(ctx context.Context) (time.Time, error)

func (*Node) Init

func (n *Node) Init(ctx context.Context) error

Init loads and initializes the node data from the repository including content, metadata, items, and images. Returns an error if the repository is not set or if any repository operation fails.

func (*Node) Lead

func (n *Node) Lead(ctx context.Context) (string, error)
func (n *Node) Links(ctx context.Context) ([]NodeId, error)

func (*Node) ListImages

func (n *Node) ListImages(ctx context.Context) ([]string, error)

func (*Node) ListItems

func (n *Node) ListItems(ctx context.Context) ([]string, error)

func (*Node) Ref

func (n *Node) Ref(ctx context.Context) (NodeIndexEntry, error)

func (*Node) Save

func (n *Node) Save(ctx context.Context) error

func (*Node) Stats

func (n *Node) Stats(ctx context.Context) (*NodeStats, error)

func (*Node) String

func (n *Node) String() string

func (*Node) Tags

func (n *Node) Tags(ctx context.Context) ([]string, error)

func (*Node) Touch

func (n *Node) Touch(ctx context.Context) error

func (*Node) Update

func (n *Node) Update(ctx context.Context) error

func (*Node) Updated

func (n *Node) Updated(ctx context.Context) (time.Time, error)

type NodeContent

type NodeContent struct {
	// Hash is the stable content hash computed by the repository hasher.
	Hash string

	// Title is the canonical title for the content. For Markdown this is the
	// first H1; for RST it is the detected title.
	Title string

	// Lead is the first paragraph immediately following the title. It is used
	// as a short summary or preview of the content.
	Lead string

	// Links is the list of numeric outgoing node links discovered in the
	// content (for example "../42"). Entries are normalized NodeId values.
	Links []NodeId

	// Format is a short hint of the detected format. Typical values are
	// "markdown", "rst", or "empty".
	Format string

	// Body is the content body with Markdown frontmatter removed when present.
	// For non-Markdown formats this is the original file content.
	Body string

	// Frontmatter is the parsed YAML frontmatter when present. It is non-nil
	// only for Markdown documents that include a leading YAML block.
	Frontmatter map[string]any
}

NodeContent holds the extracted pieces of a node's primary content file (README.md or README.rst).

Fields:

  • Hash: stable content hash computed by the repository hasher.
  • Title: canonical title (first H1 for Markdown, or RST title detected).
  • Lead: first paragraph immediately following the title (used as a short summary).
  • Links: numeric outgoing node links discovered in the content (../N).
  • Format: short hint of the detected format ("markdown", "rst", or "empty").
  • Frontmatter: parsed YAML frontmatter when present (Markdown only).
  • Body: the raw body bytes of the content file with frontmatter removed for Markdown (or the original bytes for other formats), represented as a string.

func ParseContent

func ParseContent(rt *toolkit.Runtime, data []byte, format string) (*NodeContent, error)

ParseContent extracts a NodeContent value from raw file bytes.

The format parameter is a filename hint (e.g., "README.md", "README.rst"). When format is ambiguous the function applies simple heuristics to choose between Markdown and reStructuredText. The returned NodeContent contains a deterministic, deduplicated, sorted list of discovered numeric links.

ParseContent uses the provided runtime hasher to compute content Hash. If the input is empty or only whitespace, a NodeContent with Format == "empty" is returned.

type NodeCreate added in v0.38.0

type NodeCreate struct {
	Key    string `json:"key"`
	Schema string `json:"schema,omitempty"`
	// Body is the node's complete markdown content; its H1 is the title. Meta
	// is the node's complete metadata document. These are the only two inputs
	// a node is built from — there is deliberately no second, field-at-a-time
	// way to write a title, lead, tags, or attributes.
	Body []byte `json:"body,omitempty"`
	Meta []byte `json:"meta,omitempty"`
}

type NodeData

type NodeData struct {
	// ID is the node identifier as a string (for example "42" or "42-0001").
	// Keep this lightweight while other fields are exposed via accessors.
	ID      NodeId
	Content *NodeContent
	Meta    *NodeMeta
	Stats   *NodeStats

	// Ancillary names (attachments and images). Implementations may populate these
	// from the repository.
	Items  []string
	Images []string
}

NodeData is a high-level representation of a KEG node. Implementations may compose this from repository pieces such as meta, content, and ancillary items.

func (*NodeData) Accessed

func (n *NodeData) Accessed() time.Time

Accessed returns the accessed timestamp from stats when available.

func (*NodeData) ContentChanged

func (n *NodeData) ContentChanged() bool

ContentChanged reports whether the source state has changed since stats were refreshed.

func (*NodeData) ContentHash

func (n *NodeData) ContentHash() string

ContentHash returns the content hash if content is present, otherwise the empty string.

func (*NodeData) Created

func (n *NodeData) Created() time.Time

Created returns the created timestamp from stats when available.

func (*NodeData) Format

func (n *NodeData) Format() string

Format returns the content format hint (for example "markdown" or "rst").

func (*NodeData) Lead

func (n *NodeData) Lead() string

Lead returns the short lead/summary for the node. Prefer stats then content.

func (n *NodeData) Links() []NodeId

Links returns the outgoing links discovered for the node. Prefer stats and fall back to parsed content links when stats are unavailable.

func (*NodeData) MetaHash

func (n *NodeData) MetaHash() string

MetaHash returns the stored programmatic hash when available.

func (*NodeData) Ref

func (n *NodeData) Ref() NodeIndexEntry

Ref builds a NodeIndexEntry from the NodeData. If the NodeData.ID is malformed ParseNode may fail and the function will fall back to a zero NodeId.

func (*NodeData) Tags

func (n *NodeData) Tags() []string

Tags returns a copy of the normalized tag list from metadata or nil if not set.

func (*NodeData) Title

func (n *NodeData) Title() string

Title returns the canonical title for the node. Prefer stats title and fall back to parsed content title when available.

func (*NodeData) Touch

func (n *NodeData) Touch(ctx context.Context, now *time.Time)

func (*NodeData) UpdateMeta

func (n *NodeData) UpdateMeta(ctx context.Context, now *time.Time) error

func (*NodeData) Updated

func (n *NodeData) Updated() time.Time

Updated returns the updated timestamp from stats when available.

type NodeEvent added in v0.6.0

type NodeEvent struct {
	Kind   NodeEventKind
	NodeID NodeId
	Field  string // "content", "meta", "stats", or ""
}

NodeEvent describes a single change or access observed on a node. Field identifies which part of the node was affected: "content" for README.md, "meta" for meta.yaml, "stats" for stats.json, or "" when the event applies to the node as a whole (e.g. creation or deletion of the entire directory).

type NodeEventKind added in v0.6.0

type NodeEventKind int

NodeEventKind identifies the type of change that occurred on a node.

const (
	// NodeEventCreated indicates a new node was created.
	NodeEventCreated NodeEventKind = iota + 1
	// NodeEventModified indicates an existing node file was modified.
	NodeEventModified
	// NodeEventDeleted indicates a node or node file was removed.
	NodeEventDeleted
	// NodeEventAccessed indicates a node's content or metadata was read.
	NodeEventAccessed
)

func (NodeEventKind) String added in v0.6.0

func (k NodeEventKind) String() string

String returns a human-readable label for the event kind.

type NodeId

type NodeId struct {
	ID    int
	Alias string
	// Code is an additional random identifier used to signify an uncommitted node.
	Code string
}

NodeId is the stable numeric identifier for a KEG node. The ID field is the canonical non-negative integer identifier. The optional Code field is a zero-padded 4-digit numeric suffix used to represent an uncommitted or temporary variant of the node.

func NewTempNode

func NewTempNode(ctx context.Context, id string) *NodeId

NewTempNode creates a new NodeId using the provided base id string and a 4-digit numeric code. The function attempts to parse the base id via ParseNode; if that fails it will try to parse the string as a non-negative integer. If the id is empty or cannot be parsed as a non-negative integer the returned NodeId will have ID set to 0.

The Code is generated with crypto/rand when available and falls back to the current nanotime if random bytes cannot be obtained. The code is returned as a zero-padded 4-digit string.

The context parameter is accepted to allow future callers to pass context without changing the signature. It is not used by the current implementation.

func ParseNode

func ParseNode(s string) (*NodeId, error)

ParseNode converts a string into a *NodeId.

Accepted forms:

  • "0" or a non-negative integer without leading zeros (for example "1", "23")

  • "<id>-<code>" where <id> follows the rules above and <code> is exactly 4 digits

  • "keg:<alias>/<id>" or "keg:<alias>/<id>-<code>" to include an alias.

Examples:

"42"               -> &NodeId{ID:42, Code:""}, nil
"42-0001"          -> &NodeId{ID:42, Code:"0001"}, nil
"keg:work/23"      -> &NodeId{ID:23, Keg:"work"}, nil
"keg:work/23-0001" -> &NodeId{ID:23, Keg:"work", Code:"0001"}, nil
"0023"             -> nil, error (leading zeros not allowed)
""                 -> nil, error

func (NodeId) Compare

func (n NodeId) Compare(other NodeId) int

Compare returns -1 if n < other, 1 if n > other, and 0 if they are equal.

func (NodeId) Equals

func (n NodeId) Equals(other NodeId) bool

Equals reports whether two Nodes are identical in ID and Code.

func (NodeId) Gt

func (n NodeId) Gt(other NodeId) bool

Gt reports whether n is strictly greater than other using ID then Code.

func (NodeId) Gte

func (n NodeId) Gte(other NodeId) bool

Gte reports whether n is greater than or equal to other.

func (NodeId) Increment

func (n NodeId) Increment() NodeId

Increment returns a new NodeId with the ID value increased by one while preserving the Code.

func (NodeId) Lt

func (n NodeId) Lt(other NodeId) bool

Lt reports whether n is strictly less than other using ID then Code.

func (NodeId) Lte

func (n NodeId) Lte(other NodeId) bool

Lte reports whether n is less than or equal to other.

func (NodeId) Path

func (id NodeId) Path() string

Path returns the path component for this NodeId suitable for use in file names or URLs.

Examples:

NodeId{ID:42, Code:""}      -> "42"
NodeId{ID:42, Code:"0001"}  -> "42-0001"
NodeId{ID:42, Keg:"work"} -> "keg:work/42"

func (NodeId) PathNumeric added in v0.23.0

func (id NodeId) PathNumeric() string

PathNumeric returns just the "<id>" or "<id>-<code>" portion of the node identifier, without any "keg:<alias>/" prefix. It is the canonical tail shared by every reference form.

func (NodeId) String

func (id NodeId) String() string

func (NodeId) Valid

func (id NodeId) Valid() bool

Valid reports whether the NodeId ID is a non-negative integer.

type NodeIndex

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

NodeIndex is an in-memory index of node descriptors used to construct the `nodes.tsv` index artifact.

The index stores a slice of `NodeIndexEntry` values in a deterministic order (ascending by numeric node id, with codes used to break ties). It provides helpers to parse a serialized index, mutate the in-memory list, and produce the canonical serialized bytes.

Concurrency note: NodeIndex itself does not perform internal synchronization. Callers that require concurrent access should guard an instance with a mutex.

func ParseNodeIndex

func ParseNodeIndex(ctx context.Context, data []byte) (NodeIndex, error)

ParseNodeIndex parses the serialized nodes index bytes into a NodeIndex.

Expected input is zero or more lines separated by newline. Each non-empty line represents a node entry in the canonical TSV format used by the repo. Parsers should tolerate empty input and skip malformed lines while continuing to parse the remainder. An empty input yields an empty NodeIndex and no error.

Parsing rules and leniency:

  • Each valid line is expected to contain at least the ID field. Additional columns (for example updated timestamp and title) are accepted when present.
  • Lines that cannot be parsed into a valid NodeIndexEntry are skipped and do not cause the entire parse to fail. This allows forward compatibility when new columns are added to the on-disk format.
  • The returned NodeIndex contains entries in the order they were parsed; it is the caller's responsibility to sort or normalize ordering if desired.

Returns:

  • a NodeIndex containing parsed NodeIndexEntry values.
  • a non nil error only for unexpected conditions preventing parsing of the entire input (for example severe encoding issues). Minor line-level parse problems are tolerated and do not cause an error.

Example input (5-column format):

"42\t2025-01-02T15:04:05Z\t2024-06-01T10:00:00Z\t2025-01-03T08:00:00Z\tMy Title\n"

Column order: id<TAB>updated<TAB>created<TAB>accessed<TAB>title

func (*NodeIndex) Add

func (idx *NodeIndex) Add(ctx context.Context, data *NodeData) error

Add inserts the provided node into the index. The index should remain sorted by ascending node id after the operation.

Behavior expectations:

  • If idx is nil the call is a no-op and returns nil.
  • The method should ensure idx.data is initialized when first used.
  • Adding an existing node id should be idempotent: the existing entry should be updated or replaced rather than producing duplicates.
  • The operation is in-memory only and does not perform I/O.

Typical callers: - Index builders that aggregate node metadata into the nodes index. - Tests that need to construct an in-memory nodes list.

Note: This method does not acquire any synchronization; callers should hold a lock if concurrent mutations are possible.

Implementation note:

The function should insert or update the NodeIndexEntry derived from the
supplied NodeData. After modification, idx.data must be ordered so that
Next and serialized output are stable and deterministic.

func (*NodeIndex) Data

func (idx *NodeIndex) Data(ctx context.Context) ([]byte, error)

Data serializes the NodeIndex into the canonical on-disk TSV representation.

Serialization rules:

  • Each entry produces a single line in the form used by the repository's nodes index. Column order is: id<TAB>updated<TAB>created<TAB>accessed<TAB>title<LF>.
  • Entries must be emitted in ascending node id order.
  • An empty index returns an empty byte slice.

The returned bytes are owned by the caller and may be written atomically by the repository layer. The function should not modify idx.data.

Implementation note:

The function should not rely on external state. It must produce stable,
deterministic output suitable for writing to an index file.

func (*NodeIndex) Get

func (idx *NodeIndex) Get(ctx context.Context, node NodeId) *NodeIndexEntry

Get returns the NodeIndexEntry pointer for the provided node if present. The lookup uses node.Path() to match the ID field of entries.

Returns:

  • *NodeIndexEntry when the entry is present.
  • nil when the entry is not present or idx is nil.

The returned pointer points into the internal slice. Callers that need to modify the entry should copy it first to avoid data races.

func (*NodeIndex) List

func (idx *NodeIndex) List(ctx context.Context) []NodeIndexEntry

List returns the in-memory slice of NodeIndexEntry. The returned slice is the underlying data and callers should not mutate it to avoid data races.

func (*NodeIndex) Next

func (idx *NodeIndex) Next(ctx context.Context) NodeId

Next returns the next available NodeId id based on the current index contents.

Semantics:

  • If the index is empty, Next returns NodeId{ID:0, Code:""} (the zeroth id).
  • Otherwise Next returns a NodeId whose ID is one greater than the highest numeric ID present in the index. If entries contain code suffixes the numeric portion is used for ordering.
  • The function does not modify the index.

Implementation note:

The function should examine idx.data to determine the maximal numeric id and
return the subsequent id. It should not allocate or write any external state.

func (*NodeIndex) Rm

func (idx *NodeIndex) Rm(ctx context.Context, node NodeId) error

Rm removes the node identified by id from the index.

Behavior expectations: - If idx is nil the call is a no-op and returns nil. - If the node is not present the call should not error. - After removal the index slice should remain in a stable, sorted state. - This method only mutates in-memory state and does not perform I/O.

Typical callers: - Index maintenance routines that remove entries for deleted nodes. - Tests cleaning up expected state.

Implementation note:

The function should locate the entry whose ID equals node.Path() and remove
it from the slice. The remaining slice should preserve deterministic order.

type NodeIndexEntry

type NodeIndexEntry struct {
	ID       string    `json:"id" yaml:"id"`
	Title    string    `json:"title" yaml:"title"`
	Updated  time.Time `json:"updated" yaml:"updated"`
	Created  time.Time `json:"created" yaml:"created"`
	Accessed time.Time `json:"accessed" yaml:"accessed"`
}

NodeIndexEntry is a small descriptor for a node used by repository listings and indices. It contains the node id as a string, a human-friendly title, and timestamps for updated, created, and accessed.

type NodeMeta

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

NodeMeta holds manually edited node metadata and helpers to read/update it.

Programmatic fields (title/hash/timestamps/lead/links) are represented by NodeStats. NodeMeta focuses on human-editable yaml data and comment-preserving writes.

func NewMeta

func NewMeta(ctx context.Context, now time.Time) *NodeMeta

NewMeta constructs an empty NodeMeta.

func ParseMeta

func ParseMeta(ctx context.Context, raw []byte) (*NodeMeta, error)

ParseMeta parses raw yaml bytes into NodeMeta. Empty input returns an empty NodeMeta.

func (*NodeMeta) AddTag

func (m *NodeMeta) AddTag(tag string)

func (*NodeMeta) Get

func (m *NodeMeta) Get(key string) (string, bool)

Get retrieves scalar metadata fields by key.

func (*NodeMeta) Keys added in v0.40.0

func (m *NodeMeta) Keys() []string

Keys returns the top-level keys present in the metadata document, in document order. It exists so callers can detect two inputs writing the same key before either write happens; Get cannot serve that purpose because it reports only scalars and so silently misses nested keys.

func (*NodeMeta) RmTag

func (m *NodeMeta) RmTag(tag string)

func (*NodeMeta) Set

func (m *NodeMeta) Set(ctx context.Context, key string, val any) error

Set updates known NodeMeta keys (tags) and preserves unknown keys in the yaml node when available.

func (*NodeMeta) SetAttrs

func (m *NodeMeta) SetAttrs(ctx context.Context, attrs map[string]any) error

func (*NodeMeta) SetTags

func (m *NodeMeta) SetTags(tags []string)

func (*NodeMeta) Tags

func (m *NodeMeta) Tags() []string

func (*NodeMeta) ToYAML

func (m *NodeMeta) ToYAML() string

ToYAML serializes only manually edited metadata fields.

func (*NodeMeta) ToYAMLWithStats

func (m *NodeMeta) ToYAMLWithStats(stats *NodeStats) string

ToYAMLWithStats serializes metadata while optionally merging programmatic NodeStats fields into the emitted yaml.

type NodeMoveOptions added in v0.39.0

type NodeMoveOptions struct {
	Source       NodeId `json:"source"`
	Destination  NodeId `json:"destination"`
	ExpectedHash string `json:"expected_hash,omitempty"`
}

type NodeOpenOptions added in v0.33.0

type NodeOpenOptions struct {
	ID        NodeId    `json:"id"`
	Touch     bool      `json:"touch,omitempty"`
	LockToken LockToken `json:"lock_token,omitempty"`
}

type NodeRef added in v0.23.0

type NodeRef struct {
	Form      RefForm
	Node      NodeId
	Alias     string // RefAlias only
	Namespace string // RefQualified only, "@" sigil stripped
	KegName   string // RefQualified only
}

NodeRef is a parsed node reference. Node always carries the numeric id and optional 4-digit code. Form selects how the owning keg is addressed:

  • RefLocal: Node only; resolves against the current keg.
  • RefAlias: Alias set; resolves against the current keg's Links table then the tap-settings kegs map. Node.Alias mirrors Alias.
  • RefQualified: Namespace+KegName set; the hub is implied from context.

func ParseNodeRef added in v0.23.0

func ParseNodeRef(s string) (*NodeRef, error)

ParseNodeRef parses any of the three node-reference forms. It is a superset of ParseNode: bare ids and "keg:<alias>/<id>" parse identically, and the qualified "keg:@<namespace>/<keg>/<id>" form is additionally recognized.

The forms are disambiguated purely textually: a leading "keg:@" with two slashes is qualified; "keg:" with one slash is an alias; anything else is a bare local id.

func (NodeRef) String added in v0.23.0

func (r NodeRef) String() string

String renders the canonical text form, the inverse of ParseNodeRef.

type NodeRemoveOptions added in v0.39.0

type NodeRemoveOptions struct {
	ID           NodeId `json:"id"`
	ExpectedHash string `json:"expected_hash,omitempty"`
}

type NodeSnapshotRequest added in v0.38.0

type NodeSnapshotRequest struct {
	ID      NodeId `json:"id"`
	Message string `json:"message,omitempty"`
}

type NodeStats

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

NodeStats contains programmatic node data derived by tooling.

func NewStats

func NewStats(now time.Time) *NodeStats

func ParseStats

func ParseStats(ctx context.Context, raw []byte) (*NodeStats, error)

ParseStats extracts programmatic node stats from raw JSON bytes.

func (*NodeStats) AccessCount

func (s *NodeStats) AccessCount() int

func (*NodeStats) Accessed

func (s *NodeStats) Accessed() time.Time

func (*NodeStats) ClearOmega added in v0.28.0

func (s *NodeStats) ClearOmega()

func (*NodeStats) Created

func (s *NodeStats) Created() time.Time

func (*NodeStats) Creator added in v0.41.0

func (s *NodeStats) Creator() string

Creator is the attributed username, never an authorization credential.

func (*NodeStats) EnsureTimes

func (s *NodeStats) EnsureTimes(now time.Time)

func (*NodeStats) Hash

func (s *NodeStats) Hash() string

func (*NodeStats) IncrementAccessCount

func (s *NodeStats) IncrementAccessCount()

func (*NodeStats) Lead

func (s *NodeStats) Lead() string
func (s *NodeStats) Links() []NodeId

func (*NodeStats) Omega added in v0.28.0

func (s *NodeStats) Omega() (float64, bool)

func (*NodeStats) SetAccessCount

func (s *NodeStats) SetAccessCount(count int)

func (*NodeStats) SetAccessed

func (s *NodeStats) SetAccessed(t time.Time)

func (*NodeStats) SetCreated

func (s *NodeStats) SetCreated(t time.Time)

func (*NodeStats) SetHash

func (s *NodeStats) SetHash(hash string, now *time.Time)

func (*NodeStats) SetIdentity added in v0.41.0

func (s *NodeStats) SetIdentity(uuid, creator string)

SetIdentity preserves portable attribution through serialization. The hosting repository generates and validates identity on supported write paths.

func (*NodeStats) SetLead

func (s *NodeStats) SetLead(lead string)
func (s *NodeStats) SetLinks(links []NodeId)

func (*NodeStats) SetOmega added in v0.28.0

func (s *NodeStats) SetOmega(omega float64)

func (*NodeStats) SetTitle

func (s *NodeStats) SetTitle(title string)

func (*NodeStats) SetUpdated

func (s *NodeStats) SetUpdated(t time.Time)

func (*NodeStats) Title

func (s *NodeStats) Title() string

func (*NodeStats) ToJSON

func (s *NodeStats) ToJSON() ([]byte, error)

func (*NodeStats) UUID added in v0.41.0

func (s *NodeStats) UUID() string

UUID is the portable node identity. Stored copies may share it.

func (*NodeStats) UpdateFromContent

func (s *NodeStats) UpdateFromContent(content *NodeContent, now *time.Time)

func (*NodeStats) UpdateFromSource added in v0.28.0

func (s *NodeStats) UpdateFromSource(rt *toolkit.Runtime, content *NodeContent, meta *NodeMeta, now *time.Time)

func (*NodeStats) Updated

func (s *NodeStats) Updated() time.Time

type NodeUpdateOptions added in v0.33.0

type NodeUpdateOptions struct {
	ID             NodeId    `json:"id"`
	Schema         string    `json:"schema,omitempty"`
	Content        []byte    `json:"content"`
	HasContent     bool      `json:"has_content,omitempty"`
	Meta           []byte    `json:"meta,omitempty"`
	HasMeta        bool      `json:"has_meta,omitempty"`
	LockToken      LockToken `json:"lock_token,omitempty"`
	ExpectedHash   string    `json:"expected_hash,omitempty"`
	SnapshotBefore bool      `json:"snapshot_before,omitempty"`
}

type NodeUpdateResult added in v0.33.0

type NodeUpdateResult struct {
	ID         NodeId                  `json:"id"`
	Validation *SchemaValidationResult `json:"validation,omitempty"`
	Hash       string                  `json:"hash"`
}

type NodeValidationPayload added in v0.25.0

type NodeValidationPayload struct {
	// Create validates a complete creation draft without reading or allocating a node.
	Create     bool
	ID         NodeId
	Schema     string
	Content    []byte
	HasContent bool
	Meta       []byte
	HasMeta    bool
}

type NodeView added in v0.23.0

type NodeView struct {
	ID NodeId
	// Content is the node's primary content (README.md).
	Content []byte
	// Meta is the raw metadata bytes as stored (may be empty).
	Meta []byte
	// Stats is the parsed programmatic stats (zero-value when absent).
	Stats *NodeStats
	// Files and Images list asset names; nil when the backend lacks the
	// capability.
	Files  []string
	Images []string
	// contains filtered or unexported fields
}

NodeView is a node's full state assembled in one operation.

func (NodeView) Hash added in v0.39.0

func (v NodeView) Hash() string

Hash is the node's precondition token: the value a caller echoes back as NodeUpdateOptions.ExpectedHash so a write is rejected when the node changed after it was read. It covers content *and* metadata (see nodeStateHash), so a content edit and a metadata edit on one node correctly conflict with each other. An empty result means the node has neither yet.

type NodeWriteOptions added in v0.38.0

type NodeWriteOptions struct {
	Schema string
}

NodeWriteOptions configures schema-aware direct LocalKeg writes. These writes keep the direct methods' node-lock and transaction behavior and do not participate in advisory session locks.

type Option

type Option func(*LocalKeg)

Option is a functional option for configuring LocalKeg behavior

type OrientationState added in v0.39.0

type OrientationState struct {
	// AllowedTargets binds discovered KEGs to destination Hubs; nil is a Hub-local context.
	AllowedTargets []string `json:"-"`
	// RootHub is trusted local routing metadata and is never transported.
	RootHub  string `json:"-"`
	Root     string `json:"root"`
	Active   string `json:"active"`
	Revision string `json:"revision,omitempty"`
}

OrientationState is the minimum state a Hub needs to recompute current authority for a governed request.

func DecodeOrientationState added in v0.39.0

func DecodeOrientationState(value string) (OrientationState, error)

DecodeOrientationState parses a versioned orientation header. The result is untrusted until the Hub authenticates the caller and evaluates current permissions.

func OrientationStateFromContext added in v0.39.0

func OrientationStateFromContext(ctx context.Context) (OrientationState, bool)

OrientationStateFromContext returns trusted orientation state, when present.

type OrientationValidator added in v0.39.0

type OrientationValidator func(context.Context) error

OrientationValidator recomputes authority at an operation boundary.

type PreconditionConflictError added in v0.39.0

type PreconditionConflictError struct {
	Resource       string
	CurrentHash    string
	CurrentContent []byte
}

PreconditionConflictError reports an optimistic-concurrency conflict while preserving the current representation needed to recover and retry. It unwraps to ErrConflict so existing conflict checks continue to work.

func (*PreconditionConflictError) Error added in v0.39.0

func (e *PreconditionConflictError) Error() string

func (*PreconditionConflictError) Unwrap added in v0.39.0

func (e *PreconditionConflictError) Unwrap() error

type QueryExpr added in v0.18.0

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

QueryExpr is an opaque compiled tag boolean expression. Callers obtain one via ParseQueryExpression and pass it to EvaluateQueryExpression. The underlying AST is unexported; external packages cannot inspect or implement it.

func ParseQueryExpression added in v0.18.0

func ParseQueryExpression(raw string) (QueryExpr, error)

ParseQueryExpression compiles raw into a QueryExpr that can be evaluated with EvaluateQueryExpression. Returns an error if raw is empty or syntactically invalid.

type QueryFilteredIndex added in v0.15.0

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

func NewQueryFilteredIndex added in v0.15.0

func NewQueryFilteredIndex(name, query string, resolve func(term string, data *NodeData) bool) (*QueryFilteredIndex, error)

NewQueryFilteredIndex creates a QueryFilteredIndex for the given index file name and boolean query string. The optional resolve callback enables key=value attribute predicates and other term types.

When resolve is nil, terms are evaluated as tag names against the node's tag set.

name should be the bare filename used when writing to the repository, e.g. "golang.md".

func NewQueryFilteredIndexWithSort added in v0.18.0

func NewQueryFilteredIndexWithSort(name, query string, resolve func(term string, data *NodeData) bool, sortOrder QueryFilteredSortOrder) (*QueryFilteredIndex, error)

NewQueryFilteredIndexWithSort creates a QueryFilteredIndex with an explicit sort order. The sortOrder parameter accepts "id", "created", "accessed", or empty string (default: sort by Updated descending).

func (*QueryFilteredIndex) Add added in v0.15.0

func (idx *QueryFilteredIndex) Add(ctx context.Context, data *NodeData) error

Add evaluates the query expression against the node and, if it matches, inserts or updates the node entry maintaining the configured sort order.

func (*QueryFilteredIndex) Clear added in v0.15.0

func (idx *QueryFilteredIndex) Clear(ctx context.Context) error

Clear resets the index to an empty state.

func (*QueryFilteredIndex) Data added in v0.15.0

func (idx *QueryFilteredIndex) Data(ctx context.Context) ([]byte, error)

Data serializes the QueryFilteredIndex to the same markdown format as ChangesIndex.Data. Entries are in reverse-chronological order.

func (*QueryFilteredIndex) Name added in v0.15.0

func (idx *QueryFilteredIndex) Name() string

Name returns the short index filename used with repo.WriteIndex.

func (*QueryFilteredIndex) Remove added in v0.15.0

func (idx *QueryFilteredIndex) Remove(ctx context.Context, node NodeId) error

Remove removes the node identified by node from the index. If the node is not present the call is a no-op.

type QueryFilteredSortOrder added in v0.18.0

type QueryFilteredSortOrder string

QueryFilteredIndex is an in-memory index of nodes that match a boolean query expression. It supports the full query expression system: tag names, key=value attribute predicates, boolean operators (and/or/not), and parenthesized grouping.

The resolve callback, when non-nil, is called for each term in the query expression with each candidate node. This allows higher-level packages (e.g. pkg/tapper) to inject attribute predicate support without creating a dependency from pkg/keg to pkg/tapper.

When resolve is nil, the index falls back to tag-only matching (each term is evaluated as a tag name against the node's tag set).

Concurrency note: QueryFilteredIndex does not perform internal synchronization. Callers should guard access with a mutex when needed. QueryFilteredSortOrder controls the sort order of entries in a QueryFilteredIndex.

const (
	// QFSortUpdated sorts by Updated descending (newest first). This is the
	// default when no sort order is specified.
	QFSortUpdated QueryFilteredSortOrder = ""
	// QFSortID sorts by node ID ascending.
	QFSortID QueryFilteredSortOrder = "id"
	// QFSortCreated sorts by Created descending (newest first).
	QFSortCreated QueryFilteredSortOrder = "created"
	// QFSortAccessed sorts by Accessed descending (newest first).
	QFSortAccessed QueryFilteredSortOrder = "accessed"
)

type QueryOptions added in v0.23.0

type QueryOptions struct {
	// Expr is the boolean query expression, e.g. "project and .updated>2026-01-01".
	Expr string
}

QueryOptions configures Keg.Query.

type RateLimitError

type RateLimitError struct {
	RetryAfter time.Duration // suggested wait time
	Message    string
	Cause      error
}

RateLimitError represents a throttling response that includes a suggested RetryAfter duration and an optional message. It is always considered retryable.

func (*RateLimitError) Error

func (e *RateLimitError) Error() string

func (*RateLimitError) Retryable

func (e *RateLimitError) Retryable() bool

func (*RateLimitError) Unwrap

func (e *RateLimitError) Unwrap() error

type ReadNodesOptions added in v0.33.0

type ReadNodesOptions struct {
	NodeIDs []NodeId `json:"node_ids,omitempty"`
	Query   string   `json:"query,omitempty"`
	Touch   bool     `json:"touch,omitempty"`
}

type RefForm added in v0.23.0

type RefForm int

RefForm enumerates the three shapes a node reference may take.

const (
	// RefLocal is a bare "<id>" or "<id>-<code>" resolving against the current keg.
	RefLocal RefForm = iota
	// RefAlias is "keg:<alias>/<id>[-<code>]" — the alias resolves against the
	// current keg's Links table (then the tap-settings kegs map).
	RefAlias
	// RefQualified is "keg:@<namespace>/<keg>/<id>[-<code>]" — fully qualified;
	// the hub is implied from the current keg's hub.
	RefQualified
	// RefSettingsAlias is the explicit keg:~alias/node form.
	RefSettingsAlias
)

type RelatedDirection added in v0.33.0

type RelatedDirection string
const (
	RelatedLinks     RelatedDirection = "links"
	RelatedBacklinks RelatedDirection = "backlinks"
)

type RelatedNodesOptions added in v0.33.0

type RelatedNodesOptions struct {
	NodeIDs   []NodeId         `json:"node_ids"`
	Direction RelatedDirection `json:"direction"`
}

type RelationSchema added in v0.25.0

type RelationSchema struct {
	Name        string                 `yaml:"name,omitempty" json:"name,omitempty"`
	Type        string                 `yaml:"type,omitempty" json:"type,omitempty"`
	Description string                 `yaml:"description,omitempty" json:"description,omitempty"`
	Required    bool                   `yaml:"required,omitempty" json:"required,omitempty"`
	Maturity    []MaturityWeightSchema `yaml:"maturity,omitempty" json:"maturity,omitempty"`
}

type RemoteKeg added in v0.23.0

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

RemoteKeg implements Keg over tapper-hub's operation-level HTTP API. Each Keg method is a single HTTP round trip against the per-keg base URL (`<hub>/api/v1/@{namespace}/kegs/{keg}`); all orchestration — locking discipline, dex/index maintenance, stats touching — happens server-side inside the hub's LocalKeg.

func NewRemoteKeg added in v0.23.0

func NewRemoteKeg(baseURL, token string, rt *toolkit.Runtime) *RemoteKeg

NewRemoteKeg constructs a RemoteKeg speaking the hub's operation API at baseURL (the per-keg prefix, trailing slash trimmed) with bearer token authentication.

func (*RemoteKeg) AppendSnapshot added in v0.23.0

func (k *RemoteKeg) AppendSnapshot(ctx context.Context, id NodeId, msg string) (Snapshot, error)

AppendSnapshot implements Keg via POST /nodes/{id}/snapshots. The body carries only the message — the hub computes the snapshot payloads from the node's current server-side state.

func (*RemoteKeg) AppendSnapshots added in v0.38.0

func (k *RemoteKeg) AppendSnapshots(ctx context.Context, nodes []NodeSnapshotRequest) ([]Snapshot, error)

func (*RemoteKeg) BaseURL added in v0.23.0

func (k *RemoteKeg) BaseURL() string

BaseURL returns the per-keg API prefix this client targets.

func (*RemoteKeg) Commit added in v0.23.0

func (k *RemoteKeg) Commit(ctx context.Context, id NodeId) error

Commit implements Keg via POST /nodes/{id}/commit.

func (*RemoteKeg) Create added in v0.23.0

func (k *RemoteKeg) Create(ctx context.Context, opts *CreateOptions) (CreateResult, error)

Create implements Keg via a single POST /nodes carrying the composed content (and meta when tags/attrs are set). The node payload is composed client-side exactly as LocalKeg composes it; the hub assigns the id.

func (*RemoteKeg) CreateNodes added in v0.38.0

func (k *RemoteKeg) CreateNodes(ctx context.Context, nodes []NodeCreate) ([]CreateNodeResult, error)

func (*RemoteKeg) CreateSchema added in v0.33.0

func (k *RemoteKeg) CreateSchema(ctx context.Context, typeName string, data []byte) error

func (*RemoteKeg) DeleteAttachment added in v0.41.0

func (k *RemoteKeg) DeleteAttachment(ctx context.Context, id NodeId, kind AttachmentKind, name string) error

func (*RemoteKeg) DeleteFile added in v0.23.0

func (k *RemoteKeg) DeleteFile(ctx context.Context, id NodeId, name string) error

DeleteFile implements Keg via DELETE /nodes/{id}/assets/{name}.

func (*RemoteKeg) DeleteImage added in v0.23.0

func (k *RemoteKeg) DeleteImage(ctx context.Context, id NodeId, name string) error

DeleteImage implements Keg via DELETE /nodes/{id}/images/{name}.

func (*RemoteKeg) DeleteSchema added in v0.25.0

func (k *RemoteKeg) DeleteSchema(ctx context.Context, typeName string, opts SchemaWriteOptions) error

DeleteSchema implements Keg via DELETE /schemas/{type}.

func (*RemoteKeg) Dex added in v0.23.0

func (k *RemoteKeg) Dex(ctx context.Context) (*Dex, error)

Dex implements Keg via GET /dex, which returns every index artifact in one response. The artifacts are parsed through the same index reader used by LocalKeg without constructing a repository.

func (*RemoteKeg) DexArtifacts added in v0.33.0

func (k *RemoteKeg) DexArtifacts(ctx context.Context) (*DexArtifacts, error)

func (*RemoteKeg) Doctor added in v0.33.0

func (k *RemoteKeg) Doctor(ctx context.Context) ([]DoctorIssue, error)

func (*RemoteKeg) ExportNodes added in v0.23.0

func (k *RemoteKeg) ExportNodes(ctx context.Context, opts ExportNodesOptions) (io.ReadCloser, error)

ExportNodes implements Keg via GET /archive. The hub's response body is the keg-archive stream and is returned directly without buffering; the caller must Close it.

func (*RemoteKeg) ForceUnlock added in v0.23.0

func (k *RemoteKeg) ForceUnlock(ctx context.Context, id NodeId) error

ForceUnlock implements Keg via DELETE /nodes/{id}/lock?force=1.

func (*RemoteKeg) GetContent added in v0.23.0

func (k *RemoteKeg) GetContent(ctx context.Context, id NodeId) ([]byte, error)

GetContent implements Keg via GET /nodes/{id}/content.

func (*RemoteKeg) GetMeta added in v0.23.0

func (k *RemoteKeg) GetMeta(ctx context.Context, id NodeId) (*NodeMeta, error)

GetMeta implements Keg. Absent meta yields an empty meta rather than an error, matching LocalKeg semantics.

func (*RemoteKeg) GetMetaRaw added in v0.23.0

func (k *RemoteKeg) GetMetaRaw(ctx context.Context, id NodeId) ([]byte, error)

GetMetaRaw implements Keg via GET /nodes/{id}/meta. Missing meta propagates ErrNotExist.

func (*RemoteKeg) GetSnapshot added in v0.23.0

func (k *RemoteKeg) GetSnapshot(ctx context.Context, id NodeId, rev RevisionID, opts SnapshotReadOptions) (Snapshot, []byte, []byte, *NodeStats, error)

GetSnapshot implements Keg via GET /nodes/{id}/snapshots/{rev}, passing resolve_content when opts request materialized payloads.

func (*RemoteKeg) GetStats added in v0.23.0

func (k *RemoteKeg) GetStats(ctx context.Context, id NodeId) (*NodeStats, error)

GetStats implements Keg via GET /nodes/{id}/stats. Stats are server-managed; there is no write counterpart.

func (*RemoteKeg) Grep added in v0.23.0

func (k *RemoteKeg) Grep(ctx context.Context, opts GrepOptions) ([]GrepMatch, error)

Grep implements Keg via POST /grep.

func (*RemoteKeg) ImportNodes added in v0.23.0

func (k *RemoteKeg) ImportNodes(ctx context.Context, r io.Reader, opts ImportNodesOptions) ([]ImportedNode, error)

ImportNodes implements Keg via POST /archive, streaming r as the request body.

func (*RemoteKeg) Index added in v0.23.0

func (k *RemoteKeg) Index(ctx context.Context, opts IndexOptions) error

Index implements Keg via POST /indexes/rebuild.

func (*RemoteKeg) Info added in v0.33.0

func (k *RemoteKeg) Info(ctx context.Context) (*KegInfo, error)

func (*RemoteKeg) Init added in v0.23.0

func (k *RemoteKeg) Init(ctx context.Context) error

Init implements Keg. Remote kegs are created through the hub's keg-creation endpoint (POST /api/v1/@{namespace}/kegs) at the Tap layer, not through the per-keg operation API.

func (*RemoteKeg) ListAttachments added in v0.41.0

func (k *RemoteKeg) ListAttachments(ctx context.Context, in AttachmentListRequest) (*AttachmentPage, error)

func (*RemoteKeg) ListEntries added in v0.33.0

func (k *RemoteKeg) ListEntries(ctx context.Context, opts ListEntriesOptions) (*ListEntriesResult, error)

func (*RemoteKeg) ListFiles added in v0.23.0

func (k *RemoteKeg) ListFiles(ctx context.Context, id NodeId) ([]string, error)

ListFiles implements Keg via GET /nodes/{id}/assets.

func (*RemoteKeg) ListImages added in v0.23.0

func (k *RemoteKeg) ListImages(ctx context.Context, id NodeId) ([]string, error)

ListImages implements Keg via GET /nodes/{id}/images.

func (*RemoteKeg) ListIndexes added in v0.23.0

func (k *RemoteKeg) ListIndexes(ctx context.Context) ([]string, error)

ListIndexes implements Keg via GET /indexes.

func (*RemoteKeg) ListNodes added in v0.23.0

func (k *RemoteKeg) ListNodes(ctx context.Context) ([]NodeId, error)

ListNodes implements Keg via GET /nodes.

func (*RemoteKeg) ListSchemas added in v0.25.0

func (k *RemoteKeg) ListSchemas(ctx context.Context) ([]string, error)

ListSchemas implements Keg via GET /schemas.

func (*RemoteKeg) ListSnapshots added in v0.23.0

func (k *RemoteKeg) ListSnapshots(ctx context.Context, id NodeId) ([]Snapshot, error)

ListSnapshots implements Keg via GET /nodes/{id}/snapshots.

func (*RemoteKeg) ListView added in v0.36.0

func (k *RemoteKeg) ListView(ctx context.Context, opts ListViewOptions) (*ListViewResult, error)

ListView resolves a whole listing page in one request. A hub that does not implement the route answers 404, which is reported as ErrListViewUnsupported so the caller can fall back rather than fail.

func (*RemoteKeg) Lock added in v0.23.0

func (k *RemoteKeg) Lock(ctx context.Context, id NodeId) (LockInfo, error)

Lock implements Keg via POST /nodes/{id}/lock. The hub owns the lease; no client-side renewal goroutine runs.

func (*RemoteKeg) LockStatus added in v0.23.0

func (k *RemoteKeg) LockStatus(ctx context.Context, id NodeId) (LockInfo, error)

LockStatus implements Keg via GET /nodes/{id}/lock; a zero LockInfo means unheld.

func (*RemoteKeg) Move added in v0.23.0

func (k *RemoteKeg) Move(ctx context.Context, opts NodeMoveOptions) ([]NodeId, error)

Move implements Keg via POST /nodes/{src}/move.

func (*RemoteKeg) MoveBatch added in v0.41.0

func (k *RemoteKeg) MoveBatch(ctx context.Context, items []MoveItem) ([]MutationResult, error)

func (*RemoteKeg) NodeExists added in v0.23.0

func (k *RemoteKeg) NodeExists(ctx context.Context, id NodeId) (bool, error)

NodeExists implements Keg via HEAD /nodes/{id}.

func (*RemoteKeg) OpenNode added in v0.33.0

func (k *RemoteKeg) OpenNode(ctx context.Context, opts NodeOpenOptions) (*NodeView, error)

func (*RemoteKeg) Query added in v0.23.0

func (k *RemoteKeg) Query(ctx context.Context, opts QueryOptions) ([]NodeIndexEntry, error)

Query implements Keg via POST /query.

func (*RemoteKeg) ReadAttachment added in v0.41.0

func (k *RemoteKeg) ReadAttachment(ctx context.Context, id NodeId, kind AttachmentKind, name string) ([]byte, error)

func (*RemoteKeg) ReadContentAt added in v0.23.0

func (k *RemoteKeg) ReadContentAt(ctx context.Context, id NodeId, rev RevisionID) ([]byte, error)

ReadContentAt implements Keg via GET /nodes/{id}/snapshots/{rev}/content.

func (*RemoteKeg) ReadFile added in v0.23.0

func (k *RemoteKeg) ReadFile(ctx context.Context, id NodeId, name string) ([]byte, error)

ReadFile implements Keg via GET /nodes/{id}/assets/{name}.

func (*RemoteKeg) ReadImage added in v0.23.0

func (k *RemoteKeg) ReadImage(ctx context.Context, id NodeId, name string) ([]byte, error)

ReadImage implements Keg via GET /nodes/{id}/images/{name}.

func (*RemoteKeg) ReadIndex added in v0.23.0

func (k *RemoteKeg) ReadIndex(ctx context.Context, name string) ([]byte, error)

ReadIndex implements Keg via GET /indexes/{name}.

func (*RemoteKeg) ReadNode added in v0.23.0

func (k *RemoteKeg) ReadNode(ctx context.Context, id NodeId) (*NodeView, error)

ReadNode implements Keg via GET /nodes/{id}: the node's full state in one round trip.

func (*RemoteKeg) ReadNodes added in v0.33.0

func (k *RemoteKeg) ReadNodes(ctx context.Context, opts ReadNodesOptions) ([]NodeView, error)

func (*RemoteKeg) ReadSchema added in v0.25.0

func (k *RemoteKeg) ReadSchema(ctx context.Context, typeName string) ([]byte, error)

ReadSchema implements Keg via GET /schemas/{type}.

func (*RemoteKeg) RelatedNodes added in v0.33.0

func (k *RemoteKeg) RelatedNodes(ctx context.Context, opts RelatedNodesOptions) ([]NodeIndexEntry, error)

func (*RemoteKeg) Remove added in v0.23.0

func (k *RemoteKeg) Remove(ctx context.Context, opts NodeRemoveOptions) ([]NodeId, error)

Remove implements Keg as a batch-of-one call to POST /nodes/remove.

func (*RemoteKeg) RemoveBatch added in v0.41.0

func (k *RemoteKeg) RemoveBatch(ctx context.Context, items []RemoveItem) ([]MutationResult, error)

func (*RemoteKeg) RemoveNodes added in v0.33.0

func (k *RemoteKeg) RemoveNodes(ctx context.Context, opts RemoveNodesOptions) (RemoveNodesResult, error)

func (*RemoteKeg) RenewLock added in v0.41.0

func (k *RemoteKeg) RenewLock(ctx context.Context, id NodeId, token LockToken) (LockInfo, error)

func (*RemoteKeg) RestoreBatch added in v0.41.0

func (k *RemoteKeg) RestoreBatch(ctx context.Context, items []RestoreItem) ([]MutationResult, error)

func (*RemoteKeg) RestoreSnapshot added in v0.23.0

func (k *RemoteKeg) RestoreSnapshot(ctx context.Context, id NodeId, rev RevisionID) error

RestoreSnapshot implements Keg via POST /nodes/{id}/snapshots/{rev}/restore.

func (*RemoteKeg) SetContent added in v0.23.0

func (k *RemoteKeg) SetContent(ctx context.Context, id NodeId, data []byte) error

SetContent implements Keg through the aggregate JSON mutation endpoint.

func (*RemoteKeg) SetMeta added in v0.23.0

func (k *RemoteKeg) SetMeta(ctx context.Context, id NodeId, meta *NodeMeta) error

SetMeta implements Keg through the aggregate JSON mutation endpoint.

func (*RemoteKeg) SetSettings added in v0.39.0

func (k *RemoteKeg) SetSettings(ctx context.Context, data []byte, opts SettingsWriteOptions) error

SetSettings implements Keg via PUT /settings with the raw settings bytes.

func (*RemoteKeg) SetTarget added in v0.23.0

func (k *RemoteKeg) SetTarget(target *Target)

SetTarget records the keg's resolved location.

func (*RemoteKeg) SetTokenFn added in v0.28.1

func (k *RemoteKeg) SetTokenFn(fn func() string)

SetTokenFn installs a per-request token source. Each request calls fn and uses its return value for the Authorization header; an empty return sends the request unauthenticated. Pass nil to revert to the static token.

func (*RemoteKeg) Settings added in v0.39.0

func (k *RemoteKeg) Settings(ctx context.Context) (*Settings, error)

Settings implements Keg via GET /settings.

func (*RemoteKeg) Summary added in v0.23.0

func (k *RemoteKeg) Summary(ctx context.Context) (*KegSummary, error)

Summary implements Keg via GET /summary.

func (*RemoteKeg) Target added in v0.23.0

func (k *RemoteKeg) Target() *Target

Target returns the keg's resolved location, or nil when unknown.

func (*RemoteKeg) Token added in v0.23.0

func (k *RemoteKeg) Token() string

Token returns the bearer token used for authentication ("" when none).

func (*RemoteKeg) Touch added in v0.23.0

func (k *RemoteKeg) Touch(ctx context.Context, id NodeId) error

Touch implements Keg via POST /nodes/{id}/touch.

func (*RemoteKeg) Unlock added in v0.23.0

func (k *RemoteKeg) Unlock(ctx context.Context, id NodeId, token LockToken) error

Unlock implements Keg via DELETE /nodes/{id}/lock with the X-Lock-Token header proving ownership.

func (*RemoteKeg) UpdateNode added in v0.33.0

func (k *RemoteKeg) UpdateNode(ctx context.Context, opts NodeUpdateOptions) (*NodeUpdateResult, error)

func (*RemoteKeg) UpdateNodes added in v0.38.0

func (k *RemoteKeg) UpdateNodes(ctx context.Context, updates []NodeUpdateOptions) ([]NodeUpdateResult, error)

func (*RemoteKeg) ValidateNode added in v0.25.0

func (k *RemoteKeg) ValidateNode(ctx context.Context, id NodeId) (*SchemaValidationResult, error)

ValidateNode implements Keg via POST /nodes/{id}/validate.

func (*RemoteKeg) ValidateNodePayload added in v0.25.0

func (k *RemoteKeg) ValidateNodePayload(ctx context.Context, payload NodeValidationPayload) (*SchemaValidationResult, error)

ValidateNodePayload implements Keg via POST /validate.

func (*RemoteKeg) ValidateNodes added in v0.33.0

func (k *RemoteKeg) ValidateNodes(ctx context.Context, opts ValidateNodesOptions) ([]SchemaValidationResult, error)

func (*RemoteKeg) Watch added in v0.23.0

func (k *RemoteKeg) Watch(ctx context.Context, ids ...NodeId) (<-chan NodeEvent, error)

Watch implements Keg by subscribing to the hub's per-node websocket event stream (/nodes/{id}/events) for each requested node.

func (*RemoteKeg) WriteAttachment added in v0.41.0

func (k *RemoteKeg) WriteAttachment(ctx context.Context, id NodeId, kind AttachmentKind, name string, data []byte) error

func (*RemoteKeg) WriteFile added in v0.23.0

func (k *RemoteKeg) WriteFile(ctx context.Context, id NodeId, name string, data []byte) error

WriteFile implements Keg via PUT /nodes/{id}/assets/{name}.

func (*RemoteKeg) WriteImage added in v0.23.0

func (k *RemoteKeg) WriteImage(ctx context.Context, id NodeId, name string, data []byte) error

WriteImage implements Keg via PUT /nodes/{id}/images/{name}.

func (*RemoteKeg) WriteSchema added in v0.25.0

func (k *RemoteKeg) WriteSchema(ctx context.Context, typeName string, data []byte, opts SchemaWriteOptions) error

WriteSchema implements Keg via PUT /schemas/{type}.

type RemoveItem added in v0.41.0

type RemoveItem struct {
	ID           int    `json:"id"`
	ExpectedHash string `json:"expected_hash"`
}

RemoveItem identifies a node and the version the caller intends to remove.

type RemoveNodesOptions added in v0.33.0

type RemoveNodesOptions struct {
	Nodes []NodeRemoveOptions `json:"nodes,omitempty"`
	Query string              `json:"query,omitempty"`
}

type RemoveNodesResult added in v0.33.0

type RemoveNodesResult struct {
	Removed []RemovedNode `json:"removed"`
	Failure *BatchFailure `json:"failure,omitempty"`
}

type RemovedNode added in v0.33.0

type RemovedNode struct {
	ID        NodeId   `json:"id"`
	Rewritten []NodeId `json:"rewritten"`
}

type RenderOptions added in v0.11.0

type RenderOptions struct {
	// Links supplies the source KEG settings aliases.
	Links []LinkEntry
	// BaseURL is the keg-root URL prefix for rewritten node links; node N
	// lives at {BaseURL}N. Defaults to "/".
	BaseURL string

	// NodeID identifies the node being rendered. When set, every relative
	// destination is resolved as if the page were {BaseURL}{NodeID}/README.md,
	// which covers ./images/X, ./assets/X, and ../N/README.md in addition to
	// ../N. When empty, only the legacy ../N shape is rewritten and other
	// relative links pass through unchanged.
	NodeID string

	// NoTrailingSlash emits node hrefs as {BaseURL}N instead of {BaseURL}N/.
	NoTrailingSlash bool

	// KegResolver maps keg:-scheme references to hrefs. namespace is "" for
	// the bare-alias form keg:ALIAS/N. Returning "" leaves the link
	// untouched, as does a nil resolver.
	KegResolver func(namespace, alias, nodeID string) string
}

RenderOptions configures markdown-to-HTML rendering.

type Repository

type Repository interface {
	// WithKegRead executes fn inside one coherent keg-wide read snapshot.
	// Boundaries are reentrant: reads may nest inside reads or writes.
	WithKegRead(ctx context.Context, fn func(context.Context) error) error
	// WithKegWrite executes fn while holding the keg-wide mutation boundary.
	// Writes may nest inside writes; upgrading a read boundary to a write is
	// rejected with ErrKegLockUpgrade.
	WithKegWrite(ctx context.Context, fn func(context.Context) error) error

	// Name returns a short, human-friendly backend identifier.
	Name() string

	// HasNode reports whether id exists as a node in the backend.
	// Missing nodes should return (false, nil). Backend/storage failures should
	// be returned as non-nil errors.
	HasNode(ctx context.Context, id NodeId) (bool, error)
	// Next reserves and returns the next available node id. The reservation must
	// prevent concurrent callers from receiving the same id, but need not contain
	// node content yet. Implementations should honor ctx cancellation where
	// applicable.
	Next(ctx context.Context) (NodeId, error)
	// ListNodes returns all node ids present in the backend.
	// Returned ids should be deterministic (stable ordering) when possible.
	ListNodes(ctx context.Context) ([]NodeId, error)
	// MoveNode renames or relocates a node from id to dst.
	// Implementations should return typed/sentinel errors when source is missing
	// or destination already exists.
	MoveNode(ctx context.Context, id NodeId, dst NodeId) error
	// DeleteNode removes the node and all associated persisted data.
	// If id does not exist, implementations should return a typed/sentinel
	// not-exist error.
	DeleteNode(ctx context.Context, id NodeId) error

	// WithNodeLock executes fn while holding an exclusive lock for node id.
	// Implementations should block until the lock is acquired or ctx is
	// canceled, and must release the lock after fn returns.
	WithNodeLock(ctx context.Context, id NodeId, fn func(context.Context) error) error
	// ReadContent reads the primary node content bytes (for example README.md).
	// Missing nodes should return a typed/sentinel not-exist error.
	ReadContent(ctx context.Context, id NodeId) ([]byte, error)
	// WriteContent writes primary node content bytes for id.
	// Implementations should perform atomic writes when possible.
	WriteContent(ctx context.Context, id NodeId, data []byte) error
	// ReadMeta reads raw node metadata bytes (for example meta.yaml).
	// Missing nodes should return a typed/sentinel not-exist error.
	ReadMeta(ctx context.Context, id NodeId) ([]byte, error)
	// WriteMeta writes raw node metadata bytes.
	// Implementations should preserve atomicity when possible.
	WriteMeta(ctx context.Context, id NodeId, data []byte) error
	// ReadStats returns parsed programmatic node stats for id.
	// Backends that persist stats inside meta.yaml should parse and return those
	// fields while preserving any manual metadata concerns at higher layers.
	ReadStats(ctx context.Context, id NodeId) (*NodeStats, error)
	// WriteStats writes programmatic node stats for id.
	// Implementations should preserve manually edited metadata fields when stats
	// and metadata share a storage representation.
	WriteStats(ctx context.Context, id NodeId, stats *NodeStats) error

	// GetIndex reads an index artifact by name (for example "nodes.tsv").
	// Callers should treat returned bytes as immutable.
	GetIndex(ctx context.Context, name string) ([]byte, error)
	// WriteIndex writes an index artifact by name.
	// Implementations should prefer atomic file replacement semantics.
	WriteIndex(ctx context.Context, name string, data []byte) error
	// ListIndexes returns available index artifact names.
	// Results should be deterministic when possible.
	ListIndexes(ctx context.Context) ([]string, error)
	// ClearIndexes removes or resets index artifacts in the backend.
	// This method should be idempotent and context-aware.
	ClearIndexes(ctx context.Context) error

	// ReadSettings reads repository-level keg settings.
	// Missing settings should return typed/sentinel not-exist errors.
	ReadSettings(ctx context.Context) (*Settings, error)
	// WriteSettings persists repository-level keg settings.
	// Implementations should perform atomic writes when possible.
	WriteSettings(ctx context.Context, settings *Settings) error
}

Repository is the storage backend contract used by KEG. Implementations are responsible for moving node data between storage and the service layer.

type RepositoryAtomicWrite added in v0.38.0

type RepositoryAtomicWrite interface {
	// WithKegAtomicWrite runs fn under a KEG-wide write boundary and restores
	// repository state when fn returns an error.
	WithKegAtomicWrite(ctx context.Context, fn func(context.Context) error) error
}

RepositoryAtomicWrite optionally provides rollback for a complete KEG mutation. PostgreSQL repositories already get this behavior from WithKegWrite transactions; local repositories implement this capability for multi-node operations.

type RepositoryAttachmentMetadata added in v0.41.0

type RepositoryAttachmentMetadata interface {
	// ListAttachmentMetadata returns all attachment names, kinds, and sizes for a node.
	ListAttachmentMetadata(context.Context, NodeId) ([]Attachment, error)
}

RepositoryAttachmentMetadata lists kind-qualified metadata without loading payloads.

type RepositoryBatchRead added in v0.36.0

type RepositoryBatchRead interface {
	// ReadMetaBatch returns raw metadata keyed by node id path.
	ReadMetaBatch(ctx context.Context, ids []NodeId) (map[string][]byte, error)

	// ReadStatsBatch returns parsed statistics keyed by node id path.
	ReadStatsBatch(ctx context.Context, ids []NodeId) (map[string]*NodeStats, error)
}

RepositoryBatchRead optionally reads many nodes' metadata or statistics in one operation.

It exists because listings need a value for every matching node when they sort or filter on a metadata key, and the per-node path costs a round trip each — on a database-backed repository, two queries per node, since each read also checks existence. A backend that can answer the whole set at once implements this; one that cannot simply omits it and callers fall back to reading node by node.

Implementations return only the nodes they found. A missing entry means the node has no metadata or statistics, which callers treat as empty rather than as an error: listings render from an index that is allowed to drift.

type RepositoryConcurrentAccess added in v0.33.0

type RepositoryConcurrentAccess interface {
	// SupportsConcurrentAccess reports whether calls using ctx may execute
	// concurrently with other repository operations.
	SupportsConcurrentAccess(ctx context.Context) bool
}

RepositoryConcurrentAccess optionally reports whether the repository can safely service concurrent calls for the supplied context. Repositories that do not implement this interface are assumed to support concurrent access. A transaction-bound backend can return false while still allowing normal pooled calls to run concurrently.

type RepositoryEvents added in v0.6.0

type RepositoryEvents interface {
	// Watch begins observing the specified node ids, or all nodes when no ids
	// are supplied. It closes the returned channel and releases per-watch
	// resources when ctx is canceled.
	Watch(ctx context.Context, ids ...NodeId) (<-chan NodeEvent, error)
	// Emit sends a NodeEvent to all active subscribers whose filters match.
	// Repositories use it for programmatic events such as access tracking.
	Emit(ev NodeEvent)
}

RepositoryEvents is an optional interface that Repository implementations may satisfy to provide live change notifications. Consumers use a type assertion to check whether the underlying repository supports events.

Watch begins observing changes for the specified node IDs (or all nodes when no IDs are given). The watch is scoped to ctx: events are delivered on the returned channel until ctx is cancelled, and implementations must close the channel when observation ends. There is no separate teardown — cancelling ctx releases all per-watch resources.

type RepositoryFiles

type RepositoryFiles interface {
	// ListFiles lists file attachment names for a node.
	ListFiles(ctx context.Context, id NodeId) ([]string, error)
	// ReadFile reads a file attachment for a node.
	ReadFile(ctx context.Context, id NodeId, name string) ([]byte, error)
	// WriteFile stores a file attachment for a node.
	WriteFile(ctx context.Context, id NodeId, name string, data []byte) error
	// DeleteFile removes a file attachment from a node.
	DeleteFile(ctx context.Context, id NodeId, name string) error
}

RepositoryFiles provides optional per-node file attachment access.

type RepositoryImages

type RepositoryImages interface {
	// ListImages lists image names for a node.
	ListImages(ctx context.Context, id NodeId) ([]string, error)
	// ReadImage reads an image payload for a node.
	ReadImage(ctx context.Context, id NodeId, name string) ([]byte, error)
	// WriteImage stores an image payload for a node.
	WriteImage(ctx context.Context, id NodeId, name string, data []byte) error
	// DeleteImage removes an image from a node.
	DeleteImage(ctx context.Context, id NodeId, name string) error
}

RepositoryImages provides optional per-node image access.

type RepositoryLock added in v0.11.0

type RepositoryLock interface {
	// AcquireLock acquires a cross-process lock on a node. Returns a token
	// that proves ownership. If the node is already locked by a non-stale
	// lock, blocks until the lock is released or ctx is canceled.
	AcquireLock(ctx context.Context, id NodeId) (LockToken, error)

	// ReleaseLock releases a cross-process lock. The token must match the
	// token returned by AcquireLock. Returns an error if the token does not
	// match or no lock is held.
	ReleaseLock(ctx context.Context, id NodeId, token LockToken) error

	// LockStatus returns the current lock state for a node. If no lock is
	// held (or the lock is stale), returns a zero LockInfo with no error.
	LockStatus(ctx context.Context, id NodeId) (LockInfo, error)

	// ForceReleaseLock unconditionally removes a lock regardless of token
	// ownership. Use as an escape hatch for stuck or stale locks.
	ForceReleaseLock(ctx context.Context, id NodeId) error
}

RepositoryLock provides cross-process token-based node locking. This is an optional interface (like RepositoryFiles, RepositoryImages, RepositorySnapshots). Not all repository implementations need cross-process locking.

Cross-process locks are separate from the process-scoped WithNodeLock on the core Repository interface. WithNodeLock serializes concurrent goroutines within a single process; RepositoryLock coordinates across separate CLI invocations or MCP server sessions.

type RepositoryLockRenewal added in v0.41.0

type RepositoryLockRenewal interface {
	// RenewLock requires the current unexpired lease token.
	RenewLock(context.Context, NodeId, LockToken) (LockInfo, error)
}

RepositoryLockRenewal extends a live lease without replacing its token.

type RepositorySchemas added in v0.25.0

type RepositorySchemas interface {
	// ListSchemas returns stored schema type names in lexicographic order.
	ListSchemas(ctx context.Context) ([]string, error)
	// ReadSchema returns the raw YAML stored for typeName.
	ReadSchema(ctx context.Context, typeName string) ([]byte, error)
	// CreateSchema stores a schema only when typeName does not already exist.
	// Exactly one concurrent creator succeeds; later creators return ErrExist.
	CreateSchema(ctx context.Context, typeName string, data []byte) error
	// WriteSchema stores raw YAML for a type whose existence the business layer
	// has already verified. Schema creation is a separate operation.
	WriteSchema(ctx context.Context, typeName string, data []byte) error
	// DeleteSchema removes the stored schema for typeName.
	DeleteSchema(ctx context.Context, typeName string) error
}

RepositorySchemas provides optional keg-level schema storage.

Implementations persist raw YAML keyed by schema type. LocalKeg owns parsing, type checks, JSON schema validation, and write policy; repositories only persist and retrieve schema bytes.

type RepositorySettingsDocuments added in v0.39.0

type RepositorySettingsDocuments interface {
	// ReadSettingsDocument returns the settings bytes exactly as persisted.
	ReadSettingsDocument(ctx context.Context) ([]byte, error)
	// WriteSettingsDocument atomically persists the exact supplied settings bytes.
	WriteSettingsDocument(ctx context.Context, data []byte) error
}

RepositorySettingsDocuments preserves the exact persisted representation of the keg settings document for optimistic concurrency and round-trip editing. LocalKeg uses it when available and falls back to Repository's structured settings methods for older external repositories.

type RepositorySnapshots

type RepositorySnapshots interface {
	// AppendSnapshot appends a new revision with optimistic parent check.
	// Implementations should preserve SnapshotWrite.CreatedAt when supplied and
	// otherwise stamp the revision with the current runtime clock time.
	AppendSnapshot(ctx context.Context, id NodeId, in SnapshotWrite) (Snapshot, error)

	// GetSnapshot returns snapshot metadata and optional state payloads.
	// When opts.ResolveContent is true, returned content must be fully materialized.
	GetSnapshot(ctx context.Context, id NodeId, rev RevisionID, opts SnapshotReadOptions) (snap Snapshot, content []byte, meta []byte, stats *NodeStats, err error)

	// ListSnapshots returns revisions for a node in deterministic order.
	ListSnapshots(ctx context.Context, id NodeId) ([]Snapshot, error)

	// ReadContentAt reconstructs content at a specific revision.
	ReadContentAt(ctx context.Context, id NodeId, rev RevisionID) ([]byte, error)

	// RestoreSnapshot restores live node state to rev. Implementations may append
	// a restore snapshot when createRestoreSnapshot is true.
	RestoreSnapshot(ctx context.Context, id NodeId, rev RevisionID, createRestoreSnapshot bool) error
}

RepositorySnapshots provides revision-based history operations.

type RepositoryVideos added in v0.41.0

type RepositoryVideos interface {
	// ListVideos returns original filenames sorted lexicographically.
	ListVideos(context.Context, NodeId) ([]string, error)
	// ReadVideo returns the original bytes for a filename.
	ReadVideo(context.Context, NodeId, string) ([]byte, error)
	// WriteVideo stores original bytes without renaming the attachment.
	WriteVideo(context.Context, NodeId, string, []byte) error
	// DeleteVideo removes the kind-qualified attachment.
	DeleteVideo(context.Context, NodeId, string) error
}

RepositoryVideos persists originals without transcoding or thumbnail generation.

type RestoreItem added in v0.41.0

type RestoreItem struct {
	ID           int        `json:"id"`
	Revision     RevisionID `json:"rev"`
	ExpectedHash string     `json:"expected_hash"`
}

RestoreItem selects a revision and guards the current live node version.

type RevisionID

type RevisionID int64

type SchemaDefinition added in v0.25.0

type SchemaDefinition struct {
	Version  int            `yaml:"version,omitempty" json:"version,omitempty"`
	Type     string         `yaml:"type,omitempty" json:"type,omitempty"`
	Summary  string         `yaml:"summary,omitempty" json:"summary,omitempty"`
	Meta     map[string]any `yaml:"meta,omitempty" json:"meta,omitempty"`
	Markdown MarkdownSchema `yaml:"markdown,omitempty" json:"markdown,omitempty"`
	// Maturity is deprecated legacy compatibility. Prefer property-scoped
	// metadata maturity rows under meta.properties.<property>.maturity.
	Maturity  []MetadataMaturitySchema `yaml:"maturity,omitempty" json:"maturity,omitempty"`
	Relations []RelationSchema         `yaml:"relations,omitempty" json:"relations,omitempty"`
}

func ParseSchemaDefinition added in v0.25.0

func ParseSchemaDefinition(data []byte) (*SchemaDefinition, error)

func (*SchemaDefinition) MetadataMaturityWeights added in v0.28.0

func (s *SchemaDefinition) MetadataMaturityWeights() []MetadataMaturitySchema

type SchemaPolicy added in v0.25.0

type SchemaPolicy struct {
	Strict bool           `yaml:"strict,omitempty" json:"strict,omitempty"`
	Human  ValidationMode `yaml:"human,omitempty" json:"human,omitempty"`
	Agent  ValidationMode `yaml:"agent,omitempty" json:"agent,omitempty"`
	API    ValidationMode `yaml:"api,omitempty" json:"api,omitempty"`
}

type SchemaSetValidationError added in v0.38.0

type SchemaSetValidationError struct {
	Results []SchemaValidationResult `json:"results"`
}

SchemaSetValidationError is retained for wire/API compatibility with older callers. Strict policy is now write-scoped, so live operations no longer scan a complete keg or produce this aggregate error.

func (*SchemaSetValidationError) Error added in v0.38.0

func (e *SchemaSetValidationError) Error() string

func (*SchemaSetValidationError) Unwrap added in v0.38.0

func (e *SchemaSetValidationError) Unwrap() error

type SchemaValidationError added in v0.25.0

type SchemaValidationError struct {
	NodeID string
	Type   string
	Issues []ValidationIssue
}

func (*SchemaValidationError) Error added in v0.25.0

func (e *SchemaValidationError) Error() string

func (*SchemaValidationError) Unwrap added in v0.25.0

func (e *SchemaValidationError) Unwrap() error

type SchemaValidationResult added in v0.25.0

type SchemaValidationResult struct {
	NodeID string            `json:"node_id,omitempty" yaml:"node_id,omitempty"`
	Type   string            `json:"type,omitempty" yaml:"type,omitempty"`
	Valid  bool              `json:"valid" yaml:"valid"`
	Issues []ValidationIssue `json:"issues,omitempty" yaml:"issues,omitempty"`
}

type SchemaWriteOptions added in v0.39.0

type SchemaWriteOptions struct {
	ExpectedHash string `json:"expected_hash,omitempty"`
}

type Settings added in v0.39.0

type Settings = SettingsV2

Settings is the latest version of the keg settings document.

func NewSettings added in v0.39.0

func NewSettings(options ...SettingsOption) *Settings

func ParseKegSettings added in v0.39.0

func ParseKegSettings(data []byte) (*Settings, error)

ParseKegSettings parses raw YAML settings data into the latest Settings version. It detects the "kegv" version field and performs migration from earlier versions when necessary.

func ParseKegSettingsStrict added in v0.39.0

func ParseKegSettingsStrict(data []byte) (*Settings, error)

ParseKegSettingsStrict parses raw user-supplied settings data for persistence. It rejects user-defined index entries that collide with required system indexes or duplicate another user index.

func (*Settings) Hash added in v0.39.0

func (kc *Settings) Hash() string

Hash returns the optimistic-concurrency token associated with Raw.

func (*Settings) Location added in v0.39.0

func (kc *Settings) Location() *time.Location

Location returns the *time.Location for the configured Timezone. It returns time.UTC if the Timezone field is empty or invalid.

func (*Settings) Raw added in v0.39.0

func (kc *Settings) Raw() []byte

Raw returns the exact document representation read from storage. Settings constructed in memory fall back to their canonical YAML representation.

func (*Settings) ResolveAlias added in v0.39.0

func (kc *Settings) ResolveAlias(alias string) (*Target, error)

func (*Settings) SnapshotPolicy added in v0.39.0

func (kc *Settings) SnapshotPolicy() (mode string, idleAfter time.Duration, err error)

SnapshotPolicy resolves the effective snapshot policy for this settings.

func (*Settings) String added in v0.39.0

func (kc *Settings) String() string

func (*Settings) ToJSON added in v0.39.0

func (kc *Settings) ToJSON() ([]byte, error)

ToJSON serializes the Settings to JSON.

func (*Settings) ToYAML added in v0.39.0

func (kc *Settings) ToYAML() ([]byte, error)

ToYAML serializes the Settings to YAML. The result is what gets persisted — to the on-disk `keg` file, or over the wire to a hub — so it carries no schema modeline. Editors get one added on open; see Tap.KegSettingsEdit.

func (*Settings) Touch added in v0.39.0

func (kc *Settings) Touch(t time.Time)

func (*Settings) UserIndexEntries added in v0.39.0

func (kc *Settings) UserIndexEntries() []IndexEntry

UserIndexEntries returns the user-defined indexes from a runtime settings.

type SettingsOption added in v0.39.0

type SettingsOption = func(cfg *Settings)

type SettingsV1 added in v0.39.0

type SettingsV1 struct {
	// Kegv is the version of the specification.
	Kegv string `yaml:"kegv"`

	// Updated indicates when the keg was last indexed.
	Updated string `yaml:"updated,omitempty"`

	// Title is the title of the KEG worklog or project.
	Title string `yaml:"title,omitempty"`

	// URL is the main URL where the KEG can be found.
	URL string `yaml:"url,omitempty"`

	// Creator is the URL or identifier of the creator of the KEG.
	Creator string `yaml:"creator,omitempty"`

	// State indicates the current state of the KEG (e.g., living, archived).
	State string `yaml:"state,omitempty"`

	// Description describes the KEG content.
	Description string `yaml:"description"`

	// Indexes is a list of index entries that link to related files or nodes.
	Indexes []IndexEntry `yaml:"indexes,omitempty"`
	// contains filtered or unexported fields
}

SettingsV1 represents the initial version of the KEG settings specification.

type SettingsV2 added in v0.39.0

type SettingsV2 struct {
	// Kegv is the version of the specification.
	Kegv string `yaml:"kegv" json:"kegv"`

	// Updated indicates when the keg was last indexed.
	Updated string `yaml:"updated,omitempty" json:"updated,omitempty"`

	// Title is the title of the KEG worklog or project.
	Title string `yaml:"title,omitempty" json:"title,omitempty"`

	// URL is the main URL where the KEG can be found.
	URL string `yaml:"url,omitempty" json:"url,omitempty"`

	// Creator is the URL or identifier of the creator of the KEG.
	Creator string `yaml:"creator,omitempty" json:"creator,omitempty"`

	// State indicates the current state of the KEG (e.g., living, archived).
	State string `yaml:"state,omitempty" json:"state,omitempty"`

	// Description describes the KEG content.
	Description string `yaml:"description" json:"description"`

	// Instructions are KEG-level guidance shown to agents when orienting to
	// this keg.
	Instructions string `yaml:"instructions,omitempty" json:"instructions,omitempty"`

	// Links holds a list of LinkEntry objects representing related links or
	// references in the configuration.
	Links []LinkEntry `yaml:"links,omitempty" json:"links,omitempty"`

	// Indexes is a list of index entries that link to related files or nodes.
	Indexes []IndexEntry `yaml:"indexes,omitempty" json:"indexes,omitempty"`

	// ListFields are the field selectors a node listing shows by default, in
	// the vocabulary of ParseFieldSelector: a bare word names a metadata key
	// ("type", "subkind"), a leading dot names a statistics field (".omega"),
	// and "id", "title", and "tags" are reserved. One setting drives both the
	// default `tap list` format and the columns of the hosted node list, so a
	// keg presents the same shape everywhere. Empty means the built-in default.
	ListFields []string `yaml:"listFields,omitempty" json:"list_fields,omitempty"`

	// Timezone is the IANA timezone for resolving ambiguous timestamps
	// within this keg (e.g. "America/Chicago"). Defaults to "UTC".
	Timezone string `yaml:"timezone,omitempty" json:"timezone,omitempty"`

	// Snapshots controls automatic snapshot behavior for this keg.
	Snapshots *SnapshotSettings `yaml:"snapshots,omitempty" json:"snapshots,omitempty"`

	// SchemaPolicy controls actor validation modes. Strict adds explicit schema
	// selection to live nonzero-node writes whose resolved mode is block.
	SchemaPolicy *SchemaPolicy `yaml:"schemaPolicy,omitempty" json:"schemaPolicy,omitempty"`
	// contains filtered or unexported fields
}

SettingsV2 represents the second (current) version of the KEG settings specification. It extends V1 with additional fields such as Links.

func (*SettingsV2) MaterializeSystemIndexes added in v0.39.0

func (kc *SettingsV2) MaterializeSystemIndexes()

MaterializeSystemIndexes ensures required system indexes are present in the runtime settings view and removes any legacy persisted declarations of those indexes.

func (*SettingsV2) UnmarshalJSON added in v0.41.0

func (s *SettingsV2) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts legacy summary only when description is absent.

func (*SettingsV2) UnmarshalYAML added in v0.41.0

func (s *SettingsV2) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML accepts legacy summary only when description is absent.

type SettingsWriteOptions added in v0.39.0

type SettingsWriteOptions struct {
	ExpectedHash string `json:"expected_hash,omitempty"`
}

type Snapshot

type Snapshot struct {
	ID        RevisionID
	Node      NodeId
	Parent    RevisionID // 0 for root
	CreatedAt time.Time
	Message   string

	// Integrity + retrieval hints
	ContentHash  string
	MetaHash     string
	StatsHash    string
	IsCheckpoint bool // full content stored instead of patch
}

type SnapshotContentKind

type SnapshotContentKind string

SnapshotContentKind describes how snapshot content bytes are stored.

const (
	// SnapshotContentKindPatch stores content as a diff from a base revision.
	SnapshotContentKindPatch SnapshotContentKind = "patch"
	// SnapshotContentKindFull stores full reconstructed content bytes.
	SnapshotContentKindFull SnapshotContentKind = "full"
)

type SnapshotContentWrite

type SnapshotContentWrite struct {
	Kind SnapshotContentKind
	Base RevisionID

	// Algorithm identifies the patch format, for example "xdiff-v1".
	Algorithm string
	Data      []byte

	// Hash is the digest of fully materialized content at this revision.
	Hash string
}

SnapshotContentWrite describes content payload for a new snapshot revision.

type SnapshotPolicyResult added in v0.28.0

type SnapshotPolicyResult struct {
	Mode      string
	IdleAfter time.Duration
	Scanned   int
	Created   []Snapshot
}

SnapshotPolicyResult summarizes one automatic snapshot-policy scan.

func (SnapshotPolicyResult) CreatedCount added in v0.28.0

func (r SnapshotPolicyResult) CreatedCount() int

CreatedCount returns the number of snapshots appended during the scan.

type SnapshotReadOptions

type SnapshotReadOptions struct {
	// ResolveContent reconstructs full content bytes for the selected revision.
	ResolveContent bool
}

SnapshotReadOptions configures how snapshots are loaded.

type SnapshotSettings added in v0.39.0

type SnapshotSettings struct {
	// Mode controls whether the hub should create idle snapshots automatically.
	// Supported values are "auto" and "off".
	Mode string `yaml:"mode,omitempty" json:"mode,omitempty"`

	// IdleAfter is a Go-style duration string. Nodes become eligible for auto
	// snapshots only after their last edit has been idle for at least this long.
	IdleAfter string `yaml:"idleAfter,omitempty" json:"idleAfter,omitempty"`
}

SnapshotSettings holds per-keg automatic snapshot policy settings.

func DefaultSnapshotSettings added in v0.39.0

func DefaultSnapshotSettings() *SnapshotSettings

DefaultSnapshotSettings returns the default automatic snapshot settings.

type SnapshotWrite

type SnapshotWrite struct {
	ExpectedParent RevisionID
	Message        string
	// CreatedAt preserves an externally supplied revision timestamp. When zero,
	// repositories stamp the snapshot with the current runtime clock time.
	CreatedAt time.Time

	Meta  []byte
	Stats *NodeStats

	Content SnapshotContentWrite
}

SnapshotWrite describes append parameters for a new node snapshot.

type TagIndex

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

TagIndex is an in-memory index mapping a normalized tag string to the list of nodes that declare that tag.

The index format (used by ParseTagIndex and Data) is line-oriented. Each line represents a tag and its node list in the form:

<tag>\t<node1> <node2> ...\n

Where <nodeN> is the node.Path() string representation (for example "42" or "42-0001"). Parsers should tolerate empty input and skip empty lines. When serializing, the implementation should produce stable output by sorting tag keys and de-duplicating and sorting node lists.

Note: TagIndex does not perform internal synchronization. Callers that need concurrent access should guard the index with a mutex.

func ParseTagIndex

func ParseTagIndex(ctx context.Context, data []byte) (TagIndex, error)

ParseTagIndex parses the serialized tag index bytes into a TagIndex.

Expected input is zero or more lines separated by newline. Each non-empty line must contain a tag, a tab, and a space-separated list of node ids. Invalid or malformed lines should be handled gracefully by ignoring the offending line and continuing parsing. An empty input yields an empty TagIndex and no error.

func (*TagIndex) Add

func (idx *TagIndex) Add(ctx context.Context, data *NodeData) error

Add incorporates the node into the index for each tag present on the node.

Behavior notes: - If idx is nil this is a no-op. - The method should ensure idx.data is initialized when first used. - Duplicate entries for a given tag should be avoided (idempotent add). - The node should be added using node.Path() as the identifier.

func (*TagIndex) Data

func (idx *TagIndex) Data(ctx context.Context) ([]byte, error)

Data serializes the TagIndex to the canonical byte representation described for ParseTagIndex.

Serialization requirements:

  • Tags (map keys) must be emitted in a stable, deterministic order. When a tag token can be parsed as a NodeId id it may be ordered numerically; otherwise fall back to lexicographic ordering.
  • NodeId lists for each tag must be de-duplicated and sorted by numeric id then by code (the same ordering ParseNode/NodeId.Compare implies).
  • Lines must use a single tab between tag and the node list, and a single space between node ids. Each line must be terminated with a newline.
  • If the index is empty return an empty byte slice and no error.

func (*TagIndex) Rm

func (idx *TagIndex) Rm(ctx context.Context, node NodeId) error

Rm removes the node from all tag lists in the index.

Behavior notes:

  • If idx is nil this is a no-op.
  • If a tag has no remaining nodes after removal it should be removed from the map to avoid emitting empty tag lines when serialized.

type Target added in v0.20.0

type Target struct {
	// Hub is an optional explicit hub pin for a keg reference. It is normally
	// empty: the hub is resolved from the Namespace via the tapper settings's
	// namespaces map. The canonical keg reference does not carry a hub.
	Hub string `yaml:"hub,omitempty"`

	// HubURL is the resolved base URL for the hub (for example
	// "https://atlas.foldwise.ai"). It is derived at resolution time from the
	// tapper settings's hubs map and is intentionally not serialized. A keg
	// reference that reaches NewKegFromTarget without it was never resolved
	// against a hub and is rejected.
	HubURL string `yaml:"-"`

	// Url is the URL for a direct HTTP(S) target.
	Url string `yaml:"url,omitempty"`

	// Namespace is the namespace owner for hub targets. The "@" sigil is
	// implied; do not store it. A user's default namespace shares their
	// username; organizations and other namespace types use the same field.
	Namespace string `yaml:"namespace,omitempty"`

	// KegName is the keg's name within the Namespace.
	KegName string `yaml:"kegName,omitempty"`

	// BasicAuthUser is the HTTP basic-auth username for URL targets.
	// Distinct from Namespace, which addresses a hub-scheme namespace owner.
	BasicAuthUser string `yaml:"basicAuthUser,omitempty"`

	Password string `yaml:"password,omitempty"`
	Token    string `yaml:"token,omitempty"`
	TokenEnv string `yaml:"tokenEnv,omitempty"`

	// Readonly specifies that the target is read only.
	Readonly bool `yaml:"readonly,omitempty"`
}

Target describes a resolved KEG repository target.

The Target type is the canonical, minimal shape used by tooling. Valid input forms that map into Target include:

- API or HTTP targets:

  • Full URL scalars (http:// or https://).
  • Mapping form with "url" and optional user/password/token/tokenEnv. Query params like "readonly", "token", and "token-env" are honored.

- Keg reference shorthand and structured form:

  • Compact scalar shorthand "keg:@namespace/keg" (canonical; namespace optional as "keg:keg"). The hub is resolved from the namespace, never encoded. "keg:/@namespace/keg" is accepted as an input variant.
  • Mapping form with "namespace" and "kegName" (and an optional "hub" pin).

Fields:

  • Hub: hub name when using an API style target.
  • Url: canonical URL when provided or parsed from a scalar.
  • Namespace/KegName: structured hub pieces used to compose API paths. Namespace is the owner — a user's default namespace shares their username, but organizations and other namespace types are also valid. The "@" sigil is implied and not stored.
  • BasicAuthUser: HTTP basic-auth username for URL targets. Distinct from Namespace, which addresses a hub-scheme namespace owner.
  • Password/Token/TokenEnv: credential hints. TokenEnv is preferred for production usage.
  • Readonly: when true the target was requested read only.

func NewApi added in v0.20.0

func NewApi(hub string, namespace, kegName string, opts ...TargetOption) Target

NewApi constructs a Target representing a keg API endpoint. namespace is the namespace owner (no "@" sigil); kegName is the keg's name within it.

func Parse added in v0.20.0

func Parse(raw string) (*Target, error)

Parse parses a user-supplied target scalar into a Target.

Accepted input forms:

  • Canonical keg reference "keg:@namespace/keg" (namespace optional as "keg:keg"); "keg:/@namespace/keg" is an accepted variant. The leading "@" sigil marks the namespace and is stripped on parse so the stored namespace never carries it; Path() and String() re-apply it. The hub is resolved from the namespace, never encoded in the reference.
  • HTTP/HTTPS URL scalars.

Filesystem paths, file:// URLs, and every other scheme are unsupported. The function is permissive with common HTTP and keg-reference variants (extra whitespace and duplicate slashes). It returns an error for empty or malformed references.

func (*Target) Expand added in v0.20.0

func (k *Target) Expand(env toolkit.Env) error

Expand replaces environment variables in target fields.

func (*Target) Host added in v0.20.0

func (kt *Target) Host() string

Host returns the hostname portion for HTTP/HTTPS targets.

func (*Target) Path added in v0.20.0

func (kt *Target) Path() string

func (*Target) Port added in v0.20.0

func (kt *Target) Port() string

func (*Target) Scheme added in v0.20.0

func (kt *Target) Scheme() string

Scheme reports the inferred scheme for this Target value. A keg reference (identified by a Namespace owner, KegName, or explicit Hub pin) implies the keg scheme. Otherwise we classify the URL.

func (*Target) String added in v0.20.0

func (kt *Target) String() string

String returns a human-friendly representation of the target. A keg reference renders in the canonical "keg:@namespace/kegName" form (namespace omitted as "keg:kegName" when unset). The hub is NOT part of the reference — it is resolved from the namespace via settings — so the scheme is always the real "keg" scheme, never a hub name. HTTP targets return the canonical URL.

func (*Target) UnmarshalYAML added in v0.20.0

func (k *Target) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML accepts either a remote URL or keg-reference scalar, or a mapping node that decodes into the full Target struct. Mapping form may include structured hub/namespace/keg fields.

Filesystem fields and file:// scalars are rejected.

type TargetOption added in v0.20.0

type TargetOption = func(t *Target)

func WithHubURL added in v0.23.0

func WithHubURL(hubURL string) TargetOption

WithHubURL sets the resolved hub base URL on a hub Target. The tapper layer uses this to push the URL looked up from the configured hubs map down into the Target so NewKegFromTarget composes the API endpoint against it.

func WithReadonly added in v0.20.0

func WithReadonly() TargetOption

type TokenResolver added in v0.20.0

type TokenResolver interface {
	// ResolveToken returns the bearer token for target, or an empty string when
	// no credential is available.
	ResolveToken(target *Target) string
}

TokenResolver supplies bearer tokens for remote targets when no token is configured inline on the target or via TokenEnv. Implementations typically look up credentials in a persistent auth store keyed by the target's hub root. A resolver returns "" when no credential is available; a nil TokenResolver is legal and means "no fallback".

type TransientError

type TransientError struct {
	Cause error
}

TransientError marks a transient (retryable) failure, e.g. network timeout, DB deadlock. It implements both Temporary() and Retryable().

func (*TransientError) Error

func (e *TransientError) Error() string

func (*TransientError) Retryable

func (e *TransientError) Retryable() bool

func (*TransientError) Temporary

func (e *TransientError) Temporary() bool

func (*TransientError) Unwrap

func (e *TransientError) Unwrap() error

type ValidateNodesOptions added in v0.33.0

type ValidateNodesOptions struct {
	NodeIDs []NodeId `json:"node_ids,omitempty"`
}

type ValidationActor added in v0.25.0

type ValidationActor string
const (
	ValidationActorHuman   ValidationActor = "human"
	ValidationActorAgent   ValidationActor = "agent"
	ValidationActorAPI     ValidationActor = "api"
	ValidationActorImport  ValidationActor = "import"
	ValidationActorRestore ValidationActor = "restore"
)

func ValidationActorFromContext added in v0.25.0

func ValidationActorFromContext(ctx context.Context) ValidationActor

type ValidationIssue added in v0.25.0

type ValidationIssue struct {
	Level   string `json:"level" yaml:"level"`
	Field   string `json:"field,omitempty" yaml:"field,omitempty"`
	Message string `json:"message" yaml:"message"`
}

type ValidationMode added in v0.25.0

type ValidationMode string
const (
	ValidationModeAuto  ValidationMode = ""
	ValidationModeOff   ValidationMode = "off"
	ValidationModeWarn  ValidationMode = "warn"
	ValidationModeBlock ValidationMode = "block"
)

func ResolveValidationMode added in v0.38.0

func ResolveValidationMode(ctx context.Context, policy *SchemaPolicy) ValidationMode

ResolveValidationMode applies a request context override, then the selected actor's policy, then Tapper's actor defaults. Callers that need to explain write requirements before performing a write should use this resolver so their UI and Tapper's authoritative save path cannot drift apart.

func ValidationModeFromContext added in v0.25.0

func ValidationModeFromContext(ctx context.Context) ValidationMode

Jump to

Keyboard shortcuts

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