core

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2026 License: MIT Imports: 29 Imported by: 0

Documentation

Index

Constants

View Source
const (
	MaxRetryBackoff = 10 * time.Minute
	MaxMinInterval  = 10 * time.Minute
)

MaxRetryBackoff bounds one wait between attempts, and MaxMinInterval bounds the pacing gap. Both sit under the collection timeout's own ceiling for the same reason it has one.

View Source
const (
	FieldID    = "id"
	FieldTime  = "time"
	FieldTitle = "title"
	FieldURL   = "url"
)
View Source
const (
	MaxFieldDescriptionLength = 512
	MaxFieldExamples          = 8
	MaxFieldExampleLength     = 512
)
View Source
const (
	MaxFieldNameLength = 64
	MaxFields          = 64
	MaxPathsPerField   = 32
)
View Source
const (
	MaxConfigBytes         int64 = 1 << 20
	MaxControlFileBytes    int64 = 1 << 20
	MaxSourceDocumentBytes int64 = 64 << 20
	MaxLocalInputBytes     int64 = 64 << 20
	MaxNarrativeBytes      int64 = 4 << 20
)
View Source
const (
	// MaxCLIOutputBytes bounds each captured subprocess stream.
	MaxCLIOutputBytes = 64 << 20
	// DeclaredCommandDirectory is the neutral cwd for every source command. Base-controlled
	// executables and support files are reached only through the trust-digested bin/ PATH.
	DeclaredCommandDirectory = "/"
	// DeclaredCommandEnvironmentPolicy is both the trust disclosure and the digest input for
	// the fixed child-environment boundary implemented below.
	DeclaredCommandEnvironmentPolicy = "provider environment without runtime startup loaders or base-resolving home/config roots"
)
View Source
const (
	BaseDirMode  os.FileMode = 0o700
	BaseFileMode os.FileMode = 0o600
)

Base permissions. A base is created owner-only whether or not it is versioned: git stores no directory mode and tracks only the executable bit, so versioning is never a reason to relax them. What versioning changes is that fkf stops repairing modes inside a tree that git, an editor, and a teammate's clone also own.

View Source
const (
	GraphFile         = "graph.tsv"
	GraphMetaFile     = "graph.meta.json"
	TaskTraceFile     = "TASKS.md"
	BaseAgentsFile    = "AGENTS.md"
	BaseSkillsDir     = ".agents/skills"
	BaseBinDir        = "bin"
	ConfigFileName    = "fkf.yaml"
	LocalConfigName   = "fkf.local.yaml"
	MarkdownExtension = ".md"
)

Generated graph filenames live at the base root. They remain outside every typed layer so listings cannot confuse rebuildable cache data with collected or authored content.

View Source
const BaseEnvVar = "FKF_BASE"

BaseEnvVar is the only process-wide input fkf reads. There is deliberately no global configuration file: a base carries its own definition, so the environment only has to answer "which base", never "configured how".

View Source
const ConfigVersion = 1

ConfigVersion is the fkf.yaml contract frozen for the v1 line. It is deliberately separate from the binary version and from the stored-document marker: one says how to read the base, one says which program is running, and one says how a collected file was encoded.

View Source
const MaxBaseNameLength = 63

MaxBaseNameLength bounds the MCP server title and every fkf:// resource authority. Keeping it at a DNS-label-sized 63 bytes makes the generated connection instructions bounded while leaving ordinary personal and team names comfortably readable.

View Source
const MaxFreshnessAgeHours = 10 * 365 * 24

MaxFreshnessAgeHours is ten years. Larger freshness windows are operationally equivalent to disabling refresh/staleness checks, while their conversion to time.Duration can wrap and invert the comparison. A finite shared bound keeps configuration and CLI arithmetic honest.

View Source
const MaxRetryAttempts = 5

MaxRetryAttempts bounds the declared attempt count. A source that may run ten times is not declaring back-pressure, it is declaring a loop.

View Source
const MaxSourceNameLength = 255 - len(".json")

MaxSourceNameLength keeps `<source>.json` within the 255-byte filename-component limit shared by the supported Linux and macOS filesystems. Source names are ASCII by grammar, so the byte count is also the schema's character count.

View Source
const MaxSyncConcurrency = 4

MaxSyncConcurrency bounds simultaneous provider processes. Each process owns separately bounded stdout and stderr buffers, so a small ceiling contains aggregate memory use.

View Source
const SchemaURL = "https://fmind.github.io/fkf/fkf.schema.json"

SchemaURL is where the generated schema is published, and what an editor's `# yaml-language-server` comment points at. `fkf config schema` prints the same bytes, so an offline editor never has to fetch it.

Variables

View Source
var ErrBaseBusy = errors.New("base has an active writer")

ErrBaseBusy reports that another fkf process is already mutating the same physical base. Readers never acquire this lock; it serializes only whole CLI write workflows.

View Source
var ErrCLIOutputTooLarge = errors.New("CLI output exceeds size limit")

ErrCLIOutputTooLarge reports a subprocess stream that exceeded the in-memory bound.

View Source
var ErrConfig = errors.New("invalid configuration")

ErrConfig marks every load and validation failure, so the CLI can map the whole class to exit code 2 without inspecting messages.

View Source
var ErrFileTooLarge = errors.New("file exceeds size limit")
View Source
var ErrNoBase = errors.New("no fkf base found")

ErrNoBase reports that none of the three discovery rules found a base.

View Source
var ErrNotAddressable = errors.New("path is not addressable in a base")

ErrNotAddressable reports a base-relative path outside the published URI grammar. The CLI maps it to exit code 2: naming a file a base does not address is a usage error, not a failure.

View Source
var ErrPathEscapes = errors.New("path escapes the base")

ErrPathEscapes reports a base-relative path that leaves the base root. Escaping paths are rejected rather than clamped: silently rewriting `../../etc/passwd` into `etc/passwd` turns a hostile link into a plausible one, and a reader cannot tell the difference.

View Source
var ErrUnsafePath = errors.New("unsafe filesystem path")

ErrUnsafePath identifies a symlink, reparse-like entry, or non-directory component in a path that fkf may create beneath. Callers preflight all targets before writing so a later failure cannot leave a partially initialized store.

View Source
var ErrUntrusted = errors.New("base is not trusted on this machine")

ErrUntrusted reports a base whose configuration has not been trusted on this machine, or whose configuration changed since it was. The CLI maps it to exit code 3.

Layers is the canonical ordered layer list, consumed by path resolution, scaffolding, configuration validation, and every `list` command.

View Source
var RunPlaceholders = []string{"date", "next_date", "start", "end", "base", "home"}

RunPlaceholders are the only substitutions fkf makes into `run:` arguments. Every one is a value fkf itself computes; collected data never chooses an argument or executable.

View Source
var Version = resolveVersion(version, debug.ReadBuildInfo)

Version is the fkf build version, surfaced via `fkf --version`, the MCP implementation record, and every stored document's provenance, so a result can always be traced to the build that produced it.

It is resolved once from three sources, most authoritative first: a linker-injected tag, the module version Go records for `go install`, and finally a VCS-stamped development string. Resolving it rather than hardcoding a constant is what stops `fkf --version` from claiming 0.1.0 forever.

Functions

func CleanRelative

func CleanRelative(relative string) (string, error)

CleanRelative normalizes one base-relative slash path and refuses anything that could address a file outside the base. Backslashes and NUL are rejected outright rather than normalized, because both mean the caller is not describing a base-relative URI.

func ConfigDigest

func ConfigDigest(ctx context.Context, root string) (string, error)

ConfigDigest reduces the canonical execution plan to one hash. The plan is loaded and merged first, so comments, key ordering, and retrieval-only semantics cannot re-arm trust while a local overlay that changes what runs still does.

bin/ belongs in the plan because it is committed with the base and sits first on the PATH every declared command gets. Without it, a base whose `run: git log …` had been read and approved could ship or later receive a bin/git, and `fkf sync` would run it with no prompt: the digest would still match, because only the YAML had been hashed. For a shared team base that is a `git pull` away from code execution on every teammate's machine.

func ConfigSchema

func ConfigSchema() map[string]any

ConfigSchema returns the JSON Schema for fkf.yaml. It is written by hand rather than reflected from the structs on purpose: the interesting constraints — the placeholder set, the jq subset, `body` needing `{{id}}` — are not expressible as Go tags, and a schema that silently omits them would tell an editor a broken file is fine.

func ConfigureLogging

func ConfigureLogging()

ConfigureLogging installs a text slog handler on stderr at info. There is no configurable level: fkf's own logging is one line per MCP call and a diagnostic when a declared command fails, and a level key would be one more thing in a base's configuration that is about fkf rather than about the base.

func EncodeConfigSchema

func EncodeConfigSchema() ([]byte, error)

EncodeConfigSchema renders the schema exactly as it is published: two-space indented JSON with a trailing newline, which is what dprint and the docs site expect.

func ExpandHome

func ExpandHome(value string) string

ExpandHome resolves a leading `~` against the current user's home directory. Every path fkf accepts from configuration or the command line goes through it, so `--base ~/brain` works in a shell that does not expand tildes (an exec'd argv, an MCP launch line).

func IsWellKnownField

func IsWellKnownField(name string) bool

IsWellKnownField reports whether fkf gives this suggested name built-in semantics.

func LayerNames

func LayerNames() string

LayerNames renders the canonical layer list for a diagnostic.

func LookPathIn

func LookPathIn(name, pathList string) (string, bool)

LookPathIn resolves a command name against an explicit PATH list, returning the absolute path and whether it was found. An empty pathList means the process PATH, which is what the few callers with no base in hand — resolving the shell itself — need.

func NewCommandFailure

func NewCommandFailure(cause error, stderr string) error

NewCommandFailure wraps a runner adapter's failed process without rendering provider stderr. ExecRunner uses it below; the exported constructor also lets another hermetic Runner preserve the same retry and privacy contract without manufacturing a leaky error string.

func OpenRegularFile

func OpenRegularFile(path string) (*os.File, error)

OpenRegularFile opens one regular, non-symlink leaf. Lstat rejects a FIFO or device already present before os.Open can block on it, while fstat and SameFile bind subsequent reads to the inspected inode. As with the store's other path checks, a hostile local writer can still swap the path between syscalls; base writers are expected to use fkf's atomic replacement seam.

func ReadFileLimit

func ReadFileLimit(path string, limit int64) ([]byte, error)

ReadFileLimit reads one regular, non-symlink file while enforcing a hard byte bound. The limit is checked both from metadata and while reading so a concurrently growing file cannot force an unbounded allocation.

func ReadFileLimitContext

func ReadFileLimitContext(ctx context.Context, path string, limit int64) ([]byte, error)

ReadFileLimitContext is ReadFileLimit with cooperative cancellation while bytes are read. Regular files do not block indefinitely, but a maximum-sized document can still be large enough that an agent cancellation must stop the read before the next audit stage begins.

func RequireTrust

func RequireTrust(ctx context.Context, root string) error

RequireTrust is the gate every command that executes a declared command calls first. It names the remedy, because a refusal the user cannot act on is just an outage.

func RequireTrustConfig

func RequireTrustConfig(ctx context.Context, config *Config) error

RequireTrustConfig gates the exact decoded execution plan a caller is about to execute.

func ResolveAbsolutePath

func ResolveAbsolutePath(value string) (string, error)

ResolveAbsolutePath expands the one supported home-relative spelling and anchors a relative path to the caller's current directory. It deliberately does not evaluate symlinks: the chosen root spelling is part of trust identity, while confinement checks inspect symlinks separately at the boundary where they matter.

func ResolveExecutable

func ResolveExecutable(name, pathList string) (string, error)

ResolveExecutable is LookPathIn with the diagnostic a caller about to exec needs.

func RunCLI

func RunCLI(ctx context.Context, cmd []string, cwd string, timeout time.Duration) (string, error)

RunCLI executes a CLI command with a given timeout and working directory. The parent context is honored, so an interrupt/SIGTERM cancels in-flight subprocesses instead of waiting out the per-call timeout.

func RunCLIBounded

func RunCLIBounded(ctx context.Context, cmd []string, cwd, stdin string, timeout time.Duration, maxOutputBytes int) (string, error)

RunCLIBounded executes a CLI while applying the caller's tighter limit independently to stdout and stderr.

func RunCLIStdin

func RunCLIStdin(ctx context.Context, cmd []string, cwd, stdin string, timeout time.Duration) (string, error)

RunCLIStdin executes a CLI command feeding it one in-memory document. It exists for the single case that needs it — handing a stored document to the `jq` a `?jq=` URI names — so that expression stays one argv element and never reaches a shell.

func SanitizePathList

func SanitizePathList(pathList, forbiddenRoot string) string

SanitizePathList returns only absolute PATH entries that cannot be supplied by mutable content below forbiddenRoot. Empty and relative entries are working-directory aliases, so a shell given one can execute a different file after Cmd.Dir changes than the resolver reviewed before exec. An inherited absolute entry inside the base has the same flaw: a pull can replace its executable without changing the digest, except for the canonical base/bin directory which callers prepend separately after filtering the inherited list.

Inspection fails closed. A missing or unreadable inherited directory cannot resolve a command now, and retaining it would let it become executable later without another config or trust change.

func ScalarString

func ScalarString(value any) (string, bool)

ScalarString renders a decoded JSON scalar. Objects and arrays are refused rather than stringified: a field path that lands on an object is a configuration mistake, and `map[…]` in a URI would hide it.

func StateDir

func StateDir() string

StateDir is where machine-local fkf state lives. It follows XDG so a test can redirect it with one variable, which is what keeps the suite hermetic.

func SyncDirectory

func SyncDirectory(dir string) error

SyncDirectory persists a directory-entry change where the platform supports directory fsync. Windows does not expose the same primitive through os.File.Sync.

func ValidBodyValue

func ValidBodyValue(value string) bool

ValidBodyValue reports whether collected data is safe to pass as one opaque argv value. Shell punctuation is ordinary data because body commands never use a shell. The boundary rejects options, invalid UTF-8, controls and invisible format characters instead: those can change CLI interpretation or make reviewed text differ from the bytes that execute.

func ValidateDate

func ValidateDate(value string) error

ValidateDate checks the YYYY-MM-DD form every dated command and layer path uses.

func ValidateDirectoryConfinement

func ValidateDirectoryConfinement(path string) error

ValidateDirectoryConfinement additionally requires the leaf to be a real directory when it already exists.

func ValidateEntityScheme

func ValidateEntityScheme(value string) error

ValidateEntityScheme enforces the open-but-non-reserved entity namespace shared by the stored relation boundary and the URI parser. `file` and `external` are internal URI kinds; the protocol names remain reserved for external addresses rather than entity aliases.

func ValidateFieldMap

func ValidateFieldMap(fields FieldMap, event bool) error

ValidateFieldMap enforces the open map's small structural contract. Only identity and event time are mandatory; the other well-known names and every custom name are optional.

func ValidateFieldSchema

func ValidateFieldSchema(schema FieldSchema) error

ValidateFieldSchema enforces the small semantic contract shared by config, documents, graph, retrieval, and authored relations.

func ValidatePathConfinement

func ValidatePathConfinement(path string) error

ValidatePathConfinement accepts an operating-system-managed root alias, then rejects every deeper existing symlink and every non-directory intermediate component. A missing suffix is valid because MkdirAll may create it after all targets have passed the same preflight.

func ValidateRelationValue

func ValidateRelationValue(value string) error

ValidateRelationValue checks the canonical relation boundary available to core and sources. File references receive their existence and child-addressability checks when the graph is built, but their complete lexical grammar is enforced here so a successful collection can never store a relation the graph parser will later refuse.

func ValidateSourceName

func ValidateSourceName(name string) error

ValidateSourceName keeps every configured and stored source usable as the same flat JSON filename and URI segment. Stored evidence crosses this boundary again because it may have been hand-edited after collection.

func ValidateWithinRoot

func ValidateWithinRoot(root, absolute string) error

ValidateWithinRoot rejects a symlink on any component strictly between a base's root and the leaf it is about to open. It deliberately does not inspect the root itself or anything above it: a base legitimately lives behind a symlinked path (`~/brain -> /data/brain`, or macOS's /tmp), and the owner chose that. What must not happen is a path *inside* the base — committed to git and carried by every clone — pointing back out of it.

The check races a symlink swapped between here and the open that follows. Closing that needs every caller to hold an *os.Root, which is a larger change than this seam; the race requires write access to the base, whereas the escape this refuses requires only a `git pull`.

func WithCommandEnvironment

func WithCommandEnvironment(ctx context.Context, values map[string]string) (context.Context, error)

WithCommandEnvironment binds explicit immutable subprocess configuration to one call tree. It avoids process-global account switching while keeping provider transports independently testable. Values are never included in command diagnostics.

func WithCommandEnvironmentForRoot

func WithCommandEnvironmentForRoot(
	ctx context.Context, values map[string]string, forbiddenRoot string,
) (context.Context, error)

WithCommandEnvironmentForRoot also names repository content that runtime startup state must never select implicitly. Provider commands run from a neutral directory, so this separate root keeps absolute HOME/XDG spellings inside the base out of their child environment.

func WriteDataToJSON

func WriteDataToJSON(data any, path string) error

WriteDataToJSON writes any to file (indent 2) or stdout if path is empty. Files are written atomically (temp + rename) so interrupted runs and concurrent readers never observe a torn file, with owner-only permissions (0700/0600) because the workspace concentrates personal data (emails, chat, shell history) on disk.

func WriteFileAtomicMode

func WriteFileAtomicMode(path string, bytes []byte, mode os.FileMode) error

WriteFileAtomicMode atomically replaces one explicitly named file with the requested mode, including file-data and containing-directory synchronization.

Types

type BaseOrigin

type BaseOrigin string

BaseOrigin records which rule selected the base, so `fkf config` and every "no base" diagnostic can say where the answer came from rather than leaving the user to guess.

const (
	BaseFromFlag        BaseOrigin = "flag"
	BaseFromEnvironment BaseOrigin = "environment"
	BaseFromDiscovery   BaseOrigin = "discovery"
)

func DiscoverBase

func DiscoverBase(explicit string) (string, BaseOrigin, error)

DiscoverBase resolves the base root from an explicit path, then the environment, then by walking up from the working directory to the nearest directory holding fkf.yaml — the way git finds its repository. A base is never created implicitly, so a miss is an error with all three rules named rather than a silent fallback to the working directory.

type BinScript

type BinScript struct {
	// Name is the entry's path relative to bin/, so a helper under bin/lib/ is named
	// "lib/impl.sh" and stays distinct from a sibling of the same base name.
	Name string `json:"name"`
	// Kind is "script" for a regular file or the filesystem mode type for another accepted
	// entry. Symlinks are refused before a BinScript can be returned.
	Kind   string `json:"kind"`
	Digest string `json:"digest"`
	// Target is empty for every accepted entry; links are rejected as unsafe.
	Target string `json:"target,omitempty"`
	// Executable reports the bit that decides whether PATH lookup will pick this entry up.
	Executable bool `json:"executable"`
}

BinScript is one accepted entry of a base's bin/, reduced to what a reviewer has to agree to: its name, kind, executable state, and content digest when it is a regular file.

func BinScripts

func BinScripts(ctx context.Context, root string) ([]BinScript, error)

BinScripts lists a base's bin/ in name order, walking it to the bottom. An absent or empty bin/ is not an error: most bases have none, and "nothing to review" is a valid answer for the trust listing.

The walk is recursive because a one-level listing left `bin/lib/impl.sh` outside the digest entirely: a reviewer approves `bin/helper` once, and every later edit to the file it sources is trusted silently. Every entry kind is recorded, so a directory, FIFO, or device under bin/ contributes to the hash instead of vanishing from it.

type Cardinality

type Cardinality string

Cardinality is the number of scalar values one declared field may project from one record. It is intentionally smaller than JSON Schema: provider reshaping belongs in run:, while fkf only needs enough information to reject ambiguous identities and presentation values.

const (
	CardinalityOne      Cardinality = "one"
	CardinalityOptional Cardinality = "optional"
	CardinalityMany     Cardinality = "many"
)

func (Cardinality) Allows

func (c Cardinality) Allows(count int) bool

Allows reports whether a projected scalar count satisfies the declaration.

func (Cardinality) MaxOne

func (c Cardinality) MaxOne() bool

MaxOne reports whether a consumer may safely request one scalar from the field.

type Config

type Config struct {
	FKF     int                `json:"fkf"`
	Name    string             `json:"name"`
	Schema  FieldSchema        `json:"schema"`
	Layers  map[Layer]bool     `json:"layers"`
	Sources map[string]*Source `json:"sources"`
	Sync    SyncConfig         `json:"sync"`
	Bin     []string           `json:"bin,omitempty"`

	Path      string            `json:"path"`
	LocalPath string            `json:"local_path,omitempty"`
	Origins   map[string]string `json:"origins,omitempty"`
}

Config is one base's complete, resolved definition.

func LoadConfig

func LoadConfig(root string) (*Config, error)

LoadConfig reads a base's committed configuration and, when present, its machine-local overlay. Both are decoded strictly; the resolved value of every overridden key records which file it came from, so `fkf config` can show the merge rather than just its result.

func (*Config) EnabledSources

func (c *Config) EnabledSources() []*Source

EnabledSources returns the enabled sources in stable order.

func (*Config) SourceNames

func (c *Config) SourceNames() []string

SourceNames returns the declared source names in stable order, which is the order every report, `status` table, and `--dry-run` listing uses.

func (*Config) Store

func (c *Config) Store() Store

Store returns the layout this configuration describes.

type ErrLayerDisabled

type ErrLayerDisabled struct{ Layer Layer }

ErrLayerDisabled reports a request for a layer this base does not enable. It is a distinct error because "you turned it off" and "it is empty" are different answers, and a command that conflates them teaches the user to ignore both.

func (ErrLayerDisabled) Error

func (e ErrLayerDisabled) Error() string

type FieldDefinition

type FieldDefinition struct {
	Description string      `json:"description" yaml:"description"`
	Cardinality Cardinality `json:"cardinality" yaml:"cardinality"`
	Relation    bool        `json:"relation,omitempty" yaml:"relation,omitempty"`
	Examples    []string    `json:"examples,omitempty" yaml:"examples,omitempty"`
}

FieldDefinition gives one base-chosen semantic name a stable meaning across every source and authored page. Relation values are canonical fkf URIs produced by the source command; fkf validates and transcribes them but never guesses or coerces provider identities.

type FieldMap

type FieldMap map[string]FieldPaths

FieldMap is a source's open semantic projection. Keys are user-chosen; built-in consumers read only the well-known names while retrieval may index every additional value.

func (FieldMap) EvalField

func (m FieldMap) EvalField(name string, value any) ([]string, error)

EvalField projects every declared path while refusing non-scalar values. Collection uses it before accepting a record; the simpler read helpers can then rely on stored documents having crossed this typed boundary already.

func (FieldMap) EvalRelation

func (m FieldMap) EvalRelation(name string, value any) ([]string, error)

EvalRelation projects relation values without trimming or otherwise normalizing provider strings. Presentation fields may discard surrounding whitespace, but a relation is an identity: changing even one byte would make the stored graph mean something the provider did not emit.

func (FieldMap) EvalString

func (m FieldMap) EvalString(name string, value any) (string, bool)

EvalString projects the first scalar value selected by the field's paths, in declaration order. This gives a custom field a deterministic fallback when providers use more than one location for the same meaning.

func (FieldMap) EvalStrings

func (m FieldMap) EvalStrings(name string, value any) []string

EvalStrings flattens every declared path in order and removes duplicate scalar values.

func (FieldMap) Names

func (m FieldMap) Names() []string

Names returns a deterministic field order for retrieval and receipts.

func (FieldMap) Path

func (m FieldMap) Path(name string) FieldPath

Path returns the first path for a single-valued well-known field, or the zero path.

func (FieldMap) Paths

func (m FieldMap) Paths(name string) FieldPaths

Paths returns every path declared for one field.

type FieldPath

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

A field path is a deliberate subset of jq: `.key`, `.a.b`, `[n]`, `[]`, and `."odd key"`. Nothing else — no `select`, no pipes, no functions — because anything richer belongs in the source's helper where the real jq already is. Two properties follow, and both are the reason for the subset:

  • A path that fkf accepts is valid jq, so a user debugging a source can paste it straight into `jq` and see the same value.
  • Evaluation is one small total function over decoded JSON, with no expression language, no evaluator state, and nothing to sandbox.

func ParseFieldPath

func ParseFieldPath(raw string) (FieldPath, error)

ParseFieldPath compiles one path, naming the offending character when it falls outside the subset. A path is validated at configuration load, so a typo fails before any command runs.

func (FieldPath) Eval

func (p FieldPath) Eval(value any) []any

Eval returns every value the path selects. It is multi-valued because `[]` iterates, so one semantic field can combine provider arrays and scalar fallbacks without a special case. Null and missing are the same absence: neither is a graph destination or searchable value.

func (FieldPath) EvalString

func (p FieldPath) EvalString(value any) (string, bool)

EvalString returns the selected value only when the path projects exactly one scalar. Numbers are rendered without an exponent so a JSON id of 412 addresses the same record as the string "412" — the alternative is a URI fragment reading `4.12e+02`.

func (FieldPath) EvalStrings

func (p FieldPath) EvalStrings(value any) []string

EvalStrings returns every selected value rendered as a scalar string, in path order and deduplicated. Every field uses this same union before its declared cardinality is checked.

func (FieldPath) IsZero

func (p FieldPath) IsZero() bool

IsZero reports an undeclared path. An optional field is absent rather than empty, so a caller never has to distinguish "not declared" from "declared as nothing".

func (FieldPath) MarshalJSON

func (p FieldPath) MarshalJSON() ([]byte, error)

MarshalJSON stores the path verbatim, so a document's field map round-trips through re-collection and stays pasteable into jq.

func (FieldPath) String

func (p FieldPath) String() string

String returns the path exactly as it was written, which is what the stored document records and what a user pastes into jq.

func (*FieldPath) UnmarshalJSON

func (p *FieldPath) UnmarshalJSON(data []byte) error

UnmarshalJSON recompiles a stored path so a read never trusts an unvalidated string.

type FieldPaths

type FieldPaths []FieldPath

FieldPaths is one or more alternative projections for one semantic field. YAML and stored JSON keep the common one-path case as a string while accepting a list when several provider locations contribute values, so the open map stays readable without special-casing people.

func (FieldPaths) MarshalJSON

func (p FieldPaths) MarshalJSON() ([]byte, error)

MarshalJSON preserves the compact public shape: one path is a string, several are an array.

func (*FieldPaths) UnmarshalJSON

func (p *FieldPaths) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts the same scalar-or-list shape from stored documents.

func (*FieldPaths) UnmarshalYAML

func (p *FieldPaths) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML compiles every configured path at the trust boundary.

type FieldSchema

type FieldSchema map[string]FieldDefinition

FieldSchema is the base's open semantic dictionary. Keys are user-chosen field and relation names; sources only associate those names with provider paths.

func (FieldSchema) Names

func (s FieldSchema) Names() []string

Names returns the semantic names in deterministic order.

func (FieldSchema) Select

func (s FieldSchema) Select(fields FieldMap) FieldSchema

Select copies the definitions used by one source so a collected document remains self-describing even after fkf.yaml changes.

type Layer

type Layer string

Layer is one typed storage layer of a base.

const (
	// LayerEvents holds one collected document per source per completed local day.
	LayerEvents Layer = "events"
	// LayerIndex holds one point-in-time document per source: the things you have, as
	// opposed to the things that happened. It was called `index` while it also held the
	// derived caches, which made one word mean a layer, a source kind, two rebuildable files,
	// a wiki command, and a wiki page at once.
	LayerIndex Layer = "index"
	// LayerTasks holds task execution traces.
	LayerTasks Layer = "tasks"
	// LayerProjects holds status-bearing intent pages.
	LayerProjects Layer = "projects"
	// LayerWiki holds the flat OKF v0.2 knowledge bundle.
	LayerWiki Layer = "wiki"
)

func ParseLayer

func ParseLayer(value string) (Layer, error)

ParseLayer converts a user-supplied name into a known layer.

type OutputFormat

type OutputFormat string

OutputFormat is how a source's stdout is decoded.

const (
	// FormatJSON expects one JSON document: an array of records, or an object holding them
	// at `records:`. Empty stdout is an error, because a CLI that prints JSON prints `[]`.
	FormatJSON OutputFormat = "json"
	// FormatNDJSON expects one JSON value per line. Empty stdout is an empty day, because a
	// paginating CLI legitimately prints nothing when a day held nothing.
	FormatNDJSON OutputFormat = "ndjson"
)

type RetryPolicy

type RetryPolicy struct {
	Attempts int           `json:"attempts,omitempty"`
	Backoff  time.Duration `json:"backoff,omitempty"`
	// On is what may be retried, and it is required whenever Attempts exceeds one. Retrying
	// anything is how a source that is failing for a real reason turns into a source that
	// hammers a provider quietly: a declared list makes the reviewer say which failure is
	// transient. An entry is `exit:<n>` or a substring matched against the command's stderr.
	On []string `json:"on,omitempty"`
}

RetryPolicy is a source's declared back-pressure. Attempts counts the total, so 1 is the default "run it once" and needs no key.

func (RetryPolicy) IsZero

func (r RetryPolicy) IsZero() bool

IsZero lets the policy be omitted from JSON when nothing is declared.

type Source

type Source struct {
	Name     string        `json:"name"`
	Enabled  bool          `json:"enabled"`
	Layer    Layer         `json:"layer"`
	Run      []string      `json:"run"`
	Format   OutputFormat  `json:"format"`
	Records  FieldPath     `json:"records,omitzero"`
	Fields   FieldMap      `json:"fields,omitempty"`
	Schema   FieldSchema   `json:"-"`
	Body     []string      `json:"body,omitempty"`
	Requires []string      `json:"requires,omitempty"`
	Install  string        `json:"install,omitempty"`
	Timeout  time.Duration `json:"timeout,omitempty"`
	// Retry and MinInterval declare HOW fkf invokes the command, never what it is, which is
	// exactly the relationship `timeout:` already has to `run:`. They exist because the
	// alternative is shell: a rate-limited provider drove a real base to wrap `gh search` in a
	// hand-written script that sleeps until the limit resets, which moved a retry loop out of
	// tested Go and into the one surface a human has to re-read on every trust.
	Retry       RetryPolicy   `json:"retry,omitzero"`
	MinInterval time.Duration `json:"min_interval,omitempty"`
	// Window asks `fkf sync` to render this source's `run:` ONCE for the whole requested
	// range — {{start}}/{{end}} span every day being collected, not one — and bucket the
	// records it returns into one document per day by each record's declared `fields.time`.
	//
	// It exists because a day's worth of work is not what most sources actually cost: a local
	// script's fixed overhead (a filesystem scan, a process start) repeats on every day a
	// day-at-a-time sync asks for, and a paginating search API charges one call PER DAY
	// against a rate limit that counts calls, not days. `window:` collects what `run:`
	// already returns for a wider range in one call instead of many.
	Window bool `json:"window,omitempty"`
}

Source is one declared collection command.

func (Source) BodyFieldNames

func (s Source) BodyFieldNames() []string

BodyFieldNames returns the declared record fields that select body argv values. Static base and home placeholders keep their execution meaning even when the open field map uses the same name, so they are deliberately absent here.

func (Source) HasBody

func (s Source) HasBody() bool

HasBody reports whether this source can fetch one record's body on demand.

func (Source) RetryAttempts

func (s Source) RetryAttempts() int

RetryAttempts is the total number of runs this source allows, never fewer than one.

type Store

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

Store is the resolved, immutable layout of one base.

func NewStore

func NewStore(root string, enabled map[Layer]bool) Store

NewStore resolves a store from a root and the layer activation the base declares. An absent entry is disabled, so a hand-written configuration cannot silently enable a layer.

func (Store) BinDir

func (s Store) BinDir() string

BinDir is the base's own script directory, prepended to PATH for every declared command.

func (Store) ConfigPath

func (s Store) ConfigPath() string

ConfigPath is the committed configuration file of this base.

func (Store) Dir

func (s Store) Dir(layer Layer) (string, error)

Dir returns the resolved directory of an enabled layer.

func (Store) Enabled

func (s Store) Enabled(layer Layer) bool

Enabled reports whether a layer is activated.

func (Store) EnabledLayers

func (s Store) EnabledLayers() []Layer

EnabledLayers returns the activated layers in canonical order.

func (Store) EnforcePermissions

func (s Store) EnforcePermissions() bool

EnforcePermissions reports whether a permission audit may repair modes. A versioned base is only inspected.

func (Store) LayerOf

func (s Store) LayerOf(relative string) (Layer, bool)

LayerOf returns the layer a base-relative path belongs to, if any. It is how a URI is checked against layer activation without every caller re-deriving the first segment.

func (Store) LocalConfigPath

func (s Store) LocalConfigPath() string

LocalConfigPath is the gitignored machine-local overlay of this base.

func (Store) Relative

func (s Store) Relative(absolute string) (string, error)

Relative maps an absolute path inside the base back to its base-relative slash form. It is the inverse of Resolve and the only place a URI is minted from a filesystem walk.

func (Store) Resolve

func (s Store) Resolve(relative string) (string, error)

Resolve maps a base-relative slash path to an absolute path inside the base. Every read and write in fkf goes through it, so it is where the three ways out of a base are refused: a path that escapes lexically, a path addressing a disabled layer, and a path outside the addressable set.

The addressable set is the published URI grammar and nothing else. Confining to the root was not enough on its own: a base IS a git repository, so `.git/config` always exists beside the layers, and a user wiring up a source drops a `.env` there — both were readable through `fkf read`, and therefore through the ungated MCP `read` tool. `fkf.local.yaml` stays out deliberately; it is the machine-local overlay and no URI names it.

func (Store) Root

func (s Store) Root() string

Root returns the base directory.

func (Store) Versioned

func (s Store) Versioned() bool

Versioned reports whether the base has recognizable, real git working-tree metadata. Detecting this beats declaring it: a configuration key could claim a base is versioned when it is not, and the permission contract would then be wrong in the one direction that matters.

type SyncConfig

type SyncConfig struct {
	Days             int           `json:"days"`
	IndexMaxAgeHours int           `json:"index_max_age_hours"`
	Timeout          time.Duration `json:"timeout"`
	Concurrency      int           `json:"concurrency"`
}

SyncConfig tunes collection. Every key has a visible default in the file `fkf init` writes.

func DefaultSync

func DefaultSync() SyncConfig

DefaultSync is what an omitted `sync:` block means, and what `fkf init` writes verbatim.

type TrustChange

type TrustChange struct {
	Kind TrustChangeKind `json:"kind"`
	Item TrustItemKind   `json:"item"`
	Name string          `json:"name"`
}

TrustChange is one line of the re-trust review.

func DiffTrustItems

func DiffTrustItems(stored, current []TrustItem) []TrustChange

DiffTrustItems reports how the base's current items differ from what was trusted. Both sides are sorted by (kind, name) already, so the result is deterministic.

type TrustChangeKind

type TrustChangeKind string

TrustChangeKind is what happened to one item since it was trusted.

const (
	TrustAdded    TrustChangeKind = "added"
	TrustRemoved  TrustChangeKind = "removed"
	TrustModified TrustChangeKind = "modified"
	// TrustArmed is a script whose contents did not change but which gained the executable
	// bit. It is its own kind because it is the change a reviewer is most likely to wave
	// through as cosmetic and the one that actually decides whether PATH lookup runs it.
	TrustArmed TrustChangeKind = "armed"
	// TrustDisarmed is the same edit in reverse.
	TrustDisarmed TrustChangeKind = "disarmed"
)

type TrustItem

type TrustItem struct {
	Kind       TrustItemKind `json:"kind"`
	Name       string        `json:"name"`
	Digest     string        `json:"digest"`
	Executable bool          `json:"executable,omitempty"`
}

TrustItem is one reviewable unit of what a base can execute, reduced to a digest. Detail carries the one property that is worth naming in a diff on its own — a script's executable bit, because a mode-only pull is what arms a shadow binary and its content digest does not move when the mode does.

func TrustItems

func TrustItems(ctx context.Context, root string) ([]TrustItem, error)

TrustItems reduces everything a base can execute to reviewable canonical units: one base-wide policy, every declared source, and every entry under bin/. Invalid configuration cannot be trusted because there is no execution plan to review.

type TrustItemKind

type TrustItemKind string

TrustItemKind names what a trusted item is, so a diff can say "source" or "script" rather than showing a path and leaving the reader to infer which review it belonged to.

const (
	// TrustItemConfig is the resolved base-wide execution policy.
	TrustItemConfig TrustItemKind = "config"
	// TrustItemSource is one declared source's enabled state, commands, body fields, and policy.
	TrustItemSource TrustItemKind = "source"
	// TrustItemScript is one entry under <base>/bin.
	TrustItemScript TrustItemKind = "script"
)

type TrustRecord

type TrustRecord struct {
	Base      string      `json:"base"`
	Digest    string      `json:"digest"`
	TrustedAt string      `json:"trusted_at"`
	Items     []TrustItem `json:"items,omitempty"`
}

TrustRecord is what is stored per base. The aggregate digest is the gate; Items is what makes a re-trust reviewable.

One hash answers "did anything change" and nothing else, so the second time trust is asked for — after a `git pull` on a shared base, which is the moment the gate exists for — the only honest thing fkf could say was "the digest changed", and re-approval meant re-reading every source and every script to find the one line that moved. A review nobody re-reads is a review nobody performs.

Items is machine-local like the rest of the record, so a record written by an older build simply has none and the listing falls back to printing everything: the safe default, and no migration to write.

type TrustState

type TrustState struct {
	Base    string `json:"base"`
	Trusted bool   `json:"trusted"`
	Digest  string `json:"digest"`
	Stored  string `json:"stored_digest,omitempty"`
	Since   string `json:"trusted_at,omitempty"`
	Path    string `json:"record,omitempty"`
	// Items is what this base holds right now, and Changes is how it differs from what was
	// trusted. Changes is empty when the base is trusted, when it has never been trusted, and
	// when the stored record predates per-item digests — three states a reader tells apart
	// from Trusted and Stored, and all three of which mean "print the whole listing".
	Items   []TrustItem   `json:"items,omitempty"`
	Changes []TrustChange `json:"changes,omitempty"`
}

TrustState is the answer to "may fkf run this base's commands", with enough detail for a diagnostic to say what changed.

func ReadTrust

func ReadTrust(ctx context.Context, root string) (TrustState, error)

ReadTrust reports whether this machine has trusted the base's current configuration.

func ReadTrustConfig

func ReadTrustConfig(ctx context.Context, config *Config) (TrustState, error)

ReadTrustConfig reports trust for the exact decoded execution plan a caller will use. Binding the check to this snapshot prevents a later disk reload from approving one plan while a long-lived Base executes another plan it opened earlier.

func WriteTrust

func WriteTrust(ctx context.Context, root string, now time.Time) (TrustState, error)

WriteTrust records the base's current configuration digest for this machine.

func WriteTrustConfig

func WriteTrustConfig(ctx context.Context, config *Config, now time.Time) (TrustState, error)

WriteTrustConfig records the exact decoded execution plan shown to and approved by a caller.

type WriterLock

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

WriterLock is one process's exclusive advisory lock for a physical base.

The empty state file is deliberately persistent. Removing it after unlock creates an inode race: a waiter can hold the old inode while a third process locks a newly created file with the same name. The kernel lock itself disappears when the descriptor closes or the process exits, so a leftover file is never a stale lock.

func AcquireWriterLock

func AcquireWriterLock(ctx context.Context, root string) (*WriterLock, error)

AcquireWriterLock takes the one fail-fast writer lock for root. Its identity follows symlinks, including through the deepest existing ancestor of a not-yet-created init target, so two spellings of the same base cannot acquire independent locks.

func (*WriterLock) Close

func (lock *WriterLock) Close() error

Close releases the advisory lock. It is safe to call more than once.

Jump to

Keyboard shortcuts

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