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 CacheKeyStale(recorded, effective string) bool
- func ComputeInputsDigest(projectPath, userLocalPath string) (string, error)
- func DefaultCacheKey(kind SourceKind, in CacheKeyInputs) string
- func DeriveRepoIDFromGit(repoPath string) string
- func DisplayPath(path string) string
- func EffectiveCacheKey(kind SourceKind, keys *CacheKeys, in CacheKeyInputs, opts CacheKeyOptions) string
- func ExpandPath(path string) string
- func GitSourceCacheDir(url string) string
- func HooksScopeDir(scope string) string
- func HooksScopeDirIn(agentsHome, scope string) string
- func LockedRemoteLayerBytes(parts LayerRefParts, ref string, locked map[string]LockedLayer) ([]byte, bool)
- 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 ReadCachedLayerBytes(sourceID, layerPath, sha string) ([]byte, bool)
- func ReadLockedLayers(projectPath string) (map[string]LockedLayer, error)
- 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
- func (a *AgentsRC) EffectivePRSource() events.PRSourceConfig
- func (a AgentsRC) MarshalJSON() ([]byte, error)
- func (a *AgentsRC) NewPRListProducer(reg *events.Registry, typ, source string, fetcher events.Fetcher) (*events.PRProducer, error)
- func (a *AgentsRC) Save(projectPath string) error
- func (a *AgentsRC) SetRefreshMetadata(version, commit, describe string, refreshedAt time.Time)
- func (a *AgentsRC) UnmarshalJSON(data []byte) error
- type AgentsRCKG
- type AgentsRCKGBridge
- type AgentsRCPRFetch
- type AgentsRCPRSource
- type AppTypeProfile
- type AuditEmitter
- type AuditEvent
- type CacheKeyGit
- type CacheKeyInputs
- type CacheKeyOptions
- type CacheKeys
- type Checker
- type ClassifiedUnit
- 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 ExecutionProfile
- 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) CacheKeyStaleForLayer(src Source, locked LockedLayer) bool
- 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) WithRefresh(refresh bool) *LayeredResolver
- func (r *LayeredResolver) WithUserLocalPath(path string) *LayeredResolver
- type Lenses
- type LocalSource
- type LockDriftResult
- type LockDriftStatus
- type LockUnitDrift
- type LockedLayer
- type LockedUnit
- type MergeCategory
- type MigrationResult
- type PackageFetcher
- type PackageRef
- type PackageRefParts
- type PreconditionPolicySpec
- type PredicateSpec
- type Principal
- type Project
- type PromptFileRef
- type Proposal
- type ProvenanceWarning
- type RefreshMetadata
- type RelevanceClasses
- type ResolvedLayer
- type ResolvedPreconditionPolicy
- type ResolvedPredicate
- type Resolver
- type ReviewNudge
- type SigningPosture
- type Snapshot
- type Source
- type SourceKind
- type StageProfile
- type StalenessReason
- type StalenessResult
- type StringsOrBool
- type Topology
- type UnitDigestFunc
- type UnitsLock
- type V1DeprecationWarning
- type Verdict
- type WorkingSet
- 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 AlwaysRevalidate = cacheKeyPrefix + "always-revalidate"
AlwaysRevalidate is the sentinel effective cache key returned when a force escape is in play (`--refresh` or the source's always_revalidate marker). It is intentionally not derivable from any real input, so it never matches a recorded lock digest — the resolver therefore always re-checks upstream.
const CurrentManifestVersion = 2
CurrentManifestVersion is the current .agentsrc.json schema version. A manifest with a lower Version (or one carrying deprecated v1 keys) is considered legacy and triggers a deprecation warning — the file still loads and the legacy keys are still folded (config-v2 §15.3 deprecation cadence: warn during the soak window, do not break parsing).
const DefaultRelevanceClass = "situational"
DefaultRelevanceClass is the class assigned to any unit not explicitly listed in a stage's RelevanceClasses. Per the design (§2), an unlisted unit is "situational" — never silently dropped from the working set.
const LayerProjectLocal = "project-local"
LayerProjectLocal is the project-local overlay layer identifier — the .agentsrc.local.json scope that sits just above LayerRepoLocal in precedence. The value matches the "project-local" slot name staleness.go namespaces the overlay under in inputs_digest, so the same scope reads consistently across the merge surface and the hash surface.
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).
const V1BackupSuffix = ".v1.bak"
V1BackupSuffix is the sidecar extension `da config migrate` appends to the original manifest before rewriting it as v2 (so `.agentsrc.json` → `.agentsrc.json.v1.bak`). The backup is the un-touched v1 bytes, letting a maintainer restore the pre-migration manifest by hand during the soak window.
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 CacheKeyStale ¶ added in v0.4.0
CacheKeyStale reports whether a unit's recorded content cache key is stale relative to its freshly-derived effective key (config-distribution-model §7A.4). It is the cache-key axis of staleness — orthogonal to the inputs_digest / declared-set / unit-digest driver events in Staleness above — and the single point the resolver consults to decide whether the SHA-addressed cache may be served or the upstream must be re-checked.
A unit is cache-key-stale when:
- the effective key is the AlwaysRevalidate sentinel — a force escape (`--refresh` or the source's always_revalidate marker) is in play, so the cache is bypassed unconditionally regardless of what was recorded; or
- the recorded key differs from the effective key — the per-source cache_keys override or the kind default now pins the content on a different fact than the lock captured (e.g. a changed {env} value, a re-tag, or a switched selector), so the cached content can no longer be trusted as current.
An empty recorded key (a pre-cache-key lock, or a unit resolved before this axis existed) is treated as stale against any concrete effective key so the first resolve under the new model re-checks and records a key. Two empty keys are fresh (nothing to compare yet).
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 DefaultCacheKey ¶ added in v0.4.0
func DefaultCacheKey(kind SourceKind, in CacheKeyInputs) string
DefaultCacheKey derives the content key for a source kind from the resolved inputs, with NO per-source override applied (config-distribution-model §7A.4 / D6). It is the kind-default half of EffectiveCacheKey, exported so callers that only need the default (e.g. a doctor "what would the default be" preview) can compute it directly.
Each kind keys on the fact that pins its content:
git -> resolved commit local -> resolved commit + working-tree state (dirty marker or content hash) http -> ETag, else Last-Modified, else content digest oci -> manifest digest
An unknown kind keys on the content digest as a safe, content-addressed fallback rather than panicking, so a future source type still gets a usable (if coarse) key.
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 EffectiveCacheKey ¶ added in v0.4.0
func EffectiveCacheKey(kind SourceKind, keys *CacheKeys, in CacheKeyInputs, opts CacheKeyOptions) string
EffectiveCacheKey is the single resolution point for a source's content key (config-distribution-model §7A.4 / R6). It composes, in priority order:
- FORCE ESCAPES — a per-unit `--refresh` (opts.Refresh) or the source's config-declared always_revalidate marker yields AlwaysRevalidate, which never matches a recorded digest so the resolver always re-checks.
- PER-SOURCE OVERRIDE — when CacheKeys carries selectors (file/glob, git commit/tags, env, dir), the key is derived from those selectors.
- KIND DEFAULT — otherwise DefaultCacheKey(kind, in).
The result is a stable, namespaced string suitable for recording in / comparing against the lock. It is deterministic: identical inputs always yield identical keys, and selector ordering never affects the result (file/env/dir/tags are sorted before hashing).
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 LockedRemoteLayerBytes ¶ added in v0.4.1
func LockedRemoteLayerBytes(parts LayerRefParts, ref string, locked map[string]LockedLayer) ([]byte, bool)
LockedRemoteLayerBytes resolves the cached bytes of a LOCKED remote layer: given the parsed ref parts and the locked set, it returns the cached layer.json bytes at the recorded SHA. ok is false (and bytes nil) when the ref is not locked, has no resolved SHA, or its bytes are not in the local cache — the caller then treats the layer as an unlocked/uncached remote (skip + sync hint). It is read-only and never fetches.
func MarkProposalReviewed ¶
func ProjectContextDir ¶
ProjectContextDir returns the local workflow context directory for a project.
func ProposalPath ¶
func ProposalTargetPath ¶
func ProposalsDir ¶
func ProposalsDir() string
func ReadCachedLayerBytes ¶ added in v0.4.1
ReadCachedLayerBytes returns the cached layer.json bytes for a source+layer at the given resolved SHA/digest, read from the content-addressed layer cache with NO fetch. ok is false when nothing is cached at that SHA. It is the exported, offline read commands use to validate a LOCKED remote layer against the bytes already on disk (e.g. `da config lint`), mirroring the resolver's offline cache-hit path (readOneLockedLayer) without re-exposing the internal layout.
func ReadLockedLayers ¶ added in v0.4.1
func ReadLockedLayers(projectPath string) (map[string]LockedLayer, error)
ReadLockedLayers reads the project's locked extends-layer set from .agentsrc.lock, keyed by the as-declared extends ref. It reads through the §7A units view (ReadUnits, which migrates a legacy config-only lock on read) and projects only the `layer` units, so callers get the same offline lock surface the resolver and verify paths consume. A missing/empty lock yields an empty (non-nil) map; a malformed lock surfaces an error. It performs NO fetch.
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 artifact bundles in the form
// `source-id:artifact-path@version-spec`. Any source kind (git|local|http|oci)
// may supply an artifact (config-distribution-model §15 D8).
Packages []PackageRef `json:"packages,omitempty"`
// Features overrides feature-flag defaults (config-distribution-model §3.6).
Features map[string]string `json:"features,omitempty"`
// ExecutionProfile is the config-v2 §15-shaped, scope-mergeable layer that
// routes a task's workflow execution shape (unit relevance + topology +
// lenses) by app_type. It is a kind=layer policy fragment — see
// internal/config/execution_profile.go and the
// skill-relevance-filter design.
ExecutionProfile *ExecutionProfile `json:"execution_profile,omitempty"`
// PRSource is the config-driven pull-request event producer config
// (pr-event-source design D4). It configures the generic internal/events
// producer engine — a fetch+field-map per platform — so PR state becomes
// event.pr.* events with no per-platform Go. `gh` is the zero-config default;
// an org layer can override the default via the config-v2 scope layers.
PRSource *AgentsRCPRSource `json:"pr_source,omitempty"`
// StageProfiles are named per-stage prompt-composition profiles, keyed by
// stage (executor | verifier | reviewer | orchestrator) then by slug. Each
// profile's prompt_files is source-aware (config-v2 Q1 ruling, Option B): an
// entry is a typed {source, path, version} object, with the bare-string legacy
// form still accepted on read (see PromptFileRef). The verifier and reviewer
// stages supersede the deprecated verifier_profiles / reviewer_profiles maps,
// and topology.verifier_sequence supersedes app_type_verifier_map; all three
// legacy keys are still read and folded in here for back-compat (see
// UnmarshalJSON / foldLegacyProfiles) and are never re-emitted.
StageProfiles map[string]map[string]StageProfile `json:"stage_profiles,omitempty"`
// PreconditionPolicies is the top-level named registry of verifier
// precondition policies (verifier-precondition-policy plan, Slice B). Each
// entry is a named policy = an ordered list of predicates over the unified
// event/signal contract. A stage profile references a policy by name via
// StageProfile.PreconditionPolicy; the verifier reads the resolved policy
// from the lockfile. Absent ⇒ the built-in `default` gate applies.
PreconditionPolicies map[string]PreconditionPolicySpec `json:"precondition_policies,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:"-"`
// LegacyKeys lists the deprecated v1 JSON keys observed in the source
// manifest during UnmarshalJSON (verifier_profiles / reviewer_profiles /
// app_type_verifier_map). These keys are silently folded into the unified
// stage_profiles / execution_profile model (see foldLegacyProfiles) and
// never re-emitted; recording them here lets `da init` / `da doctor`
// surface a deprecation warning without re-parsing the file (config-v2
// §15.3 deprecation cadence). Not serialized.
LegacyKeys []string `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) EffectivePRSource ¶ added in v0.4.0
func (a *AgentsRC) EffectivePRSource() events.PRSourceConfig
EffectivePRSource resolves the pr_source config that drives the producer: the configured block when present, otherwise the built-in gh default. This is the single resolution point so a missing pr_source field falls back to a working gh source rather than an empty (inert) one (pr-event-source D4).
func (AgentsRC) MarshalJSON ¶
func (*AgentsRC) NewPRListProducer ¶ added in v0.4.0
func (a *AgentsRC) NewPRListProducer(reg *events.Registry, typ, source string, fetcher events.Fetcher) (*events.PRProducer, error)
NewPRListProducer builds the live PR list producer from this config's resolved pr_source (pr-event-source D4/R3). It is the production entry point consumers (the layered-pr-fanout poll-detector, the pr-ci verifier) call to turn PR state into event.pr.* events: it registers the control-plane PR kinds on reg and returns a producer that derives each PR's Rollup.State on every cycle. This is the reachable path that exercises the config -> engine bridge (ToEngineConfig) and the DeriveRollupState rule — without it both would be dead code.
typ is the event.pr.* kind to emit (e.g. events.KindPROpened); a nil fetcher uses the real exec/http fetcher.
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 AgentsRCPRFetch ¶ added in v0.4.0
type AgentsRCPRFetch struct {
Argv []string `json:"argv,omitempty"`
URL string `json:"url,omitempty"`
Method string `json:"method,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
Each string `json:"each,omitempty"`
Map map[string]string `json:"map,omitempty"`
}
AgentsRCPRFetch is one named fetch block (the "list" or "comments" block) of a pr_source config: how to fetch (exec argv or http url) and how to project each item onto canonical fields (each + map).
type AgentsRCPRSource ¶ added in v0.4.0
type AgentsRCPRSource struct {
// Producer selects the named producer: "gh" (default), "exec", "http", or a
// registered code producer.
Producer string `json:"producer,omitempty"`
// List fetches the open PR list and maps it onto canonical PR fields.
List *AgentsRCPRFetch `json:"list,omitempty"`
// Comments fetches one PR's comments. Optional.
Comments *AgentsRCPRFetch `json:"comments,omitempty"`
// PollIntervalS is the producer poll cadence in seconds.
PollIntervalS int `json:"poll_interval_s,omitempty"`
}
AgentsRCPRSource is the .agentsrc.json `pr_source` block (pr-event-source D4). It is the config-facing mirror of the internal/events engine's PRSourceConfig: it owns JSON (de)serialization and config-v2 scope merging, while ToEngineConfig bridges it to the engine that actually drives the producer. A platform is added by writing this block — no Go change.
func (*AgentsRCPRSource) ToEngineConfig ¶ added in v0.4.0
func (s *AgentsRCPRSource) ToEngineConfig() events.PRSourceConfig
ToEngineConfig bridges the config-facing pr_source block to the internal/events engine type that actually drives the producer (pr-event-source D4). Without this converter the configured pr_source would never reach the engine — the gh/exec/ http source the user configured would be inert. Every field is carried across so the round-trip config -> engine -> producer preserves the configured fetch and field map exactly (no round-trip loss).
A nil receiver returns the built-in gh default, so an absent pr_source still yields a working producer config rather than an empty (inert) one.
type AppTypeProfile ¶ added in v0.4.0
type AppTypeProfile struct {
// Relevance is facet 1 (the noise filter): per-stage core/situational/noise
// classification of skills/agents/lenses. The map key is the stage name
// (e.g. "orchestrate", "verify", "review").
Relevance map[string]RelevanceClasses `json:"relevance,omitempty"`
// Topology is facet 2: the executor:verifier:reviewer fan-out shape. It is the
// successor to the retired flat app_type_verifier_map — verifier_sequence lives
// here, and legacy app_type_verifier_map entries are folded in on load.
Topology Topology `json:"topology,omitempty"`
// Lenses is facet 3: the review-lens config folded in from
// lens-evidence-policy (lens_set + lens_concurrency).
Lenses Lenses `json:"lenses,omitempty"`
}
AppTypeProfile is the per-app_type execution shape: the three independently scope-overridable facets bundled into one routed layer entry.
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 CacheKeyGit ¶ added in v0.4.0
type CacheKeyGit struct {
// Commit pins the key on the resolved commit hash (the git default fact).
Commit bool `json:"commit,omitempty"`
// Tags pins the key on the set of tags pointing at the resolved commit, so a
// re-tag (without a commit change) is a new key.
Tags bool `json:"tags,omitempty"`
}
CacheKeyGit selects which git facts pin the cache key when CacheKeys.Git is set. At least one of Commit/Tags is expected; both false is a no-op selector (it contributes nothing, equivalent to omitting the git block).
type CacheKeyInputs ¶ added in v0.4.0
type CacheKeyInputs struct {
// ResolvedCommit is the resolved git commit hash for a git or local source.
ResolvedCommit string
// WorktreeDirty reports whether a local source's working tree has
// uncommitted changes (§7A.4: local keys on commit PLUS working-tree state).
WorktreeDirty bool
// WorktreeContentHash is an optional precise hash of the local working
// tree's content; when set it makes the local key sensitive to the exact
// uncommitted bytes, not just the dirty flag. Empty falls back to the dirty
// marker alone.
WorktreeContentHash string
// Tags are the git tags pointing at ResolvedCommit, used by the {git:{tags}}
// selector. Order-insensitive (sorted before hashing).
Tags []string
// ETag is the http source's upstream ETag validator (the preferred http key).
ETag string
// LastModified is the http source's Last-Modified validator (used when no
// ETag is sent).
LastModified string
// ContentDigest is the fallback content hash for an http source whose server
// sent neither ETag nor Last-Modified.
ContentDigest string
// OCIDigest is the oci manifest digest (the content-addressed oci key).
OCIDigest string
// FileContents maps a Source.cache_keys file/glob entry to its resolved
// content hash, for the {file} selector. The caller hashes the matched
// files; this layer only folds the hashes into the composite key.
FileContents map[string]string
// EnvValues maps a Source.cache_keys env name to its current value, for the
// {env} selector. A missing var is recorded as absent.
EnvValues map[string]string
// DirPresent maps a Source.cache_keys dir entry to whether it exists, for
// the {dir} selector.
DirPresent map[string]bool
}
CacheKeyInputs carries the resolved facts the caller (the resolver / fetcher) supplies for one source so cache-key derivation stays pure: this file never runs git, never reads a file, never hits the network. Each field is the already-resolved fact for its kind; selectors and defaults read only from here. Absent facts are the zero value (empty string / nil), which derive a stable "absent" contribution rather than an error.
type CacheKeyOptions ¶ added in v0.4.0
type CacheKeyOptions struct {
// Refresh is the per-unit `--refresh` force escape: when true the effective
// key is AlwaysRevalidate, so this resolve re-checks upstream unconditionally
// even if nothing else changed.
Refresh bool
}
CacheKeyOptions threads per-unit resolution-time force escapes into key derivation — the runtime half of R6 (the config half is CacheKeys.AlwaysRevalidate). It parallels EnsureResolved's opts struct rather than a package-level flag.
type CacheKeys ¶ added in v0.4.0
type CacheKeys struct {
// Files lists tracked file paths or globs whose content pins the key — a
// change to any matched file is a new key (uv `cache-keys = [{file=...}]`).
Files []string `json:"file,omitempty"`
// Git selects which git facts (commit and/or tags) pin the key. Nil means
// the git facts do not participate in the override.
Git *CacheKeyGit `json:"git,omitempty"`
// Env lists environment variable names whose values pin the key — a changed
// value (e.g. a credential or feature flag) is a new key.
Env []string `json:"env,omitempty"`
// Dir lists directory paths whose presence pins the key — a {dir} marker is
// "exists" / "absent", catching a source that appears or disappears.
Dir []string `json:"dir,omitempty"`
// AlwaysRevalidate is the config-declared force escape (§7A.4 / R6): when
// true the effective key is the AlwaysRevalidate sentinel, so the resolver
// re-checks upstream on every resolve regardless of the recorded digest.
AlwaysRevalidate bool `json:"always_revalidate,omitempty"`
}
CacheKeys is the per-source override of the default content key (config-distribution-model §7A.4, the uv `cache-keys` analog). All fields are optional and use omitempty so a source that relies on the kind default round-trips byte-for-byte with no cache_keys object emitted.
When any selector field is set, the effective key is derived from those selectors instead of the kind default (the file/glob list, the git commit/tag selectors, the named env vars, and the dir-presence markers are combined into one composite key). AlwaysRevalidate is the config-declared force escape: it supersedes every selector and the default.
func (*CacheKeys) IsZero ¶ added in v0.4.0
IsZero reports whether the cache-keys override carries no selectors and no force escape — i.e. the source falls back entirely to the kind default. A nil receiver is zero. Used by callers to decide whether to emit the object at all.
func (CacheKeys) MarshalJSON ¶ added in v0.4.0
MarshalJSON omits an all-zero cache-keys object so a source relying on the kind default round-trips with no cache_keys key at all (byte-stable v1/v2 contract). A non-zero override marshals through the field-tagged shape.
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 ClassifiedUnit ¶ added in v0.4.0
type ClassifiedUnit struct {
// Unit is the candidate skill/agent/lens id.
Unit string `json:"unit"`
// Class is the resolved relevance class: "core", "situational", or "noise".
Class string `json:"class"`
}
ClassifiedUnit is one candidate paired with the relevance class it resolved to for the active app_type × stage. It is the unit of a WorkingSet: the ordered list of these entries is the single source of truth from which both the kept working set and the suppressed/original candidate sets are derived, so the view carries no parallel bookkeeping that could drift or be lost on serialization.
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 ExecutionProfile ¶ added in v0.4.0
type ExecutionProfile struct {
// ByAppType maps an app_type string (e.g. "go-cli", "ideation") to its
// resolved execution shape. Each task in a plan picks its profile by
// naming an app_type; unmatched app_types fall back to caller defaults.
ByAppType map[string]AppTypeProfile `json:"by_app_type,omitempty"`
// DefaultClass is the relevance class assigned to any unit not explicitly
// listed in a stage's RelevanceClasses. It defaults to "situational" so a
// unit is never silently dropped from the working set — see ClassOf and
// DefaultRelevanceClass.
DefaultClass string `json:"default_class,omitempty"`
}
ExecutionProfile is the config-v2 §15-shaped, scope-mergeable layer that routes a task's workflow execution shape by app_type. It bundles three facets per app_type — unit relevance (the noise filter), topology (the executor:verifier:reviewer fan-out), and lenses (the review-lens config) — so the routing logic today scattered across app_type_verifier_map, lens_routing, and max_parallel_tasks resolves through one mergeable surface.
It is purely additive and forward-compatible with the §15 units model: an execution_profile entry is a kind=layer config layer that merges by scope precedence (org → team → repo → project-local). No CLI consumes it yet (that is t2); this slice defines the types, the AgentsRC wiring, and the relevance-class helper.
See design .agents/proposals/skill-relevance-filter.md §2 (facets) + §4 (config-v2 §15 mapping).
func (*ExecutionProfile) ClassOf ¶ added in v0.4.0
func (p *ExecutionProfile) ClassOf(appType, stage, unit string) string
ClassOf returns the relevance class ("core" | "situational" | "noise") of unit within the given app_type × stage. Any unit not explicitly listed resolves to the profile's effective default class (situational), so an unlisted unit is classed — never silently dropped.
Resolution is explicit-list-first: an explicit listing in any class wins over the default. If a unit is (mis)listed in more than one class, the most conservative wins — noise over situational over core — so an operator suppressing a unit in one place is never overridden by a stale core listing.
func (*ExecutionProfile) EffectiveDefaultClass ¶ added in v0.4.0
func (p *ExecutionProfile) EffectiveDefaultClass() string
EffectiveDefaultClass returns the profile's configured DefaultClass, falling back to DefaultRelevanceClass ("situational") when it is unset. A nil profile also resolves to the safe default so callers never have to nil-check before classifying.
func (*ExecutionProfile) SuppressNoise ¶ added in v0.4.0
func (p *ExecutionProfile) SuppressNoise(appType, stage string, candidates []string) WorkingSet
SuppressNoise classifies candidates for the given app_type × stage and returns a reversible WorkingSet: units classed "noise" are marked suppressed while everything else (core, situational, and the default class for unlisted units) stays in the effective working set. Input order is preserved and the candidate slice is never mutated, so this is a pure function safe to call on resolved working sets.
Because classification routes through ClassOf, the default_class contract holds: an unlisted unit is situational (kept) by default and is never silently dropped. A profile with default_class=noise suppresses unlisted units instead — the same reversible view, just a different default. A nil profile keeps every candidate.
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
// KeyInputs carries the resolved facts for the source's effective content
// cache key (config-distribution-model §7A.4): the manifest digest for oci,
// the digest (plus any ETag/Last-Modified validator) for http-as-packages, so
// the package resolver can derive an effective key via EffectiveCacheKey. A
// zero value falls back to the kind default keyed on Digest.
KeyInputs CacheKeyInputs
}
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
// KeyInputs carries the resolved facts the fetcher observed for this source
// (git commit, http ETag/Last-Modified/digest, local commit + worktree
// state), so the resolver can derive the source's effective content cache key
// (config-distribution-model §7A.4) via EffectiveCacheKey without re-running
// the fetch. A zero value falls back to the kind default keyed on ResolvedSHA.
KeyInputs CacheKeyInputs
}
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 type. Per config-distribution-model §15 D13 there is no source/kind asymmetry: every source type — git, http, local, and oci — is valid for extends (config layers), just as every type is valid for packages. An oci layer is pulled over the same plumbing as an oci artifact, guarded by the config-layer media type (ociLayerFetcher), so `kind` stays meaningful.
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/oci) → repo-local. It resolves each extends ref to a source (any source type may supply a layer — config-distribution-model §15 D13), 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) CacheKeyStaleForLayer ¶ added in v0.4.0
func (r *LayeredResolver) CacheKeyStaleForLayer(src Source, locked LockedLayer) bool
CacheKeyStaleForLayer reports whether the lock's recorded cache key for a resolved layer is stale (config-distribution-model §7A.4) given the source's CURRENT declared cache_keys, without contacting the upstream. It re-derives the effective key from the recorded SHA facts plus the live override and compares it against the recorded key via CacheKeyStale, so it detects:
- a `--refresh` / always_revalidate force escape (AlwaysRevalidate is always stale); and
- a cache_keys override edit that changes the key shape even from the same SHA facts — e.g. adding an {env}/{git:tags} selector switches a source from the kind default to a composite override key, which no longer matches.
Live-validator drift an http ETag or a local worktree edit would cause is not detectable here (those facts are not in the lock); that axis is the online re-fetch's job. fetchLayer consults this on the online resolve path to force a cache-bypassing re-fetch when the recorded key is stale, and doctor/sync use it to nudge a re-resolve — it is the consumer that activates staleness.go's CacheKeyStale on the cache-key axis.
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) WithRefresh ¶ added in v0.4.0
func (r *LayeredResolver) WithRefresh(refresh bool) *LayeredResolver
WithRefresh toggles the per-resolve `--refresh` force escape: when true every source's effective cache key becomes AlwaysRevalidate, so the offline cache is never served and an online resolve re-checks upstream unconditionally (config-distribution-model §7A.4 / R6). Returns the receiver for chaining.
func (*LayeredResolver) WithUserLocalPath ¶
func (r *LayeredResolver) WithUserLocalPath(path string) *LayeredResolver
WithUserLocalPath sets the user-local manifest path (test seam) and returns the receiver.
type Lenses ¶ added in v0.4.0
type Lenses struct {
// LensSet is the ordered review lenses applied (e.g.
// "architecture-standards", "acceptance-invariants", "adversarial").
LensSet []string `json:"lens_set,omitempty"`
// LensConcurrency is how the lens set runs: "parallel", "gated", or
// "tiered".
LensConcurrency string `json:"lens_concurrency,omitempty"`
}
Lenses is the review-lens facet folded in from lens-evidence-policy.
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"`
// CacheKey is the effective content cache key the layer resolved at
// (config-distribution-model §7A.4), derived from the source's cache_keys
// override and the kind default via EffectiveCacheKey. On a later resolve it is
// compared against the freshly-derived key (CacheKeyStale): a mismatch — or the
// AlwaysRevalidate sentinel from a `--refresh` / always_revalidate force escape
// — means the SHA-addressed cache may no longer be served and the upstream must
// be re-checked. Omitted for a pre-cache-key lock (treated as stale on read).
CacheKey string `json:"cache_key,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"`
// CacheKey is the effective content cache key the unit resolved at
// (config-distribution-model §7A.4). It is the cache-key staleness axis —
// orthogonal to the content-hash driver events — that CacheKeyStaleForLayer
// compares on a later resolve. Carried on the units model (not just the
// retired legacy "config" section) so the §7A units-lock cutover does NOT
// drop the §7A.4 cache-key gate: a `--refresh`/always_revalidate force escape
// and a cache_keys override edit both still register. Omitted for a unit
// resolved without a cache key (e.g. a legacy lock migrated on read).
CacheKey string `json:"cache_key,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, // execution_profile, features, kg. For execution_profile this is what makes a // scope override (or a recompute-proposed relevance diff) facet-independent: // a higher layer that sets only one facet (e.g. relevance) deep-merges over // the base, so the untouched topology/lenses facets are preserved instead of // being wiped by a wholesale last-writer replace. 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 MigrationResult ¶ added in v0.4.1
type MigrationResult struct {
// ManifestPath is the absolute path to the .agentsrc.json that was inspected.
ManifestPath string
// BackupPath is where the original v1 bytes were (or would be) copied.
BackupPath string
// AlreadyV2 is true when the manifest is already clean v2 — no version bump
// and no legacy keys — so migration is a no-op.
AlreadyV2 bool
// FromVersion is the manifest's declared schema version before migration.
FromVersion int
// ToVersion is the schema version after migration (always CurrentManifestVersion
// when a migration is needed; equal to FromVersion on a no-op).
ToVersion int
// FoldedKeys lists the deprecated v1 keys that were folded away (dropped from
// the rewritten manifest); empty when only a version bump was needed.
FoldedKeys []string
// V2JSON is the rewritten v2 manifest bytes (with a trailing newline). On a
// no-op it is the manifest's current canonical serialization.
V2JSON []byte
// DryRun is true when this result was produced without writing.
DryRun bool
// WroteFile / WroteBackup report whether the migration actually wrote the v2
// manifest and the backup sidecar. Both false on a no-op or a dry run.
WroteFile bool
WroteBackup bool
}
MigrationResult reports the outcome of a v1→v2 migration plan. It is produced by MigrateAgentsRC and is the single value `da config migrate` renders, so the command path has no migration logic of its own — it only formats this result.
The result is computed identically for a real migration and a --dry-run preview; only the WroteFile/WroteBackup flags differ (both false on a dry run).
func MigrateAgentsRC ¶ added in v0.4.1
func MigrateAgentsRC(projectPath string, dryRun bool) (MigrationResult, error)
MigrateAgentsRC plans (and, unless dryRun, performs) an opt-in v1→v2 migration of the .agentsrc.json in projectPath. It is the testable core behind `da config migrate`.
Behavior:
- Loads the manifest. LoadAgentsRC already folds the deprecated v1 keys (verifier_profiles / reviewer_profiles / app_type_verifier_map) into the unified v2 stage_profiles / execution_profile model, and MarshalJSON never re-emits them — so the migration is "load → bump version → re-serialize".
- Idempotent: a clean v2 manifest (current version, no legacy keys) is a no-op; AlreadyV2 is set and nothing is written.
- When migration is needed and dryRun is false, the ORIGINAL v1 bytes are copied to .agentsrc.json.v1.bak BEFORE the v2 manifest is written, so the pre-migration file is always recoverable. The version is bumped to CurrentManifestVersion.
This is intentionally non-destructive to v1 loading: it does not touch the loader or the silent-fold/warn path. It is an explicit, opt-in rewrite a maintainer runs per-repo during the 2-release deprecation soak.
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 source type (oci, http, git, local). 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 an unsupported source type. This is the packages counterpart to SelectFetcher: per config-distribution-model §15 D3+D8+D13, any source type may serve any unit kind — the KIND (governed by the unit's media type), not the source, decides merge/trust. Packages/artifacts accept all four source types (git, local, http, oci), and after D13 so does extends; there is no remaining source/kind asymmetry.
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 PreconditionPolicySpec ¶ added in v0.4.0
type PreconditionPolicySpec struct {
Predicates []PredicateSpec `json:"predicates"`
}
PreconditionPolicySpec is one named entry in the precondition_policies registry: an ordered list of predicates all of which must hold for the in_progress → awaiting_agent_review gate to open.
type PredicateSpec ¶ added in v0.4.0
type PredicateSpec struct {
Signal string `json:"signal"`
Args map[string]string `json:"args,omitempty"`
}
PredicateSpec is the config-facing mirror of the commands/workflow Predicate: one predicate over a single registered event/signal kind. Signal is the registered kind (e.g. "event.pr.open", "gate.quality.sonar"); Args are kind-specific (e.g. {"equals":"green"}).
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 PromptFileRef ¶ added in v0.4.0
type PromptFileRef struct {
// Source is the source id the prompt file is fetched from. Empty means
// local resolution across the repo/home search path.
Source string `json:"source,omitempty"`
// Path is the prompt file path, relative to the source (or to the local
// prompt search path when Source is empty). Required.
Path string `json:"path"`
// Version pins a source-relative revision. Empty means "as resolved".
Version string `json:"version,omitempty"`
}
PromptFileRef is a single entry in a verifier/reviewer profile's prompt_files list. Per the config-v2 Q1 ruling (Option B — force typed objects everywhere), an entry resolves to a typed object {source, path, version}. The bare-string legacy form ("verifiers/unit.md") is still accepted on read and is equivalent to {path: "<string>"} with an empty source/version; this is the same dual-form pattern as LayerRef/PackageRef so legacy .agentsrc.json files round-trip byte-for-byte while new source-aware entries gain the object form.
Source names the config source the prompt file is fetched from (e.g. a source `id` declared under `sources`); an empty source means the prompt is resolved relative to the local repo/home search path (the historical behavior). Version pins a source-relative revision; empty means "as resolved".
func (PromptFileRef) MarshalJSON ¶ added in v0.4.0
func (r PromptFileRef) MarshalJSON() ([]byte, error)
MarshalJSON emits the compact string form when only Path is set (legacy compatibility, so existing string-list profiles round-trip unchanged) and the typed-object form once Source or Version is populated. Round-trip is stable under repeated marshal/unmarshal.
func (*PromptFileRef) UnmarshalJSON ¶ added in v0.4.0
func (r *PromptFileRef) UnmarshalJSON(data []byte) error
UnmarshalJSON accepts either a bare string (legacy form) or the typed-object {source, path, version} form.
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 RelevanceClasses ¶ added in v0.4.0
type RelevanceClasses struct {
// Core units are the always-relevant working set for this stage.
Core []string `json:"core,omitempty"`
// Situational units are conditionally useful; also the default for any
// unlisted unit (DefaultRelevanceClass).
Situational []string `json:"situational,omitempty"`
// Noise units are suppressed from the working set for this stage. The
// suppression is a reversible view, not a delete.
Noise []string `json:"noise,omitempty"`
}
RelevanceClasses partitions the units relevant to one app_type × stage into three reversible-view classes. Anything not listed defaults to the profile's DefaultClass (situational), so nothing unlisted is silently dropped.
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 ResolvedPreconditionPolicy ¶ added in v0.4.0
type ResolvedPreconditionPolicy struct {
// Name is the resolved policy's registry key, or "default" when the
// resolution fell back to the built-in gate.
Name string
// Predicates is the ordered predicate set. Empty ⇒ built-in default.
Predicates []ResolvedPredicate
}
ResolvedPreconditionPolicy is the config-side mirror of the commands/workflow PreconditionPolicy. Name is the registry key the policy resolved from (or "default" for the built-in fallback). An empty Predicates slice means the workflow evaluator should apply its built-in default policy.
func ResolvePreconditionPolicy ¶ added in v0.4.0
func ResolvePreconditionPolicy(projectPath, appType string) (ResolvedPreconditionPolicy, error)
ResolvePreconditionPolicy resolves the verifier precondition policy for an app_type from the LOCKED effective config (the lockfile-backed Snapshot, never raw LoadAgentsRC). Resolution chain:
app_type
→ execution_profile.by_app_type[app_type].topology.verifier_sequence (slugs)
→ the first verifier stage profile in that sequence naming a
precondition_policy
→ that name's entry in the top-level precondition_policies registry
→ converted ResolvedPreconditionPolicy
An unset name (no profile names a policy) OR an unset/absent registry entry yields the built-in default (Name="default", empty predicates) — never an error. A profile naming a policy that IS declared but with no predicates also resolves to that named-but-empty policy; the workflow evaluator falls back to its default for an empty predicate set, so the gate still holds.
Hard validation of a profile naming a *missing* registry key is Slice B5's job (da config verify); here a missing key simply degrades to the default so the verifier always has a usable gate.
type ResolvedPredicate ¶ added in v0.4.0
type ResolvedPredicate struct {
// Signal is the registered kind, e.g. "event.pr.open", "gate.quality.sonar".
Signal string
// Args are kind-specific parameters, e.g. {"equals":"green"}.
Args map[string]string
}
ResolvedPredicate is the config-side mirror of the commands/workflow Predicate: one predicate over a single registered event/signal kind. The workflow call site converts this into workflow.Predicate (field-for-field).
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 typed fields (stage_profiles, execution_profile, …) and any remaining ExtraFields 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", "execution_profile.by_app_type.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"`
// CacheKeys overrides the kind-default content cache key for this source
// (config-distribution-model §7A.4, the uv `cache-keys` analog). Absent ⇒
// the source uses its kind default (git→commit, local→commit+worktree,
// http→ETag/Last-Modified else digest, oci→digest). See cache_keys.go for
// the derivation; a *CacheKeys pointer keeps a defaulting source byte-stable
// (no cache_keys object emitted). Omitted on a v1 source.
CacheKeys *CacheKeys `json:"cache_keys,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 SourceKind ¶ added in v0.4.0
type SourceKind string
SourceKind enumerates the four source kinds whose cache-key defaults differ (config-distribution-model §4). It mirrors the Source.Type values; a dedicated type keeps the switch in DefaultCacheKey exhaustive and self-documenting.
const ( // SourceKindGit keys on the resolved commit (an immutable content pin). SourceKindGit SourceKind = "git" // SourceKindLocal keys on the resolved commit PLUS working-tree content, so // uncommitted authoring is a distinct key (§7A.4 / D6). SourceKindLocal SourceKind = "local" // SourceKindHTTP keys on the upstream ETag/Last-Modified validator, else a // content digest. SourceKindHTTP SourceKind = "http" // SourceKindOCI keys on the manifest digest (content-addressed). SourceKindOCI SourceKind = "oci" )
func SourceKindOf ¶ added in v0.4.0
func SourceKindOf(sourceType string) SourceKind
SourceKindOf maps a Source.Type string to its SourceKind so the resolver and fetchers can derive the cache-key default without re-spelling the switch. An unrecognized type is returned verbatim as a SourceKind; DefaultCacheKey's default branch then keys it on the content digest, so a future source type still gets a usable (if coarse) key rather than a panic.
type StageProfile ¶ added in v0.4.0
type StageProfile struct {
// Label is the human-readable profile name shown in fanout/explain output.
Label string `json:"label,omitempty"`
// PromptFiles is the base-first ordered prompt composition for the profile.
PromptFiles []PromptFileRef `json:"prompt_files,omitempty"`
// PreconditionPolicy names the verifier precondition policy (a key in the
// top-level precondition_policies registry) that gates this profile's
// in_progress → awaiting_agent_review transition. Unset ⇒ the built-in
// `default` gate (verifier-precondition-policy plan, Slice B).
PreconditionPolicy string `json:"precondition_policy,omitempty"`
}
StageProfile is one named entry in an AgentsRC.StageProfiles stage map (executor | verifier | reviewer | orchestrator). A profile is a label for display plus a base-first ordered prompt_files composition. The same type serves every stage — the stage is the outer map key — so the four agentic stages are uniform, composable primitives. PromptFiles is source-aware (see PromptFileRef) so an org layer can pin a prompt to a remote config source while a repo keeps the legacy local string form.
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 Topology ¶ added in v0.4.0
type Topology struct {
// Executors is the number of parallel executor workers for the task.
Executors int `json:"executors,omitempty"`
// VerifiersPerExecutor is the verifier fan-out per executor (n exec →
// VerifiersPerExecutor·n verifiers).
VerifiersPerExecutor int `json:"verifiers_per_executor,omitempty"`
// Reviewers is the reviewer fan-out: a keyword ("per_verifier",
// "per_executor") or a stringified integer count. Kept as a string so the
// keyword and fixed-count forms share one field.
Reviewers string `json:"reviewers,omitempty"`
// VerifierSequence is the ordered verifier-profile ids for this app_type; each
// id references a stage_profiles.verifier slug. It is the successor to the
// retired flat app_type_verifier_map (legacy entries fold in on load).
VerifierSequence []string `json:"verifier_sequence,omitempty"`
}
Topology encodes the executor:verifier:reviewer fan-out a task runs under. It is what app_type "structuralizes": n executors → VerifiersPerExecutor·n verifiers, with Reviewers per verifier/executor or a fixed count.
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 V1DeprecationWarning ¶ added in v0.4.1
type V1DeprecationWarning struct {
// Detected reports whether any v1 marker was found. When false the other
// fields are empty and callers should emit nothing.
Detected bool
// Version is the manifest's declared schema version (0 when absent).
Version int
// LegacyVersion is true when Version is below CurrentManifestVersion.
LegacyVersion bool
// LegacyKeys lists the deprecated v1 keys present in the manifest (sorted).
LegacyKeys []string
}
V1DeprecationWarning is the structured deprecation notice produced when a legacy (pre-v2) .agentsrc.json shape is detected. It is intentionally a value type (not an error): a v1 manifest still loads and still works during the soak window — the warning only nudges the user toward a v2 manifest. The zero value (Detected=false) means the manifest is clean v2.
func DetectV1Deprecation ¶ added in v0.4.1
func DetectV1Deprecation(rc *AgentsRC) V1DeprecationWarning
DetectV1Deprecation inspects a loaded manifest and reports whether it uses a legacy v1 shape — either an old schema version or one of the deprecated v1 keys folded silently on load. A nil manifest yields the zero (undetected) warning. This is a pure inspection: it never mutates rc and never fails.
func (V1DeprecationWarning) Message ¶ added in v0.4.1
func (w V1DeprecationWarning) Message() string
Message renders the deprecation warning as a single human-readable line suitable for a `da doctor` / `da init` warn bullet. Returns "" when nothing was detected so callers can guard on the empty string.
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 WorkingSet ¶ added in v0.4.0
type WorkingSet struct {
// Units is every candidate in input order, each tagged with its resolved
// relevance class. This is the load-bearing field; the helper methods are
// pure projections of it.
Units []ClassifiedUnit `json:"units,omitempty"`
}
WorkingSet is the reversible result of suppressing noise-classed units from a candidate set for one app_type × stage. Units is the ordered, fully-classified candidate list — the authoritative view. Because every candidate is retained here with its class, suppression is a non-destructive view (never a delete): Candidates losslessly reconstructs the original input, Kept yields the effective working set (core + situational), and Suppressed yields the noise-classed units.
Units carries a JSON tag and no unexported state, so the view survives a JSON round-trip intact — the t2/t4 resolvers render it as --json without losing the per-unit class or the ability to reverse the suppression.
func (WorkingSet) Candidates ¶ added in v0.4.0
func (ws WorkingSet) Candidates() []string
Candidates reconstructs the original candidate set in input order, undoing the suppression view losslessly. Because Units retains every candidate with its class, this round-trips the input regardless of how units interleave and survives JSON serialization — the real "reversible view, no delete" guarantee.
func (WorkingSet) Kept ¶ added in v0.4.0
func (ws WorkingSet) Kept() []string
Kept returns the effective working set in input order: every candidate whose class is not "noise" (core, situational, and the default for unlisted units). Returns nil when nothing is kept so an empty view marshals cleanly.
func (WorkingSet) Suppressed ¶ added in v0.4.0
func (ws WorkingSet) Suppressed() []string
Suppressed returns the noise-classed candidates removed from the working set, in input order. They are retained in Units (not deleted), so the suppression is a reversible view. Returns nil when nothing is suppressed.
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.
Source Files
¶
- agentsrc.go
- audit.go
- cache_keys.go
- config.go
- editability.go
- ensure_resolved.go
- execution_profile.go
- fetcher.go
- fetcher_git_artifact.go
- fetcher_http.go
- fetcher_local_artifact.go
- fetcher_oci.go
- fetcher_oci_layer.go
- layer_schema.go
- local_source.go
- lock_units.go
- lockstatus.go
- migrate.go
- overlay.go
- paths.go
- precondition_resolve.go
- proposals.go
- resolve_locked.go
- resolver.go
- snapshot.go
- staleness.go
- verify_layers.go