Documentation
¶
Index ¶
- Constants
- Variables
- func AgentsCacheDir() string
- func AgentsContextDir() string
- func AgentsHome() string
- func AgentsLockPath(projectPath string) string
- func AgentsStateDir() string
- func AppendUnique(slice []string, s string) []string
- func ApplyProposal(proposal *Proposal) error
- func ArchiveProposal(proposal *Proposal) error
- func ArchivedProposalPath(id string) string
- func ArchivedProposalsDir() string
- func ComputeInputsDigest(projectPath, userLocalPath string) (string, error)
- func DeriveRepoIDFromGit(repoPath string) string
- func DisplayPath(path string) string
- func ExpandPath(path string) string
- func GitSourceCacheDir(url string) string
- func HooksScopeDir(scope string) string
- func HooksScopeDirIn(agentsHome, scope string) string
- func MarkProposalReviewed(proposal *Proposal, status, reason string)
- func ProjectContextDir(project string) string
- func ProposalPath(id string) string
- func ProposalTargetPath(target string) (string, error)
- func ProposalsDir() string
- func ReadLockedUnits(projectPath string) (map[string]LockedUnit, error)
- func SaveProposal(proposal *Proposal, path string) error
- func SetWindowsMirrorContext(repoPath string)
- func UserHome() string
- func UserHomeDir() (string, error)
- func UserHomeRoots() []string
- func ValidateProposal(proposal *Proposal) error
- func ValidateProposalTarget(target string) error
- func WriteConfigLock(projectPath string, layers map[string]LockedLayer) error
- func WriteUnitsLock(projectPath string, lock UnitsLock) error
- type Agent
- type AgentsRC
- type AgentsRCKG
- type AgentsRCKGBridge
- type AuditEmitter
- type AuditEvent
- type Checker
- type Config
- func (c *Config) AddProject(name, path string)
- func (c *Config) GetProjectPath(name string) string
- func (c *Config) IsPlatformEnabled(platform string) bool
- func (c *Config) ListProjects() []string
- func (c *Config) RemoveProject(name string)
- func (c *Config) Save() error
- func (c *Config) SetPlatformState(platform string, enabled bool, version string)
- type Decision
- type Defaults
- type EditScope
- type EnsureOpts
- type EnsureResolverSeam
- type EnsureResult
- type Features
- type FetchedArtifact
- type FetchedLayer
- type Fetcher
- type FieldProvenance
- type FlatResolver
- type GitRepo
- type ImportError
- type ImportFailReason
- type LayerLockStatus
- type LayerRef
- type LayerRefParts
- type LayerValue
- type LayeredResolver
- func (r *LayeredResolver) Resolve(projectPath string) (*Snapshot, error)
- func (r *LayeredResolver) ResolveLocked(projectPath string) (*Snapshot, error)
- func (r *LayeredResolver) WithClock(now func() time.Time) *LayeredResolver
- func (r *LayeredResolver) WithEmitter(e AuditEmitter) *LayeredResolver
- func (r *LayeredResolver) WithFetcher(sourceType string, f Fetcher) *LayeredResolver
- func (r *LayeredResolver) WithOffline(offline bool) *LayeredResolver
- func (r *LayeredResolver) WithProductDefaults(d map[string]any) *LayeredResolver
- func (r *LayeredResolver) WithUserLocalPath(path string) *LayeredResolver
- type LocalSource
- type LockDriftResult
- type LockDriftStatus
- type LockUnitDrift
- type LockedLayer
- type LockedUnit
- type MergeCategory
- type PackageFetcher
- type PackageRef
- type PackageRefParts
- type Principal
- type Project
- type Proposal
- type ProvenanceWarning
- type RefreshMetadata
- type ResolvedLayer
- type Resolver
- type ReviewNudge
- type SigningPosture
- type Snapshot
- type Source
- type StalenessReason
- type StalenessResult
- type StringsOrBool
- type UnitDigestFunc
- type UnitsLock
- type Verdict
- type WriteAuthorizer
- type WriteTarget
Constants ¶
const ( // ActionSourceFetch fires once per layer fetch attempt that contacts (or // would have contacted) a source. Target is the source id; Fields carry // resolved_sha and cache_hit. ActionSourceFetch = "config.source.fetch" // ActionLayerResolve fires once per imported layer successfully resolved // and validated. Target is the "source_id:layer_path" ref; Fields carry // field_count and sha. ActionLayerResolve = "config.layer.resolve" // ActionFieldOverridden fires once per effective field that more than one // layer set (the higher-precedence layer overrides the lower). Target is the // field path; Fields carry from_layer, to_layer, value_summary. ActionFieldOverridden = "config.field.overridden" // ActionFieldProtectionViolation fires when a lower-precedence (imported / // user-local) layer attempts to set a repo-protected field and is dropped. // Target is the field path; Fields carry attempted_by_layer; Outcome=dropped. ActionFieldProtectionViolation = "config.field.protection_violation" // ActionImportFailed fires when an extends import cannot be resolved. Target // is the failing ref; Fields carry reason (transport|auth|content|schema| // not_found) and whether the entry was optional (and thus skipped). ActionImportFailed = "config.import.failed" // ActionEffectiveProduced fires once at the end of a successful resolve. // Target is the repo_id (or "" when unset); Fields carry layer_count. ActionEffectiveProduced = "config.effective.produced" )
Config-tier audit action identifiers. These are the canonical `action` strings of the base event schema, matching the taxonomy table in the spec.
const ( // OutcomeSuccess marks an event for an operation that completed normally. OutcomeSuccess = "success" // OutcomeDropped marks a protected-field override that was discarded. OutcomeDropped = "dropped" // OutcomeFailure marks an import that failed (non-optional). OutcomeFailure = "failure" // OutcomeSkipped marks an optional import that failed and was skipped. OutcomeSkipped = "skipped" )
Audit outcome values used in the base event schema.
const ( UnitKindLayer = "layer" UnitKindArtifact = "artifact" )
Unit kinds (§7A.1): a resolvable unit is either a config `layer` (merges into effective config, may declare more units) or an executable `artifact` (installed discretely, invoked under trust/signing). The kind governs merge/trust only, not sourcing.
const ( LockSectionConfig = "config" LockSectionPackages = "packages" )
LockSectionConfig is the agentslock section name owned by the config resolver (spec §7). The package resolver (pass 2) owns "packages" and the graph adapter owns "adapters"; this resolver writes config + an empty packages stub.
const ( // LayerProductDefaults is the built-in defaults layer shipped by dot-agents. LayerProductDefaults = "product-defaults" // LayerUserLocal is ~/.agents/.agentsrc.json (machine-local preferences). LayerUserLocal = "user-local" // LayerRepoLocal is the committed repo-local <project>/.agentsrc.json. LayerRepoLocal = "repo-local" )
Layer identifiers, lowest precedence first. These mirror the layer model in org-config-resolution §4. The FlatResolver only produces the three FLAT-scope layers below; imported org/team/repo extends layers (config-v2 p1b) slot in between LayerUserLocal and LayerRepoLocal with the same provenance surface.
They are the canonical identifiers that `da config explain` (config-v2 p4) renders.
const AgentsLockFile = ".agentsrc.lock"
AgentsLockFile is the lockfile name — the resolved-state companion to AgentsRCFile (.agentsrc.json), committed alongside it (spec §7).
const AgentsRCFile = ".agentsrc.json"
const AgentsRCLocalFile = ".agentsrc.local.json"
AgentsRCLocalFile is the project-local overlay manifest (§7A.1): the user's personal, machine-local, per-project layer (the `.git/config` analog), stored gitignored alongside the committed manifest. It is one of the local scopes folded into inputs_digest.
const LockSectionUnits = "units"
LockSectionUnits is the agentslock section name for the unified units model (config-distribution-model §7A.3). It replaces the legacy per-tier "config" and "packages" sections: one map keyed by "source:path@version" carrying a `kind`. The "adapters" section stays separate (graph lifecycle owns it).
Variables ¶
var ( ErrProposalNotFound = errors.New("proposal not found") ErrInvalidProposalTarget = errors.New("invalid proposal target") )
var ErrLockWouldChange = errors.New("config: lock would change (--locked assertion failed)")
ErrLockWouldChange is the sentinel returned by EnsureResolved in Locked mode when the committed lock is stale (re-resolving WOULD rewrite it). It is the `--locked` CI assertion: the caller maps it to a non-zero exit. When this is returned, EnsureResolved has written nothing.
var ProtectedFields = []string{"repo_id", "project"}
ProtectedFields are repo-owned scalars that an imported (non-repo-local) layer must not override (org-config-resolution §7.4). An attempt to set one from a lower-precedence layer is dropped and recorded as a non-fatal ProvenanceWarning. In FLAT scope there are no imported layers, so a protected field simply resolves from whichever local layer set it; the guard becomes load-bearing in p1b when extends layers can carry these keys.
Functions ¶
func AgentsCacheDir ¶
func AgentsCacheDir() string
AgentsCacheDir returns the root directory for cached remote sources.
func AgentsContextDir ¶
func AgentsContextDir() string
AgentsContextDir returns the local workflow context directory under ~/.agents.
func AgentsLockPath ¶
AgentsLockPath returns the canonical .agentsrc.lock path for a project: the sibling of the repo-local .agentsrc.json (spec §7). This is the single shared definition of the lockfile location. Every section writer — the config resolver here, the package resolver (pass 2), the graph-adapter lifecycle (#178, "adapters" section), and `da doctor`/`da status` (config-v2 p2) — MUST resolve the lockfile through this helper rather than re-deriving the path, so the canonical location can never drift between writers.
func AgentsStateDir ¶
func AgentsStateDir() string
AgentsStateDir returns the XDG state directory for dot-agents.
func AppendUnique ¶
AppendUnique appends s to slice only if not already present.
func ApplyProposal ¶
func ArchiveProposal ¶
func ArchivedProposalPath ¶
func ArchivedProposalsDir ¶
func ArchivedProposalsDir() string
func ComputeInputsDigest ¶
ComputeInputsDigest hashes all local config scopes whole-normalized (§7A.3), returning a "sha256:…" digest. Each scope's manifest is re-marshaled through canonical JSON so cosmetic edits (key order, whitespace) do not register as a content change; a missing scope hashes as empty. The result is the value compared against the lock's recorded inputs_digest to detect scope drift.
userLocalPath is the resolver's user-local manifest seam (empty ⇒ default <AgentsHome>/.agentsrc.json), so staleness honors the same test override the resolver uses.
func DeriveRepoIDFromGit ¶
DeriveRepoIDFromGit returns the canonical repo_id for the project at repoPath, derived from its `origin` git remote. The canonical form is `<host>/<path>` with the `.git` suffix stripped and the host lowercased, e.g. `github.com/acme/po-core-api-se` (per org-config-resolution §5.2).
Accepted remote forms:
- SSH: git@github.com:acme/repo.git → github.com/acme/repo
- SCP-style with user: ssh://git@github.com/acme/repo.git → github.com/acme/repo
- HTTPS: https://github.com/acme/repo.git → github.com/acme/repo
- HTTP: http://gitlab.acme.internal/g/r → gitlab.acme.internal/g/r
- git://: git://github.com/acme/repo.git → github.com/acme/repo
Returns "" (no error) when:
- the directory is not a git checkout
- the repo has no `origin` remote (e.g. `git init` only)
- the remote URL cannot be parsed into a host+path pair
Per spec §5.3 git derivation is a FALLBACK — callers must not overwrite an explicit repo_id set in the manifest. See MergeGenerateAgentsRC.
func DisplayPath ¶
DisplayPath converts an absolute path to a ~ prefixed display path.
func ExpandPath ¶
ExpandPath expands a path with ~ to the full absolute path.
func GitSourceCacheDir ¶
GitSourceCacheDir returns the cache directory for a given git URL.
func HooksScopeDir ¶
HooksScopeDir returns the canonical hooks directory for a scope rooted at the resolved AgentsHome(): ~/.agents/hooks/<scope>. scope is either "global" or a managed project name. `da remove --clean`'s canonical-dir cleanup resolves the hooks subtree through this helper instead of independently concatenating "hooks/<scope>".
func HooksScopeDirIn ¶
HooksScopeDirIn returns the canonical hooks directory for a scope under an explicit agents-home root: <agentsHome>/hooks/<scope>. This is the single definition of the canonical hooks-scope path model. Callers that already hold a resolved agents-home (the `da hooks` scope-tree guard, which must honor a test-injected root rather than the process environment) use this form so the path model can never drift between entrypoints.
func MarkProposalReviewed ¶
func ProjectContextDir ¶
ProjectContextDir returns the local workflow context directory for a project.
func ProposalPath ¶
func ProposalTargetPath ¶
func ProposalsDir ¶
func ProposalsDir() string
func ReadLockedUnits ¶
func ReadLockedUnits(projectPath string) (map[string]LockedUnit, error)
ReadLockedUnits loads the §7A "units" section of a project's .agentsrc.lock (migrating a legacy config/packages lockfile in memory when needed), returning an empty map when the file or section is absent. It is the exported, read-only companion doctor and config explain call to surface resolved unit digests / last-checked state without invoking any fetch or resolve. The map key is the resolved unit ref ("source:path@version").
func SaveProposal ¶
func SetWindowsMirrorContext ¶
func SetWindowsMirrorContext(repoPath string)
SetWindowsMirrorContext checks if the repo path is under a WSL Windows mount and sets the relevant env vars.
func UserHomeDir ¶
UserHomeDir resolves the user's home directory, honoring $HOME when set before falling back to os.UserHomeDir(). This is the convention for CLI tools (git, gh, …): on Windows os.UserHomeDir() reads %USERPROFILE% and ignores $HOME, which (a) breaks per-test isolation that sets HOME to a temp dir — causing cross-test pollution and writes into the real runner profile — and (b) ignores explicit shell/WSL HOME overrides. Honoring $HOME first fixes both with zero behavior change when $HOME is unset (the normal Windows desktop case). Mirrors the os.UserHomeDir signature for drop-in use.
func UserHomeRoots ¶
func UserHomeRoots() []string
UserHomeRoots returns the applicable user home directories. When AGENTS_WINDOWS_MIRROR is set for WSL, includes the Windows home too.
func ValidateProposal ¶
func ValidateProposalTarget ¶
func WriteConfigLock ¶
func WriteConfigLock(projectPath string, layers map[string]LockedLayer) error
WriteConfigLock writes the resolved config-layer state to .agentsrc.lock via the shared agentslock writer, preserving any sibling sections (packages, adapters) another writer already populated. It also stages an empty packages stub when none exists yet, so a fresh lockfile carries both tier-1 sections (spec §7); a pre-existing packages section written by pass 2 is left intact.
func WriteUnitsLock ¶
WriteUnitsLock writes the resolved units state and inputs_digest to .agentsrc.lock via the shared agentslock writer, preserving any sibling sections (e.g. "adapters") another writer populated (§7A.3). It is the §7A successor to WriteConfigLock; a later resolver task wires it into the two-pass engine.
Types ¶
type AgentsRC ¶
type AgentsRC struct {
Schema string `json:"$schema,omitempty"`
Version int `json:"version"`
Project string `json:"project,omitempty"`
Skills []string `json:"skills,omitempty"`
Rules []string `json:"rules,omitempty"`
Agents []string `json:"agents,omitempty"`
Hooks StringsOrBool `json:"hooks"`
MCP StringsOrBool `json:"mcp"`
Settings bool `json:"settings"`
Sources []Source `json:"sources"`
KG *AgentsRCKG `json:"kg,omitempty"`
Refresh *RefreshMetadata `json:"refresh,omitempty"`
// RepoID is the canonical repository identity (e.g. "github.com/acme/manager-ui").
// Protected: imported layers cannot override it. See org-config-resolution §5.
RepoID string `json:"repo_id,omitempty"`
// Extends references config layers in the form "source-id:layer-path[@version]".
// Each entry may be a plain string or an object form `{"ref": "...", "optional": true}`.
// Tier constraint (enforced at schema validation): extends entries must reference
// git|http|local sources — see config-distribution-model §4.
Extends []LayerRef `json:"extends,omitempty"`
// Packages references executable OCI/HTTP packages in the form
// "source-id:artifact-path@version-spec". Tier constraint: oci|http sources only.
Packages []PackageRef `json:"packages,omitempty"`
// Features overrides feature-flag defaults (config-distribution-model §3.6).
Features map[string]string `json:"features,omitempty"`
// ExtraFields captures unknown JSON keys so Save() can round-trip them
// instead of silently dropping legacy or custom fields.
ExtraFields map[string]json.RawMessage `json:"-"`
}
AgentsRC represents the .agentsrc.json manifest committed to a project repo.
Schema versions:
- version=1 (legacy): only the original field surface (project, sources, …) is meaningful. The v2 additive fields below remain absent/empty on a v1 file.
- version=2: the v2 additive fields (RepoID, Extends, Packages, Features and the extended Source fields ID/CacheTTL/Auth, plus the http+oci source types) become first-class. All v2 fields use `omitempty` so a v1 manifest round-trips byte-for-byte when these fields are absent.
See specs config-distribution-model §3-§5 + org-config-resolution §15.2.
func GenerateAgentsRC ¶
GenerateAgentsRC inspects ~/.agents/ and builds a manifest for the given project.
func LoadAgentsRC ¶
LoadAgentsRC reads .agentsrc.json from the given project directory.
func MergeGenerateAgentsRC ¶
MergeGenerateAgentsRC overlays a freshly generated manifest onto an existing on-disk manifest. Scan-derived lists (skills, rules, agents, hooks, mcp, settings) come from generated; an existing non-empty project name, unknown JSON keys (ExtraFields), and supplemental sources (e.g. git remotes not produced by GenerateAgentsRC) are preserved. Source entries are unioned with deduplication so the default local source is not duplicated when merging.
func (AgentsRC) MarshalJSON ¶
func (*AgentsRC) SetRefreshMetadata ¶
SetRefreshMetadata stores the latest refresh details in the manifest.
func (*AgentsRC) UnmarshalJSON ¶
type AgentsRCKG ¶
type AgentsRCKG struct {
// GraphHome overrides KG_HOME env var for this project. Defaults to ~/.knowledge-graph.
GraphHome string `json:"graph_home,omitempty"`
// Backend selects the storage backend: "sqlite" (default) or "postgres".
// Postgres requires KG_POSTGRES_URL.
Backend string `json:"backend,omitempty"`
// Bridge configures workflow/kg bridge query behaviour for this project.
Bridge AgentsRCKGBridge `json:"bridge"`
}
AgentsRCKG is the knowledge-graph configuration block in agentsrc.json.
type AgentsRCKGBridge ¶
type AgentsRCKGBridge struct {
Enabled bool `json:"enabled"`
AllowedIntents []string `json:"allowed_intents,omitempty"`
}
AgentsRCKGBridge is the bridge sub-config within the KG section.
type AuditEmitter ¶
type AuditEmitter interface {
// Emit records one audit event. Implementations must not block resolution.
Emit(AuditEvent)
}
AuditEmitter receives config-tier audit events. It is the injectable seam the LayeredResolver emits through (WithEmitter). Implementations must be safe for concurrent use: the resolver fetches extends layers in parallel, so Emit may be called from multiple goroutines. The default emitter is noopEmitter.
func NoopEmitter ¶
func NoopEmitter() AuditEmitter
NoopEmitter returns the shared no-op AuditEmitter. Callers that want auditing off use it explicitly; the resolver also falls back to it when none is set.
type AuditEvent ¶
type AuditEvent struct {
// Timestamp is when the event was produced (UTC).
Timestamp time.Time `json:"timestamp"`
// Actor is the emitting component (always auditActor for this package).
Actor string `json:"actor"`
// Principal is the human/service identity on whose behalf resolution ran,
// or "" when not attributed.
Principal string `json:"principal,omitempty"`
// Action is one of the config.* action constants.
Action string `json:"action"`
// Target is the subject of the action (source id, layer ref, field path, or
// repo id depending on Action).
Target string `json:"target"`
// Outcome is the disposition (success, dropped, failure, skipped).
Outcome string `json:"outcome"`
// TraceID correlates every event emitted within a single Resolve call.
TraceID string `json:"trace_id"`
// Fields holds the action-specific structured attributes.
Fields map[string]any `json:"fields,omitempty"`
}
AuditEvent is a single config-tier audit record. It is the Go form of the base event schema shared by every config.* action (spec §9). Fields holds the action-specific structured attributes named in the taxonomy table.
type Checker ¶
type Checker struct {
// Authorizer is the pluggable policy implementation. When nil, no backend
// is wired and governed scopes fall back to the safe default.
Authorizer WriteAuthorizer
}
Checker decides editability. It owns the scope-derivation rules and delegates the governed (team/org) tiers to an optional WriteAuthorizer. A zero Checker (nil Authorizer) is valid and SAFE: governed sources resolve to DecisionPrompt (deny-then-confirm), never a silent allow.
func NewChecker ¶
func NewChecker(authorizer WriteAuthorizer) *Checker
NewChecker returns a Checker bound to authorizer. Passing nil yields the safe default-deny-or-prompt behavior for governed scopes.
func (*Checker) CanWrite ¶
func (c *Checker) CanWrite(p Principal, s WriteTarget) Verdict
CanWrite answers "can principal p write source s?".
Rules (proposal §8 / §9):
- local → always allow (personal).
- project with no Owner → derives to local: allow (personal project).
- project with an Owner → derives to that owner's governance: treated as a governed write (team/org) and delegated to the backend.
- team/org → governed: delegate to the backend; with no backend wired (or a backend that errors), return the SAFE default DecisionPrompt.
An unknown/empty scope is denied — callers must route through a known scope.
type Config ¶
type Config struct {
Version int `json:"version"`
Defaults Defaults `json:"defaults,omitempty"`
Projects map[string]Project `json:"projects"`
Agents map[string]Agent `json:"agents,omitempty"`
Features Features `json:"features,omitempty"`
}
Config represents the ~/.agents/config.json structure.
func (*Config) AddProject ¶
AddProject registers a project in the config.
func (*Config) GetProjectPath ¶
GetProjectPath returns the path for a registered project, or empty string.
func (*Config) IsPlatformEnabled ¶
IsPlatformEnabled checks if a platform is enabled. Defaults to true if not set.
func (*Config) ListProjects ¶
ListProjects returns all registered project names.
func (*Config) RemoveProject ¶
RemoveProject unregisters a project from the config.
type Decision ¶
type Decision string
Decision is the outcome of an editability check.
const ( // DecisionAllow means the write is permitted. DecisionAllow Decision = "allow" // DecisionDeny means the write is refused. DecisionDeny Decision = "deny" // DecisionPrompt means the write requires explicit confirmation before it // proceeds — the safe default when a source is governed but no policy // backend is wired to decide. DecisionPrompt Decision = "prompt" )
type EditScope ¶
type EditScope string
EditScope is the ownership scope of a source, as it bears on who may write it. It is the editability-relevant projection of the precedence-scope axis in §7A.1; it is deliberately NOT the full precedence ladder (product/user/ runtime never receive CRUD writes through this seam).
const ( // ScopeLocal is the personal, machine-local asset store. Always writable // by its owner — the `.git/config`/project-local-overlay analog. ScopeLocal EditScope = "local" // ScopeTeam is a team-owned source. Writes are governed by the team's // policy backend. ScopeTeam EditScope = "team" // ScopeOrg is an org-owned source. Writes are governed by the org's // policy backend. ScopeOrg EditScope = "org" // ScopeProject is a project-owned source. Its editability DERIVES: a // project owned by a team/org defers to that governance; otherwise it is // personal and local-writable. ScopeProject EditScope = "project" )
type EnsureOpts ¶
type EnsureOpts struct {
// Locked asserts the lock is fresh: a stale lock yields ErrLockWouldChange
// and no write (CI gate).
Locked bool
// Frozen uses the lock as-is and skips the staleness check entirely.
Frozen bool
// NoSync skips the caller's outputs/projection step. It does not affect the
// lock decision here; it is recorded on the result for the caller.
NoSync bool
// Offline resolves from lock/cache only and never contacts the network.
Offline bool
// UserLocalPath is the resolver's user-local manifest seam, threaded into the
// staleness inputs_digest computation so it honors the same override the
// resolver uses. Empty ⇒ default <AgentsHome>/.agentsrc.json.
UserLocalPath string
// UnitDigest is the staleness per-unit digest seam (staleness.go). Nil skips
// the per-unit digest driver event (inputs_digest + declared-set only).
UnitDigest UnitDigestFunc
// Resolver overrides the resolver seam (test injection of a fake). Nil ⇒ a
// default NewLayeredResolver(). Both Resolve (rewrites the lock) and
// ResolveLocked (read-only) stay behind this interface so tests never touch
// the network.
Resolver EnsureResolverSeam
}
EnsureOpts selects the resolution mode for EnsureResolved. The four bools map 1:1 to the §7A.5 flags (--locked / --frozen / --no-sync / --offline). The remaining fields are the existing test seams threaded through so the seam stays hermetic: no network, no real clock.
type EnsureResolverSeam ¶
type EnsureResolverSeam interface {
// Resolve rebuilds the layer stack and REWRITES the lock.
Resolve(projectPath string) (*Snapshot, error)
// ResolveLocked rebuilds the snapshot from the lock/cache WITHOUT any fetch
// or write.
ResolveLocked(projectPath string) (*Snapshot, error)
}
EnsureResolverSeam is the resolution surface EnsureResolved drives: the rewriting Resolve path and the read-only ResolveLocked path. *LayeredResolver satisfies it; tests inject a fake so no resolution touches the network.
type EnsureResult ¶
type EnsureResult struct {
// Snapshot is the resolved effective config. Always non-nil on a nil error.
Snapshot *Snapshot
// ReResolved is true when the default-mode stale path ran Resolve and
// rewrote the lock. It is false for the Frozen, Offline, fresh, and (error)
// Locked paths — none of which write.
ReResolved bool
// Fresh is true when the staleness check reported no driver event. It is
// false in Frozen mode (the check is skipped) and on any stale path.
Fresh bool
// NoSync echoes EnsureOpts.NoSync so the caller can gate its outputs step
// without re-deriving it.
NoSync bool
// Reasons lists the driver events that made the lock stale (empty when fresh
// or Frozen). Surfaced so the caller can explain why a re-resolve happened.
Reasons []StalenessReason
}
EnsureResult is the outcome of EnsureResolved: the effective-config Snapshot plus the metadata the caller needs to decide what to do next (whether the lock was rewritten, whether the lock was already fresh, and the carried-over NoSync flag for the outputs step).
func EnsureResolved ¶
func EnsureResolved(projectPath string, opts EnsureOpts) (*EnsureResult, error)
EnsureResolved is the §7A.5 auto-sync seam. It computes staleness once (unless Frozen) and dispatches to exactly one resolution path. It owns the lock half of §7A.5 only; the caller owns the outputs/projection half (gated by NoSync, echoed on the result).
It is hermetic: every resolution goes through opts.Resolver (default LayeredResolver) and staleness through the injected UnitDigest seam, so no path here contacts the network or a clock directly.
type FetchedArtifact ¶
type FetchedArtifact struct {
// Data is the raw artifact blob.
Data []byte
// Digest is the canonical "sha256:<hex>" content digest (spec §7.2).
Digest string
// CacheHit reports whether Data came from the local package cache.
CacheHit bool
// Posture is the signing posture applied to this pull.
Posture SigningPosture
}
FetchedArtifact is the result of a tier-2 package pull: the raw artifact bytes, the content digest they were fetched at (the cache key and lockfile digest, spec §7), whether the bytes came from cache, and the signing posture that governed the pull.
type FetchedLayer ¶
type FetchedLayer struct {
// Data is the raw layer.json bytes.
Data []byte
// ResolvedSHA is the git commit SHA (git) or content hash (http/local) the
// layer was fetched at. It is the cache key and the lockfile resolved_sha.
ResolvedSHA string
// CacheHit reports whether Data came from the local cache.
CacheHit bool
}
FetchedLayer is the result of a successful layer fetch: the raw layer.json bytes, the resolved SHA/content hash they were fetched at, and whether the content came from cache (vs. a fresh network fetch).
type Fetcher ¶
type Fetcher interface {
// Fetch returns the layer bytes for parts.LayerPath from src. cacheDir is
// the content-addressed cache root for this source+layer
// (~/.agents/cache/config/<source-id>/<layer-path>); the fetcher writes its
// resolved <sha>/layer.json beneath it and returns the resolved SHA.
Fetch(src Source, parts LayerRefParts, cacheDir string) (FetchedLayer, error)
}
Fetcher fetches a config layer's bytes from a resolved source. One impl per source type (git, http, local); the resolver selects by Source.Type. The interface is the test seam: a fakeFetcher stands in so no test touches the network or a git binary.
func SelectFetcher ¶
SelectFetcher returns the Fetcher for a source type, or an error for an unsupported or tier-invalid type. oci is valid only for packages (pass 2), never for extends, so it is rejected here as a schema violation.
type FieldProvenance ¶
type FieldProvenance struct {
// ActiveLayer is the winning layer id, or "" when the field is unset
// everywhere.
ActiveLayer string `json:"active_layer"`
// Layers is the ordered (lowest precedence first) per-layer stack. Always
// a non-nil slice so JSON marshals to [] not null.
Layers []LayerValue `json:"layers"`
}
FieldProvenance is the full layer stack for a single field path, ordered by precedence (lowest first). ActiveLayer is the identifier of the winning layer, or "" when no layer set the field.
type FlatResolver ¶
type FlatResolver struct {
// ProductDefaults is the lowest-precedence layer. When nil, an empty object
// is used so the layer is always present in the stack (Present=true) even
// when it carries no fields, which keeps explain output stable.
ProductDefaults map[string]any
// contains filtered or unexported fields
}
FlatResolver resolves effective config from the FLAT layer set only: built-in product defaults, the user-local manifest (~/.agents/.agentsrc.json), and the repo-local manifest. It performs no network or git fetch — `extends` entries are recorded on the effective config but not followed (that is config-v2 p1b).
func NewFlatResolver ¶
func NewFlatResolver() *FlatResolver
NewFlatResolver returns a FlatResolver with empty product defaults and the default user-local manifest path.
func (*FlatResolver) Resolve ¶
func (r *FlatResolver) Resolve(projectPath string) (*Snapshot, error)
Resolve implements Resolver for the FLAT layer set.
func (*FlatResolver) WithUserLocalPath ¶
func (r *FlatResolver) WithUserLocalPath(path string) *FlatResolver
WithUserLocalPath sets an explicit user-local manifest path (test seam) and returns the receiver for chaining.
type GitRepo ¶
type GitRepo interface {
// IsRepo reports whether dir is a git work tree.
IsRepo(dir string) bool
// Init initializes a new git repository rooted at dir.
Init(dir string) error
// Resolve returns the HEAD commit hash and working-tree dirtiness for the
// repo at dir. An unborn branch yields ("", dirty, nil).
Resolve(dir string) (commit string, dirty bool, err error)
}
GitRepo is the seam over the git operations the local source needs. The production implementation is in-process via go-git (no `git` subprocess, no PATH lookup — the repo-wide policy since the config fetcher rewrite); tests inject a fake so bootstrap and ref resolution run with no real repo or network.
IsRepo reports whether dir is a git work tree. Init initializes a repo at dir. Resolve returns the HEAD commit hash plus whether the working tree is dirty; an unborn branch (freshly-init'd, no commits) is reported as an empty commit with no error so the caller can fall back to a deterministic empty-tree ref.
func NewGoGitRepo ¶
func NewGoGitRepo() GitRepo
NewGoGitRepo returns the production GitRepo backed by the in-process go-git library.
type ImportError ¶
type ImportError struct {
// Ref is the failing extends/packages entry, e.g. "acme:org/base".
Ref string
// SourceID is the source the ref was expected from.
SourceID string
// Reason is the failure category.
Reason ImportFailReason
// Err is the underlying cause, if any.
Err error
}
ImportError carries the structured failure contract for an extends/packages import (spec §11): which ref failed, which source it came from, and the failure category. It is the Go form of a config.import.failed event.
func (*ImportError) Error ¶
func (e *ImportError) Error() string
func (*ImportError) Unwrap ¶
func (e *ImportError) Unwrap() error
Unwrap exposes the underlying cause for errors.Is/errors.As.
type ImportFailReason ¶
type ImportFailReason string
ImportFailReason classifies a config-layer import failure, mapping 1:1 to the `reason` field of the config.import.failed audit event (spec §9, §11).
const ( // ReasonTransport: the source could not be reached / the fetch I/O failed. ReasonTransport ImportFailReason = "transport" // ReasonAuth: authentication or authorization was rejected. ReasonAuth ImportFailReason = "auth" // ReasonContent: the layer was fetched but its content was unreadable. ReasonContent ImportFailReason = "content" // ReasonSchema: a tier-constraint or layer-schema violation (surfaces // before any network call for tier constraints — spec §4). ReasonSchema ImportFailReason = "schema" // ReasonNotFound: the referenced source id or layer path does not exist. ReasonNotFound ImportFailReason = "not_found" )
type LayerLockStatus ¶
type LayerLockStatus struct {
Ref string `json:"ref"`
SourceID string `json:"source_id,omitempty"`
SourceType string `json:"source_type,omitempty"` // local | git | http | oci
Optional bool `json:"optional,omitempty"`
// Locked is true when the lockfile has an entry for Ref with a non-empty SHA.
Locked bool `json:"locked"`
SHA string `json:"sha,omitempty"`
// Cached is true when the cached layer.json exists at the locked SHA. Applies
// to every source type — git/http/local layers are all content-hashed and
// written to the same content-addressed cache.
Cached bool `json:"cached"`
CachePath string `json:"cache_path,omitempty"`
// Problem is empty when the layer verifies; otherwise a short, actionable
// reason (missing lock entry, cache miss, bad ref, undeclared source).
Problem string `json:"problem,omitempty"`
}
LayerLockStatus is the offline verification result for one declared `extends` layer: whether the lockfile pins a resolved SHA for it and whether the downloaded layer bytes are present in the on-disk cache at that SHA. Every source type (git/http/local) is content-hashed and cached identically, so the same check applies to all. It is what `da config verify` (config-v2 p4c) cross-checks so a layer can be confirmed offline — present and consistent with the lockfile — without ever re-fetching.
func VerifyLayerLocks ¶
func VerifyLayerLocks(projectPath string) ([]LayerLockStatus, error)
VerifyLayerLocks cross-checks every declared `extends` layer in the project's manifest against the lockfile and the on-disk layer cache, WITHOUT any fetch or lockfile mutation. For each layer it reports whether the lockfile pins a SHA and whether the downloaded bytes for that SHA are present in the cache — the same check for git, http, and local sources (all are content-hashed and cached identically).
Returns an empty slice (no error) when the project declares no `extends`, or when the manifest is absent (the caller's manifest check owns that failure).
func (LayerLockStatus) OK ¶
func (s LayerLockStatus) OK() bool
OK reports whether this layer verified cleanly offline.
type LayerRef ¶
type LayerRef struct {
// Ref is the layer reference string "source-id:layer-path[@version]".
Ref string `json:"ref"`
// Optional marks the layer as non-fatal on fetch failure.
Optional bool `json:"optional,omitempty"`
}
LayerRef is a single entry in AgentsRC.Extends. It accepts either a bare reference string ("acme:org/base") or an object form with an optional flag:
{"ref": "acme:team/experimental", "optional": true}
Per config-distribution-model §11.
func (LayerRef) MarshalJSON ¶
MarshalJSON emits the compact string form when Optional is false, otherwise emits the object form. Round-trip is stable under repeated marshal/unmarshal.
func (*LayerRef) UnmarshalJSON ¶
UnmarshalJSON accepts either a plain string or the object form.
type LayerRefParts ¶
LayerRefParts is the parsed form of a "source-id:layer-path[@version]" ref (spec §5). Version is the optional pin; for extends it overrides the source's declared ref (git SHA, tag, or branch).
func ParseLayerRef ¶
func ParseLayerRef(ref string) (LayerRefParts, error)
ParseLayerRef splits "source-id:layer-path[@version]" into its parts (spec §5). The source-id is everything before the first ':'; the version (if present) is everything after the last '@' in the remainder. A missing ':' or an empty source-id / layer-path is a parse error.
type LayerValue ¶
type LayerValue struct {
// Layer is the layer identifier (LayerProductDefaults, …).
Layer string `json:"layer"`
// Value is the JSON-decoded value this layer contributed, or nil if the
// layer did not set this field.
Value any `json:"value"`
// Active marks the winning layer for the field.
Active bool `json:"active"`
}
LayerValue is one slot in a single field's provenance stack: the value (if any) that a given layer contributed for that field path.
Active is true on exactly one entry per field — the winning (highest precedence) layer that set a value. When no layer sets the field, every entry has Active=false and Value=nil.
type LayeredResolver ¶
type LayeredResolver struct {
// contains filtered or unexported fields
}
LayeredResolver extends the FLAT layer set with tier-1 `extends` imports (spec §6 pass 1): product defaults → user-local → extends[] (left-to-right, fetched over git/http/local) → repo-local. It resolves each extends ref to a source, enforces the tier constraint (oci in extends is a schema error), fetches + caches the layer content-addressed by SHA, validates it against the layer schema, and records the resolved SHAs to .agentsrc.lock.
func NewLayeredResolver ¶
func NewLayeredResolver() *LayeredResolver
NewLayeredResolver returns a LayeredResolver wrapping a default FlatResolver.
func (*LayeredResolver) Resolve ¶
func (r *LayeredResolver) Resolve(projectPath string) (*Snapshot, error)
Resolve implements Resolver. It builds the full layer stack (FLAT + imported extends), merges it into a Snapshot, and writes the resolved-layer SHAs to .agentsrc.lock. Layer fetch/validation errors surface as *ImportError for non-optional entries; optional entries that fail are skipped with a warning.
func (*LayeredResolver) ResolveLocked ¶
func (r *LayeredResolver) ResolveLocked(projectPath string) (*Snapshot, error)
ResolveLocked produces an effective-config Snapshot WITHOUT any network or git fetch and WITHOUT mutating .agentsrc.lock or the layer cache. It is the read-only seam `da config explain` (config-v2 p4e) parses the locked state through, so explain can be inspected offline and as a pure observer.
Resolution model (mirrors Resolve, minus all writes/fetches):
- The repo-local manifest is read. If it declares no `extends`, resolution degrades to the FLAT layer set (product-defaults → user-local → repo-local) via the embedded FlatResolver, so explain still works on a flat project.
- Otherwise the imported `extends` layers are reconstructed by reading each layer's bytes from the on-disk cache at its LOCKED SHA (from .agentsrc.lock). No fetcher is ever invoked: a layer that is absent from the lockfile, or whose cached bytes are missing, is a hard error (explain surfaces it) rather than a fetch trigger.
- The assembled stack (product-defaults → user-local → imported → repo-local) is merged through resolveSnapshot (the §7.2 merge shared with Resolve).
ResolveLocked NEVER calls WriteConfigLock and NEVER calls a Fetcher.
func (*LayeredResolver) WithClock ¶
func (r *LayeredResolver) WithClock(now func() time.Time) *LayeredResolver
WithClock sets the TTL clock seam and returns the receiver.
func (*LayeredResolver) WithEmitter ¶
func (r *LayeredResolver) WithEmitter(e AuditEmitter) *LayeredResolver
WithEmitter registers the AuditEmitter that receives config.* events emitted during resolution (spec §9). A nil emitter disables auditing. Returns the receiver for chaining.
func (*LayeredResolver) WithFetcher ¶
func (r *LayeredResolver) WithFetcher(sourceType string, f Fetcher) *LayeredResolver
WithFetcher registers a Fetcher for a source type (test seam: inject a fakeFetcher for "git" so no test touches the network). Returns the receiver.
func (*LayeredResolver) WithOffline ¶
func (r *LayeredResolver) WithOffline(offline bool) *LayeredResolver
WithOffline toggles offline mode (use last resolved SHA from the lockfile).
func (*LayeredResolver) WithProductDefaults ¶
func (r *LayeredResolver) WithProductDefaults(d map[string]any) *LayeredResolver
WithProductDefaults sets the product-defaults layer and returns the receiver.
func (*LayeredResolver) WithUserLocalPath ¶
func (r *LayeredResolver) WithUserLocalPath(path string) *LayeredResolver
WithUserLocalPath sets the user-local manifest path (test seam) and returns the receiver.
type LocalSource ¶
type LocalSource struct {
// Root is the absolute path of the local source repo (`~/.agents`).
Root string
// Git is the seam over git operations; never nil after NewLocalSource.
Git GitRepo
}
LocalSource is the git-backed `local` source rooted at Root (the `~/.agents` repo). All git access goes through Git so the type is hermetic in tests.
func NewLocalSource ¶
func NewLocalSource(root string, repo GitRepo) *LocalSource
NewLocalSource builds a LocalSource for the repo rooted at root. A nil repo defaults to the production in-process go-git implementation, so callers that do not need a fake can pass nil.
func (*LocalSource) EnsureBootstrapped ¶
func (s *LocalSource) EnsureBootstrapped() (initialized bool, err error)
EnsureBootstrapped makes the local source resolvable on first resolve / init (§7A.1): if Root is not yet a git work tree, it is git-initialized so any local-authored unit can version by the repo's ref. It is idempotent — an already-initialized repo is left untouched — and reports whether it performed an init, so a caller can log first-time setup.
func (*LocalSource) EnsureProvenanceGitignore ¶
func (s *LocalSource) EnsureProvenanceGitignore(remotePaths []string) error
EnsureProvenanceGitignore writes the idempotent da-owned block into the local source's `.gitignore` so the given remote-materialized asset paths are excluded from the local repo's git tracking (§7A.5: "one asset dir, provenance-gitignored"). After this, `da sync` never commits fetched assets, and provenance is readable from git state alone: a tracked unit is local-authored, a gitignored one is remote-materialized.
It preserves any user-authored content outside the managed markers, sorts and de-duplicates the managed paths for a stable diff, and is a no-op-stable rewrite (calling it twice with the same inputs yields the same bytes). An empty path set removes the managed block entirely.
func (*LocalSource) LockKey ¶
func (s *LocalSource) LockKey(unitPath string) (string, error)
LockKey returns the uniform unit ref / lock key for a unit at unitPath within the local source: "local:<rel-path>@<resolved-ref>" (§7A.3). unitPath may be absolute (it is made relative to Root) or already repo-relative. The path is always slash-normalized so the key is stable across OSes. The local source must be bootstrapped first.
func (*LocalSource) ResolvedRef ¶
func (s *LocalSource) ResolvedRef() (string, error)
ResolvedRef returns the local source's resolved version: the current commit, suffixed with dirtySuffix when the working tree has uncommitted changes (§7A.4). A repo with no commits yet resolves to emptyTreeRef (still deterministic), optionally dirty when the tree already has staged/working content. The repo must be bootstrapped first; a non-repo Root is an error.
type LockDriftResult ¶
type LockDriftResult struct {
// LockPresent is true when a .agentsrc.lock file exists for the project.
LockPresent bool
// HasDeclaredUnits is true when the manifest declares at least one `extends`
// or `packages` unit (lock drift is only applicable to such manifests).
HasDeclaredUnits bool
// Units holds one record per ref in the union of declared and locked units,
// sorted by Ref. Records with LockStatusOK are included so callers can render
// a healthy summary count.
Units []LockUnitDrift
}
LockDriftResult is the renderable outcome of comparing a project's .agentsrc.lock against its declared `extends`/`packages` units. Callers branch on a few booleans and iterate a single sorted slice:
- LockPresent false → no lockfile at all (only meaningful when units are declared; a project with no extends/packages and no lock is simply local).
- HasDeclaredUnits false → the manifest declares no units, so lock drift is not applicable.
- Units → per-unit drift records, sorted by Ref, covering every declared and every locked ref (the union).
IsClean reports the common "nothing to surface" case.
func LockDrift ¶
func LockDrift(projectPath string) (LockDriftResult, error)
LockDrift compares a project's committed .agentsrc.lock against the `extends`/`packages` units declared in its .agentsrc.json and reports the per-unit *driver-event* drift. It is strictly read-only: it never writes or repairs the lockfile, and it never consults a clock (§7A.3 moves staleness off the TTL axis entirely).
Drift dimensions reported:
- a declared unit absent from the lock (missing-from-lock),
- a locked unit no longer declared (extra-in-lock).
A manifest with no declared units yields HasDeclaredUnits=false and an empty Units slice. A missing manifest surfaces as the LoadAgentsRC error; a missing lockfile is not an error (LockPresent=false), since the absence is itself the drift to report against declared units.
func (LockDriftResult) IsClean ¶
func (r LockDriftResult) IsClean() bool
IsClean reports whether the result has no drift to surface: either the manifest declares no units, or every declared unit is locked and the lock carries no extra entries.
func (LockDriftResult) Problems ¶
func (r LockDriftResult) Problems() []LockUnitDrift
Problems returns the subset of Units whose status is not OK, preserving the sorted order. Convenience for doctor / config explain drift-only rendering.
type LockDriftStatus ¶
type LockDriftStatus string
LockDriftStatus classifies a single declared/locked unit's drift state.
const ( // LockStatusOK means the declared unit has a matching lock entry. (Content // staleness is a separate, clock-free axis — see staleness.go — not a drift // status, because it does not mean the lock is structurally wrong.) LockStatusOK LockDriftStatus = "ok" // LockStatusMissingFromLock means the unit is declared in .agentsrc.json // (`extends` or `packages`) but has no entry in the lockfile — the project // was never resolved, or the lock predates the declaration (run // `da install` / `da config sync`). LockStatusMissingFromLock LockDriftStatus = "missing-from-lock" // LockStatusExtraInLock means the lockfile carries an entry for a unit that // is no longer declared — a stale leftover after the declaration was removed // from the manifest. LockStatusExtraInLock LockDriftStatus = "extra-in-lock" )
type LockUnitDrift ¶
type LockUnitDrift struct {
// Ref is the declared unit reference ("source-id:path[@version]").
Ref string
// Status classifies this unit's drift.
Status LockDriftStatus
// Digest is the locked content digest, empty for missing-from-lock entries.
Digest string
// Kind is the locked unit kind (layer|artifact), empty for
// missing-from-lock entries.
Kind string
}
LockUnitDrift is one unit's drift record: its declared ref, the classification, and (when present) the locked digest / kind so doctor and config explain can render a one-line diagnostic without re-reading the lock.
type LockedLayer ¶
type LockedLayer struct {
// ResolvedSHA is the git commit SHA or content hash at fetch time.
ResolvedSHA string `json:"resolved_sha"`
// FetchedAt is the RFC3339 timestamp the layer was fetched.
FetchedAt string `json:"fetched_at"`
// TTLExpiresAt is when the SHA should be re-checked, derived from the
// source cache_ttl. Empty means never re-check automatically (requires an
// explicit `da config sync`).
TTLExpiresAt string `json:"ttl_expires_at,omitempty"`
}
LockedLayer is one entry in the lockfile's "config" section: the resolved SHA a config layer was fetched at, plus its cache TTL window (spec §7). The map key is the layer ref ("acme:org/base").
type LockedUnit ¶
type LockedUnit struct {
// Kind is UnitKindLayer or UnitKindArtifact.
Kind string `json:"kind"`
// Digest is the content hash recorded at fetch time ("sha256:…").
Digest string `json:"digest"`
// FetchedAt is the RFC3339 timestamp the unit was fetched.
FetchedAt string `json:"fetched_at"`
// LastCheckedAt is the RFC3339 timestamp of the last explicit upstream
// re-check; it powers a doctor/explain review-nudge and never drives
// auto-invalidation (§7A.3). Empty when never re-checked since fetch.
LastCheckedAt string `json:"last_checked_at,omitempty"`
}
LockedUnit is one entry in the lockfile's "units" section (§7A.3). The map key is the fully-resolved ref "source:path@resolved-version"; the entry records the kind plus content-hash and timestamps. Staleness is content-hash driven (digest mismatch), never clock-driven — LastCheckedAt is a review-nudge basis only and never auto-invalidates.
type MergeCategory ¶
type MergeCategory int
MergeCategory classifies how a field combines across layers, per org-config-resolution §7.2. The default for any field not explicitly categorized is CategoryScalar (last writer wins).
const ( // CategoryScalar: last writer in precedence order wins (the whole value is // replaced). Applies to scalars and, by default, to any uncategorized field. CategoryScalar MergeCategory = iota // CategorySetUnion: arrays representing sets — union with stable order, // dedup by value. Applies to skills, agents, rules. CategorySetUnion // CategoryMapMerge: object maps — merge by key, recursing into nested maps; // per-key value uses CategoryScalar semantics. Applies to verifier_profiles, // features, kg. CategoryMapMerge // CategoryOrderedReplace: arrays representing ordered execution — replaced // wholesale by the highest-precedence writer (never merged). Applies to // sources and to each app_type_verifier_map entry's sequence. CategoryOrderedReplace )
type PackageFetcher ¶
type PackageFetcher interface {
// FetchArtifact returns the artifact blob for parts.ArtifactPath@VersionSpec
// from src, content-addressed and cached under the packages cache root.
FetchArtifact(src Source, parts PackageRefParts) (FetchedArtifact, error)
}
PackageFetcher pulls a tier-2 package artifact from a resolved source. One impl per packages-valid source type (oci, http). The interface is the test seam: a fake stands in so no test touches a real registry or the network.
func SelectPackageFetcher ¶
func SelectPackageFetcher(sourceType string) (PackageFetcher, error)
SelectPackageFetcher returns the PackageFetcher for a source type, or an error for a source type that is not valid for packages. This is the pass-2 (p6) counterpart to SelectFetcher: packages accept oci and http; git and local are rejected as a tier/schema violation (spec §4).
type PackageRef ¶
type PackageRef struct {
// Ref is the package reference string "source-id:artifact-path@version-spec".
Ref string `json:"ref"`
}
PackageRef is a single entry in AgentsRC.Packages. The string form is the canonical wire form per config-distribution-model §5; the object form is accepted for forward compatibility with future per-entry options.
func (PackageRef) MarshalJSON ¶
func (p PackageRef) MarshalJSON() ([]byte, error)
func (*PackageRef) UnmarshalJSON ¶
func (p *PackageRef) UnmarshalJSON(data []byte) error
type PackageRefParts ¶
PackageRefParts is the parsed form of a "source-id:artifact-path@version-spec" packages ref (config-distribution-model §5). Unlike extends refs, the version spec is required for packages.
func ParsePackageRef ¶
func ParsePackageRef(ref string) (PackageRefParts, error)
ParsePackageRef splits "source-id:artifact-path@version-spec" into its parts. The source-id is everything before the first ':'; the version spec (required) is everything after the last '@'. A missing ':' / '@', or an empty component, is a parse error (spec §5: @version-spec is required for packages).
type Principal ¶
type Principal struct {
// ID is the stable identifier of the actor (e.g. a user handle).
ID string
// Groups are the team/org memberships the principal holds. A governed
// source whose Owner is in Groups is one the principal may be authorized
// to write — the policy backend has the final say.
Groups []string
}
Principal is the actor requesting a write. It is intentionally minimal: a stable id plus the groups/teams it belongs to, which is all a policy backend needs to answer ownership/membership questions. Backends may carry richer identity out of band; this seam does not model auth.
type Proposal ¶
type Proposal struct {
SchemaVersion int `yaml:"schema_version"`
ID string `yaml:"id"`
Status string `yaml:"status"`
Type string `yaml:"type"`
Action string `yaml:"action"`
Target string `yaml:"target"`
Rationale string `yaml:"rationale"`
Content string `yaml:"content"`
CreatedAt string `yaml:"created_at"`
CreatedBy string `yaml:"created_by"`
ReviewedAt string `yaml:"reviewed_at"`
ReviewReason string `yaml:"review_reason"`
}
func ListPendingProposals ¶
func LoadProposal ¶
type ProvenanceWarning ¶
type ProvenanceWarning struct {
// FieldPath is the dot-separated path of the offending field.
FieldPath string `json:"field_path"`
// AttemptedByLayer is the layer that tried to set a protected field.
AttemptedByLayer string `json:"attempted_by_layer"`
// Outcome is the disposition of the attempt; always "dropped" today.
Outcome string `json:"outcome"`
}
ProvenanceWarning is a non-fatal event emitted during resolution — currently only protected-field override attempts (org-config-resolution §7.4). It maps to the config.field.protection_violation audit event landed in config-v2 p3.
type RefreshMetadata ¶
type RefreshMetadata struct {
Version string `json:"version,omitempty"`
Commit string `json:"commit,omitempty"`
Describe string `json:"describe,omitempty"`
RefreshedAt string `json:"refreshedAt,omitempty"`
}
RefreshMetadata records the latest da install/refresh that updated a project.
type ResolvedLayer ¶
type ResolvedLayer struct {
// ID is the layer identifier (LayerProductDefaults, …).
ID string `json:"id"`
// Present reports whether the layer existed (file on disk / built-in stub).
Present bool `json:"present"`
// Raw is the layer's decoded top-level object, or nil when absent.
Raw map[string]any `json:"raw,omitempty"`
}
ResolvedLayer is one input layer that participated in resolution, in precedence order. Raw holds the layer's decoded top-level JSON object (nil when the layer was absent, e.g. no user-local file), so explain surfaces can distinguish "layer present but empty" from "layer absent".
type Resolver ¶
type Resolver interface {
// Resolve produces the effective Snapshot for the project at projectPath.
// A fatal error (e.g. repo-local manifest fails to parse) is returned;
// non-fatal events (protected-field violations) surface in Snapshot.Warnings.
Resolve(projectPath string) (*Snapshot, error)
}
Resolver produces an effective-config Snapshot from a set of layers. The FLAT implementation (FlatResolver) walks only the local layers (product defaults, user-local, repo-local). The layered implementation (config-v2 p1b) extends the same interface to fetch declared `extends` layers over git/http/local before the repo-local layer. The interface is the seam both share.
type ReviewNudge ¶
type ReviewNudge struct {
// Ref is the resolved unit ref ("source:path@version").
Ref string
// LastCheckedAt is the RFC3339 timestamp of the last upstream re-check
// (falls back to fetched_at when never re-checked since fetch). Empty when
// the unit records neither.
LastCheckedAt string
// SinceLastCheck is how long ago the last re-check was, relative to the
// injected `now`. Zero when LastCheckedAt is empty or unparseable.
SinceLastCheck time.Duration
}
ReviewNudge is the demoted-TTL advisory for one unit (§7A.3): how long since its last upstream re-check. It NEVER invalidates the lock — it only drives a "last re-checked N ago — da config sync" reminder in doctor / config explain.
func ReviewNudges ¶
func ReviewNudges(projectPath string, now time.Time) ([]ReviewNudge, error)
ReviewNudges returns the per-unit review-nudge advisories for a project's locked units, sorted by ref. Each reports how long since the unit was last re-checked upstream (last_checked_at, or fetched_at as the basis when never re-checked). This is the demoted-TTL surface: it is advisory only and never affects staleness or the lock. Units with no timestamp basis are still returned (with a zero duration) so the surface lists every locked unit.
The reference instant `now` is injected by the caller (production passes time.Now(); tests pass a fixed instant) — the per-TEST_SEAMS.md DI shape that parallels Staleness's injected UnitDigestFunc, not a package-level clock var.
type SigningPosture ¶
type SigningPosture string
SigningPosture is the declared verification stance for a fetched package artifact (config-distribution-model §12 scope boundary; signing brought in earlier per spec Q3). It is a stub in p5: the posture is recorded and the verify hook is wired, but real signature material (cosign/sigstore, spec external-agent-sources §6 roadmap) is not yet checked. The posture governs whether an unverifiable artifact is allowed to resolve.
const ( // PostureUnsigned: signatures are not expected; the artifact resolves on a // successful digest match alone. Default for the p5 stub. PostureUnsigned SigningPosture = "unsigned" // PostureOptional: a signature is verified when present but its absence is // not fatal (warn-and-continue once real verification lands). PostureOptional SigningPosture = "optional" // PostureRequired: a verified signature is mandatory; an unsigned or // unverifiable artifact must fail to resolve. PostureRequired SigningPosture = "required" )
func PostureFromSource ¶
func PostureFromSource(src Source) SigningPosture
PostureFromSource derives the signing posture for a source. The posture is read from the opaque, pass-through auth block (the only source field whose schema is not owned by the config layer — external-agent-sources spec), under the well-known "signing" key, so no new typed Source field (and thus no agentsrc.go / struct-schema change) is required for the p5 stub. An absent, empty, or unrecognized value defaults to PostureUnsigned.
func (SigningPosture) Valid ¶
func (p SigningPosture) Valid() bool
Valid reports whether p is a recognized posture.
type Snapshot ¶
type Snapshot struct {
// Effective is the merged manifest after applying every layer per the
// category merge rules (org-config-resolution §7.2).
Effective AgentsRC `json:"effective"`
// Provenance maps a dot-separated field path to its full layer stack. Only
// top-level manifest keys are pre-populated; explain surfaces may request
// deeper paths via FieldAt.
Provenance map[string]FieldProvenance `json:"provenance"`
// Layers is the ordered (lowest precedence first) set of input layers.
Layers []ResolvedLayer `json:"layers"`
// Warnings holds non-fatal resolution events (protected-field violations).
// Always non-nil ([]ProvenanceWarning{}) so JSON marshals to [].
Warnings []ProvenanceWarning `json:"warnings"`
}
Snapshot is the resolved effective-config view produced by a Resolver. It is the canonical surface consumed by `da config explain`, `workflow app-types`, bundle materialization, and config validation (config-distribution-model §10).
func (*Snapshot) EffectiveRaw ¶
EffectiveRaw returns the effective config as a decoded top-level JSON object. This is the shape explain surfaces walk for arbitrary dot-paths; it round-trips through the AgentsRC marshaler so ExtraFields (verifier_profiles, app_type_verifier_map, …) are included.
func (*Snapshot) FieldAt ¶
func (s *Snapshot) FieldAt(path string) FieldProvenance
FieldAt returns the FieldProvenance for an arbitrary dot-separated path (e.g. "kg.backend", "app_type_verifier_map.go-cli"), computing it on demand by walking each layer's raw object. Top-level paths are also available directly in s.Provenance; FieldAt is the general accessor that also handles nested keys.
func (*Snapshot) FieldNames ¶
FieldNames returns the sorted union of top-level field names that any layer sets — the keys explain's --all view iterates.
type Source ¶
type Source struct {
Type string `json:"type"` // "local" | "git" | "http" | "oci"
Path string `json:"path,omitempty"` // override path for "local"
URL string `json:"url,omitempty"` // repository URL for "git" / "http" / "oci"
Ref string `json:"ref,omitempty"` // branch/tag for "git", or OCI tag
// ID is the stable local identifier used in extends/packages refs.
// Required for v2 sources referenced by extends or packages; optional for
// bare v1-style sources that exist only for legacy compatibility.
ID string `json:"id,omitempty"`
// CacheTTL is a duration string (e.g. "4h") governing tier-1 layer TTL.
// Ignored for oci sources (which are strictly content-addressed per spec §8).
CacheTTL string `json:"cache_ttl,omitempty"`
// Auth is an opaque pass-through block whose schema is owned by the
// external-agent-sources spec. The config layer treats it as an arbitrary
// JSON object and does not introspect it.
Auth json.RawMessage `json:"auth,omitempty"`
}
Source describes where to find agent resources. The v1 surface accepts `local` and `git` types; v2 adds `http` and `oci` per config-distribution-model §4. The v2 additive fields (ID, CacheTTL, Auth) all use omitempty so a v1 Source round-trips byte-for-byte when those fields are absent.
type StalenessReason ¶
type StalenessReason string
StalenessReason classifies why a lock is considered stale. A fresh lock has no reasons; a stale lock carries one or more.
const ( // ReasonInputsDigest means the whole-normalized hash of the local config // scopes no longer matches the lock's recorded inputs_digest (§7A.3). ReasonInputsDigest StalenessReason = "inputs-digest-mismatch" // ReasonDeclaredSet means the declared `extends`/`packages` unit set changed // since the lock was written (a ref was added or removed). ReasonDeclaredSet StalenessReason = "declared-set-changed" // ReasonUnitDigest means a recorded unit's digest no longer matches the // digest recomputed for its current content. ReasonUnitDigest StalenessReason = "unit-digest-mismatch" )
type StalenessResult ¶
type StalenessResult struct {
// Fresh is true when no driver event fired — the lock matches every local
// input and may be used as-is (the `--frozen`/no-op path of EnsureResolved).
Fresh bool
// Reasons lists the distinct driver events that made the lock stale, in a
// stable order. Empty when Fresh.
Reasons []StalenessReason
// ExpectedInputsDigest is the inputs_digest computed from the current local
// scopes; doctor/explain surface it next to the lock's recorded value.
ExpectedInputsDigest string
}
StalenessResult is the renderable outcome of a clock-free staleness check. Fresh reports the common "nothing changed" case; Reasons enumerates every driver event that fired so doctor/explain can show precisely what drifted.
func Staleness ¶
func Staleness(projectPath, userLocalPath string, recompute UnitDigestFunc) (StalenessResult, error)
Staleness performs the full clock-free staleness check (§7A.3) for a project against its committed lock. It compares the recorded inputs_digest, the declared `extends`/`packages` set, and (when recompute is non-nil) each recorded unit's digest, returning every driver event that fired.
It is strictly read-only and never touches the network or a clock. recompute may be nil to skip the per-unit digest check (inputs_digest + declared-set only); userLocalPath threads the resolver's user-local seam into the digest computation.
func (StalenessResult) IsStale ¶
func (r StalenessResult) IsStale() bool
IsStale reports whether any driver event fired (the inverse of Fresh).
type StringsOrBool ¶
StringsOrBool holds either a boolean flag (all/none) or a named list. It marshals/unmarshals as either a JSON bool or a JSON string array:
true → All resources of this type false → No resources ["name1","name2"] → Only the named resources
func (*StringsOrBool) Add ¶
func (s *StringsOrBool) Add(name string)
Add appends name to Names unless All is true (already covers everything).
func (StringsOrBool) Contains ¶
func (s StringsOrBool) Contains(name string) bool
Contains returns true if name is covered (either All=true or name is in Names).
func (StringsOrBool) IsEnabled ¶
func (s StringsOrBool) IsEnabled() bool
IsEnabled returns true if any resources are enabled (All or at least one name).
func (StringsOrBool) MarshalJSON ¶
func (s StringsOrBool) MarshalJSON() ([]byte, error)
func (*StringsOrBool) Remove ¶
func (s *StringsOrBool) Remove(name string)
Remove removes name from Names. No-op if All is true.
func (*StringsOrBool) UnmarshalJSON ¶
func (s *StringsOrBool) UnmarshalJSON(data []byte) error
type UnitDigestFunc ¶
UnitDigestFunc recomputes the current content digest for a resolved unit ref ("source:path@version"), reporting whether the unit's content is locally available to hash. It is the seam through which staleness checks the third driver event (recorded digest no longer matches) without this file reaching into the resolver, the cache, or the network: callers that have a cheap local digest source (e.g. the local source's working-tree hash) supply one; callers that only want the inputs_digest + declared-set checks pass nil.
type UnitsLock ¶
type UnitsLock struct {
// Units is keyed by "source:path@resolved-version".
Units map[string]LockedUnit
// InputsDigest is the top-level whole-normalized local-scope hash. Empty
// when no local scope has been hashed yet.
InputsDigest string
}
UnitsLock is the config-owned view of the lockfile under the §7A model: the resolved units map plus the top-level inputs_digest (the whole-normalized hash of all local config scopes). Staleness is an inputs_digest mismatch OR a changed declared set OR a per-unit digest mismatch — all cheap, local, and clock-free.
func ReadUnits ¶
ReadUnits loads the §7A units view of an existing lockfile. When the file already carries a "units" section it is read directly; when it does not but a legacy "config"/"packages" pair is present, the legacy shape is migrated in memory (v1 → v2) so callers always see the unified model. A wholly absent or empty lockfile yields an empty (non-nil) units map (§7A.3).
type Verdict ¶
type Verdict struct {
Decision Decision
// Reason explains the decision in operator-facing terms.
Reason string
// Scope is the editability scope that produced the decision. For a derived
// project source this is the scope it derived TO (ScopeLocal for a personal
// project, the governing tier for an owned one).
Scope EditScope
}
Verdict is the full result of an editability check: a Decision plus a human-readable Reason and the Scope that drove it, so callers (and `config explain`/`doctor`) can render WHY a write was allowed, denied, or gated.
type WriteAuthorizer ¶
type WriteAuthorizer interface {
// Authorize returns the verdict for a governed source. Implementations
// should return DecisionAllow/DecisionDeny per their policy; returning a
// non-nil error signals the backend could not reach a decision (e.g. the
// policy service was unreachable), which the Checker treats as a safe
// fail-closed prompt rather than an allow.
Authorize(p Principal, s WriteTarget) (Verdict, error)
}
WriteAuthorizer is the policy-backend-AGNOSTIC seam org/team governance plugs into (the adapter-contract pattern). A backend answers, for a governed (team/org) source, whether the principal may write it. The default Checker calls a backend only for governed scopes; local and personal-project writes never reach a backend.
Backends are intentionally NOT registered here — registration/selection is a downstream concern. This file ships only the contract and a nil-safe default.
type WriteTarget ¶
type WriteTarget struct {
// ID is the stable local source identifier (the `id` in the `sources`
// array, §3) used by `--source`.
ID string
// Scope is the source's editability scope (ownership tier).
Scope EditScope
// Owner names the team/org that owns a governed (team/org) source, or the
// owner of a project source when project ownership is a team/org. Empty
// Owner on a project scope means a personal/unowned project, which derives
// to local-writable.
Owner string
}
WriteTarget identifies the destination of a write through the editability seam. It is distinct from the wire-format Source (agentsrc.go): a Source declares transport/versioning; a WriteTarget carries the ownership SCOPE and owner that govern who may write it.