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 AuthorityRankOf(s AuthorityScope) int
- 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 DeriveTrustedRepoID(repoPath string) (repoID string, ambiguous bool)
- 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 IsProjectableKind(kind string) bool
- func IsSyncedUnitKind(kind string) bool
- func KnownUnitKind(kind string) bool
- func LockedRemoteLayerBytes(parts LayerRefParts, ref string, locked map[string]LockedLayer) ([]byte, bool)
- func MarkProposalReviewed(proposal *Proposal, status, reason string)
- func MergeProfileUnits(units map[string]LockedUnit, set ProfileSet, fetchedAt time.Time) map[string]LockedUnit
- func ProfileLockReproducible(deltas []ProfileLockDelta) bool
- func ProfileLockUnits(set ProfileSet, fetchedAt time.Time) map[string]LockedUnit
- func ProfileUnitDigest(p ConfigProfile) string
- func ProfileUnitsForSnapshot(snap *Snapshot, fetchedAt time.Time) (map[string]LockedUnit, error)
- 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 ValidateUnitKind(kind 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 AuthorityScope
- type AuthorityViolation
- type Binding
- type CacheKeyGit
- type CacheKeyInputs
- type CacheKeyOptions
- type CacheKeys
- type Checker
- type ClassifiedUnit
- type Config
- func (c *Config) AddProject(name, path string)
- func (c *Config) BindProject(name, path string)
- func (c *Config) DropLocalBindings()
- func (c *Config) GetProjectPath(name string) string
- func (c *Config) IsPlatformEnabled(platform string) bool
- func (c *Config) IsProjectBound(name string) bool
- func (c *Config) IsProjectKnown(name string) bool
- func (c *Config) ListProjects() []string
- func (c *Config) ProjectBinding(name string) (Binding, bool)
- func (c *Config) ProjectRepoID(name string) string
- func (c *Config) RemoveProject(name string)
- func (c *Config) Save() error
- func (c *Config) SetPlatformState(platform string, enabled bool, version string)
- func (c *Config) UpgradeNeeded() bool
- type ConfigManifest
- type ConfigProfile
- type Decision
- type Defaults
- type EditScope
- type EffectivePolicy
- 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 LayeringPolicy
- type Lenses
- type LocalSource
- type LockCollision
- type LockDriftResult
- type LockDriftStatus
- type LockUnitDrift
- type LockedLayer
- type LockedUnit
- type ManifestSpec
- type MergeCategory
- type MigrationResult
- type OverridePermissions
- type PackageFetcher
- type PackageRef
- type PackageRefParts
- type PolicyLockSpec
- type PolicyMode
- type PreconditionPolicySpec
- type PredicateSpec
- type Principal
- type ProfileConflict
- type ProfileContext
- type ProfileDriftKind
- type ProfileDriftResult
- type ProfileFieldDrift
- type ProfileKind
- type ProfileLock
- type ProfileLockDelta
- type ProfileLockStatus
- type ProfileProjection
- type ProfileSelector
- type ProfileSet
- type Project
- type PromptFileRef
- type Proposal
- type ProvenanceWarning
- type RefreshMetadata
- type RelevanceClasses
- type ResolvedLayer
- type ResolvedLockInfo
- type ResolvedManifest
- type ResolvedPreconditionPolicy
- type ResolvedPredicate
- type ResolvedProfile
- type Resolver
- type ReviewNudge
- type ScopeOrdering
- 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" // UnitKindProjectSet is the §15 D13/A2 synced identity-registry unit: the // portable project IDENTITY (id + portable key, NO path). It is a first-class // unit — scope- and manifest-referenceable, locked, and a member of // inputs_digest when locally authored — under the same selector-merge law as // every other unit. The machine-local BINDING table (id → absolute-path) is // NOT this unit and is never synced/scoped/projected (see IsSyncedUnitKind). UnitKindProjectSet = "project-set" // UnitKindDescriptor is the §15 D3/A3 CONDITIONAL fourth behavior: declarative, // non-merging, non-installing projection data. It stays Go-internal / NOT a // §15 unit (no lock entry, not projected) until a descriptor becomes // source-shipped (gated by the multi-harness F4 probe). The constant reserves // the kind so the resolver/lock recognize and fail-closed on a descriptor unit // today rather than mis-resolving it (see IsProjectableKind). UnitKindDescriptor = "descriptor" )
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 AppTypeProfileRefPrefix = "execution-profile"
AppTypeProfileRefPrefix is the synthetic source segment for app_type profiles derived from execution_profile.by_app_type. The absolute ref is "<prefix>:<app_type>".
const AuthoredProfileRefPrefix = "profile"
AuthoredProfileRefPrefix is the synthetic source segment for a profile authored directly in a layer's `profiles` block. The absolute ref is "<source>:<prefix>:<name>" (or "<prefix>:<name>" for a source-less layer), keeping authored refs in their own namespace, disjoint from the execution-profile / stage-profile derived refs.
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 StageProfileRefPrefix = "stage-profile"
StageProfileRefPrefix is the synthetic source segment for stage profiles derived from stage_profiles. The absolute ref is "<prefix>:<stage>".
const UnitKindManifest = "manifest"
UnitKindManifest is the §15 unit kind for a distributable config manifest (R1). It is recognized alongside layer/artifact/project-set/profile so the resolver and lock model fail-closed on an unknown kind rather than mis-resolving a manifest. Like project-set it is synced, scope-attachable, lockable config.
const UnitKindProfile = "profile"
UnitKindProfile is the §15 unit kind for a config profile (R2): a profile is a first-class unit identified by its absolute ref <source>:<name>, resolved and locked on the §15 substrate. It is recognized alongside layer/artifact/ project-set so the resolver and lock model fail-closed on an unknown kind rather than mis-resolving a profile.
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 AuthorityRankOf ¶ added in v0.4.2
func AuthorityRankOf(s AuthorityScope) int
AuthorityRankOf returns the §15 D1a AUTHORITY-RANK weight for a scope. The total order is org > team > repo > user; every value-only scope (product, public, runtime, project-local) carries rank 0 (zero authority — it may set values but never emit a binding lock). A higher rank binds every lower one.
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 DeriveTrustedRepoID ¶ added in v0.4.2
DeriveTrustedRepoID returns the canonical repo_id for the project at repoPath ONLY when the git origin is unambiguous (FORK-1 hybrid / R12). The second return is true when origin cannot be trusted as a portable identity:
- origin has multiple configured URLs, OR
- another remote canonicalizes to a DIFFERENT repo_id than origin (e.g. the AGOrcha case: origin=NikashPrakash fork vs org=AGOrcha).
On a non-git path, a repo with no/empty origin, or an unparseable origin URL the result is ("", false): not ambiguous, just no portable key — the caller falls back to the logical id (the registry map key). Callers MUST NOT trust repoID when ambiguous is true.
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 IsProjectableKind ¶ added in v0.4.2
IsProjectableKind reports whether a unit of this kind participates in resolution/projection today. layer (merges), artifact (installs), project-set (synced identity registry), and manifest (distributable §15+L1 bundle, L2) are projectable; descriptor is NOT until descriptorsSourceShipped flips (§15 D3/A3).
func IsSyncedUnitKind ¶ added in v0.4.2
IsSyncedUnitKind reports whether a unit of this kind is SYNCED config (rides a scope, participates in the lock + inputs_digest). project-set is synced portable identity (§15 D13/A2); manifest is the synced, scope-attachable L2 bundle (distributable-config-manifest D1); the machine-local binding table (id → absolute-path) is never a unit, so it never reaches this guard. A descriptor is not synced until source-shipped.
func KnownUnitKind ¶ added in v0.4.2
KnownUnitKind reports whether kind is a recognized §15 unit kind. descriptor is recognized as a reserved kind even though it is not yet a projectable/lockable unit (see IsProjectableKind).
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 MergeProfileUnits ¶ added in v0.4.2
func MergeProfileUnits(units map[string]LockedUnit, set ProfileSet, fetchedAt time.Time) map[string]LockedUnit
MergeProfileUnits folds a ProfileSet's profile units into an existing units map (the lock's "units" section) without disturbing sibling kinds, returning a new map. It is the additive bridge the lock writer uses to record profile units alongside layer/artifact/manifest entries in one .agentsrc.lock.
func ProfileLockReproducible ¶ added in v0.4.2
func ProfileLockReproducible(deltas []ProfileLockDelta) bool
ProfileLockReproducible reports whether every current profile unit is locked with a matching digest (no missing, stale, or extra entries) — the boolean "this resolution reproduces from the lock" summary (R2).
func ProfileLockUnits ¶ added in v0.4.2
func ProfileLockUnits(set ProfileSet, fetchedAt time.Time) map[string]LockedUnit
ProfileLockUnits projects a ProfileSet's profiles into §15 LockedUnit entries (kind: profile), keyed by the profile's NAMESPACED key, each carrying the unit content digest and the fetch timestamp. Keying by Key() (not the raw Ref) keeps an authored and a synthesized fragment that share a ref as TWO distinct lock entries instead of one collapsing the other — the identity collision the authored namespace exists to prevent. The result is deterministic in content: the digest is content-derived and the map is key-stable, so a re-run over the same set yields byte-identical entries regardless of input order.
func ProfileUnitDigest ¶ added in v0.4.2
func ProfileUnitDigest(p ConfigProfile) string
ProfileUnitDigest computes the content digest of one profile unit — a stable "sha256:<hex>" hash over its identity, authority, value-position, selector, provenance, and bundle. It is deterministic: the same fragment always hashes to the same digest, and two fragments differing in any covered field hash differently, so the lock detects a fragment edit.
func ProfileUnitsForSnapshot ¶ added in v0.4.2
ProfileUnitsForSnapshot derives the kind:profile lock contribution directly from a resolved snapshot (R2): it re-derives the profile set the resolver already produces (ProfileSetFromSnapshot, the same bridge da config explain uses) and projects it to LockedUnit entries. It is the ONE call the lock-generation caller makes to feed UnitsLock.ProfileUnits, so the profile units recorded in the lock are exactly the fragments the engine resolves — never a re-derived divergence. A malformed policy / authored profile fails closed (the same error the resolve path raises), never a silent empty contribution.
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 ValidateUnitKind ¶ added in v0.4.2
ValidateUnitKind rejects a unit kind that must not appear as a resolvable/ lockable unit yet. An unknown kind and a not-yet-source-shipped descriptor are both fail-closed errors, so a lockfile or manifest can never smuggle a mis-resolved unit past the resolver (§15.9 items 7-8).
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). The persisted "units" section is the base Units folded with the ProfileUnits contribution (R2), so profile units land in the lock through this one funnel — no parallel profile-lock machinery. It is the §7A successor to WriteConfigLock.
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"`
// Locks is the §15 D1a authority-axis lock block a layer emits in the
// policy-authority pass (Phase 1): value_locks (pin a field, rejecting lower
// writes) and deny_locks (subtract a set member, deny-overrides). force_allow
// is invalid and aborts the resolve. A lock binds only scopes ranked below
// its owner's AUTHORITY-RANK (org > team > repo > user). See authority.go.
Locks *PolicyLockSpec `json:"locks,omitempty"`
// AuthorityGrants is the §15 D1a source-authority registry block: a per-source
// allowlist "source-id → scope it may carry." It is honored only when written
// by a layer whose own authority is at least the conferred scope — a lower
// scope cannot self-bless authority (a resolve-time rejection). See
// resolveAuthorityGrants in authority.go.
AuthorityGrants map[string]AuthorityScope `json:"authority_grants,omitempty"`
// LayeringPolicy is the scope-attached unified-config-profiles (L1) policy
// unit: the Phase-1 layering governance a scope emits — precedence, absolute
// locks (deny/value, no force-allow), the Decision-2 three-state
// override_permissions, and the Q4 replace-mode marker. Its owning authority
// scope is SOURCE-derived (set by the resolver from the owning layer), never
// authored on the unit. See internal/config/profile.go and the
// unified-config-profiles design (§2.3).
LayeringPolicy *LayeringPolicy `json:"layering_policy,omitempty"`
// Manifests are the scope-attached distributable config manifest units (L2),
// keyed by name; each manifest's absolute ref is <owning-source>:<name>. A
// manifest REFERENCES (by ref) the sources to pull, the layering policy/profiles
// that bind, and (optionally) the project-set to manage — it never inlines
// copies (distributable-config-manifest D1/D2/R2). This lenient typed shape is
// what round-trips; the strict fail-closed decode (no manifest->manifest edge,
// no self-declared authority, no force-allow) runs at resolve time in
// manifest.go. Its owning authority is SOURCE-derived, never authored here (D4).
Manifests map[string]ManifestSpec `json:"manifests,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"`
// GraphBackend is facet 4: the graph-backend adapter-ref the profile
// selects (app-type-profiles §2.6 / §3.1). It is an open adapter-ref of the
// form `dotagents-builtin:graph/<name>@<constraint>` (or a bare `<name>`),
// resolved against the graph-backend adapter registry
// (graph-backend-adapter-contract §4/§8) — not a closed enum. An empty value
// means the profile inherits the pipeline's default backend (crg). The
// resolver (commands/config relevance --filter graph) turns this ref into a
// registered adapter and surfaces whether it resolves.
GraphBackend string `json:"graph_backend,omitempty"`
}
AppTypeProfile is the per-app_type execution shape: the three independently scope-overridable facets bundled into one routed layer entry.
func (AppTypeProfile) GraphBackendRef ¶ added in v0.4.2
func (p AppTypeProfile) GraphBackendRef() string
GraphBackendRef returns the profile's declared graph-backend adapter ref, or the empty string when the profile selects none (and inherits the pipeline default). Centralising the read keeps callers from poking the field directly, so a later default-backend policy has one place to land.
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 AuthorityScope ¶ added in v0.4.2
type AuthorityScope string
AuthorityScope is one rung on the §15 D1a AUTHORITY-RANK axis. It is distinct from EditScope (the editability projection in editability.go): authority governs who may emit locks/caps, editability governs who may write a source.
const ( // AuthProduct is the floor: ships defaults, carries ZERO authority, and is // overridden by everything (§15 D1a "SPECIAL scopes"). AuthProduct AuthorityScope = "product" // AuthPublic is a value-only, zero-authority scope. It may supply values at // the lowest precedence, but ANY authority/lock claim it ships is inert // unless co-signed by a trusted root (the supply-chain-signing guard). It is // also the default authority of an UNGRANTED imported (extends) layer. AuthPublic AuthorityScope = "public" // AuthRuntime is the highest value-precedence, zero-authority scope: it sets // values but never locks. AuthRuntime AuthorityScope = "runtime" // AuthProjectLocal is the uncommitted per-project overlay: value-only, zero // authority (§15 D9 / D1a). AuthProjectLocal AuthorityScope = "project-local" // AuthUser is the LOWEST authority rung on the chain — beneath repo. A user // scope may emit locks only at its own scope; every higher scope, including // repo, overrides it (a personal scope can never constrain a shared repo). AuthUser AuthorityScope = "user" // AuthRepo is a shared committed scope. It MAY set its own local guardrails // but ranks below team, so a team lock still binds the repo. AuthRepo AuthorityScope = "repo" // AuthTeam ranks above repo and below org. AuthTeam AuthorityScope = "team" // AuthOrg is the highest authority rung: its locks/caps are absolute over // every lower scope. AuthOrg AuthorityScope = "org" )
func SnapshotScopeChain ¶ added in v0.4.2
func SnapshotScopeChain(snap *Snapshot) []AuthorityScope
SnapshotScopeChain returns the authority scopes present in the snapshot, in VALUE-PRECEDENCE order (CanonicalScopeOrdering().ValuePrecedence) — NOT authority-rank — because the chain seeds the VALUE-merge ordering and must match the legacy layer order (an imported org/team source sorts BELOW repo for values). Scopes reflect §15 grant upgrades (FIX 3). It is the default context scope chain `da config explain` resolves a profile against when the caller does not pin one.
type AuthorityViolation ¶ added in v0.4.2
type AuthorityViolation struct {
Kind string `json:"kind"`
Source string `json:"source,omitempty"`
Scope AuthorityScope `json:"scope,omitempty"`
Reason string `json:"reason"`
Fatal bool `json:"fatal"`
}
AuthorityViolation is a fatal or recorded breach surfaced by Phase 1. Fatal violations (self-elevation, force-allow) abort the resolve fail-closed; non-fatal ones (an inert public grant, a zero-authority lock) are recorded.
type Binding ¶ added in v0.4.2
Binding is one row of the MACHINE-LOCAL binding table: a project id resolved to its absolute path on THIS machine. Never synced (R5/R7) — it lives under the gitignored ~/.agents/local/ (FORK-3).
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"`
// contains filtered or unexported fields
}
Config represents the ~/.agents/config.json structure — the SYNCED portable surface. Absolute project paths NEVER appear here; they live in the machine-local binding table loaded from ~/.agents/local/ (home-config defects 1 & 2). The unexported bindings/upgradeNeeded fields are deliberately not JSON-tagged so json.Marshal can never leak a path into the synced tree.
func Load ¶
Load reads config.json from AgentsHome and the machine-local binding table from ~/.agents/local/. It is a PURE read (FORK-5): a legacy v1 config is migrated in memory (paths folded into the binding table) and flagged via UpgradeNeeded, but nothing is written until the next Save.
func (*Config) AddProject ¶
AddProject registers a project: it records the portable identity (deriving a trustworthy repo_id per the FORK-1 hybrid / R12 guard) in the synced registry AND the machine-local id→path binding. The two surfaces are written together by the next Save.
func (*Config) BindProject ¶ added in v0.4.2
BindProject establishes (or repairs) ONLY the machine-local path binding for a project whose identity is already known — e.g. rebinding on machine B. It does NOT mutate the synced identity registry (R5).
func (*Config) DropLocalBindings ¶ added in v0.4.2
func (c *Config) DropLocalBindings()
DropLocalBindings clears the machine-local binding table while preserving the synced identity registry. `da init --from` calls it when Load reports a legacy v1 home was cloned (UpgradeNeeded): the inline paths a v1 config carries are the SOURCE machine's absolute paths and are invalid on this machine, so they must be discarded rather than inherited (home-config defect 1). The project identities still travel; each project is then reported known-but-unbound until rebound on THIS machine (R5/R7). It does not touch the synced registry.
func (*Config) GetProjectPath ¶
GetProjectPath returns the machine-local path bound to a project, or "" when the project is known but unbound on this machine.
func (*Config) IsPlatformEnabled ¶
IsPlatformEnabled checks if a platform is enabled. Defaults to true if not set.
func (*Config) IsProjectBound ¶ added in v0.4.2
IsProjectBound reports whether a project has a machine-local path binding.
func (*Config) IsProjectKnown ¶ added in v0.4.2
IsProjectKnown reports whether a project is in the synced identity registry.
func (*Config) ListProjects ¶
ListProjects returns all registered project names from the identity registry.
func (*Config) ProjectBinding ¶ added in v0.4.2
ProjectBinding returns the machine-local binding row for a project.
func (*Config) ProjectRepoID ¶ added in v0.4.2
ProjectRepoID returns the portable rebind key recorded for a project, or "".
func (*Config) RemoveProject ¶
RemoveProject unregisters a project from both surfaces.
func (*Config) Save ¶
Save persists the SYNCED identity registry (config.json) and the MACHINE-LOCAL binding table (~/.agents/local/bindings.json), clearing UpgradeNeeded once a migrated v1 config has been rewritten in the v2 shape.
Ordering is load-bearing: the binding table is written FIRST, then the path-free config.json. A legacy v1 config carries the project path INLINE; if the path-free config.json were written first and the binding write then failed, the path would be gone from BOTH files (data loss). Writing bindings first means any failure leaves config.json untouched — the recoverable old shape still carries the path, so a retried Save recovers cleanly.
func (*Config) SetPlatformState ¶
SetPlatformState updates enabled/version for a platform in config.
func (*Config) UpgradeNeeded ¶ added in v0.4.2
UpgradeNeeded reports whether Load decoded a legacy v1 config.json whose machine-local paths must be persisted into the split shape by the next Save.
type ConfigManifest ¶ added in v0.4.2
type ConfigManifest struct {
// Ref is the absolute unit ref <source>:<name> — the identity (§15 D2).
Ref string
// Scope is the SOURCE-derived authority scope (Decision 1), stamped by the
// loader, never authored on the unit.
Scope AuthorityScope
// Spec is the authored, validated payload.
Spec ManifestSpec
}
ConfigManifest is one RESOLVED kind:manifest unit: its authored spec plus the loader-stamped identity (Ref) and SOURCE-derived authority (Scope, Decision 1). Scope is NEVER read from the unit's own contents — the loader stamps it from ref.source → source-registry → scope — so a public/untrusted manifest cannot self-grant authority (D4, the §15 no-self-blessing invariant).
func ManifestSetFromSnapshot ¶ added in v0.4.2
func ManifestSetFromSnapshot(snap *Snapshot) ([]ConfigManifest, error)
ManifestSetFromSnapshot derives every declared manifest unit from a resolved snapshot, carrying each at its EFFECTIVE authority scope (base scope upgraded by any §15 source-authority grant the stack confers). It reuses effectiveLayerScopes — the §15 grant write-guard — so a self-blessing authority claim FAILS the resolve here (the no-self-blessing invariant), and a public/ungranted manifest stays AuthPublic. It is the manifest sibling of ProfileSetFromSnapshot.
type ConfigProfile ¶ added in v0.4.2
type ConfigProfile struct {
// Ref is the absolute unit ref <source>:<name> — the identity and the
// deterministic same-scope tie-break key (Decision 6).
Ref string
// Kind selects the bundle-merge discipline (deep-map vs capability-union).
Kind ProfileKind
// Scope is the SOURCE-derived authority scope (Decision 1). It is not
// authored on the unit; the loader stamps it from the source registry.
Scope AuthorityScope
// Order is the VALUE-axis merge position of the contributing layer — lower
// merges first (lower precedence), so a higher-Order fragment wins scalar
// conflicts. The snapshot bridge sets it to the layer's index in the
// value-precedence-ordered stack (product → user → extends → repo →
// project-local), so imported (extends) fragments merge BELOW repo for VALUES
// exactly as the legacy resolveSnapshot does — independent of the fragment's
// AUTHORITY scope (which governs only locks). When Order ties (e.g. engine
// tests that leave it 0), the value-precedence rank of Scope breaks the tie.
Order int
// Selector constrains the context this fragment applies to (Decision 5).
Selector ProfileSelector
// Bundle is the config payload the profile contributes — a kind-agnostic
// object so one engine carries app_type facets, stage composition, and
// capability sets without a per-kind resolver.
Bundle map[string]any
// Authored marks a profile WRITTEN directly as a kind:profile unit (a
// user-authored profile declared in a layer's `profiles` block) as opposed to
// one SYNTHESIZED from the legacy execution_profile / stage_profiles surfaces
// (profile_migration.go). The two share one engine, but an authored profile is
// addressable and orderable distinctly from a derived one (see Key): a derived
// fragment is a projection of legacy config and must never collide in the
// addressable namespace with a profile a human wrote.
Authored bool
}
ConfigProfile is one kind:profile unit — a selector-scoped config fragment. Its identity is the absolute ref <source>:<name> (§15 D2). Its authority for precedence and permission-gating is SOURCE-derived (Decision 1): Scope is set from ref.source → source-registry → scope by the loader, NEVER self-declared by the unit. The selector governs WHERE the fragment applies; the scope governs HOW MUCH it is trusted — the two are independent (Decision 1).
func (ConfigProfile) IsAuthored ¶ added in v0.4.2
func (p ConfigProfile) IsAuthored() bool
IsAuthored reports whether the profile was authored directly (vs synthesized from the legacy execution_profile / stage_profiles surfaces).
func (ConfigProfile) Key ¶ added in v0.4.2
func (p ConfigProfile) Key() string
Key is the profile's stable, addressable identity for ordering and lookup. An authored profile is namespaced under authoredKeyPrefix so it is distinguishable from a synthesized profile (whose key is its bare ref) — two profiles with the same underlying ref but different provenance (authored vs derived) yield different keys and never alias one another.
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 EffectivePolicy ¶ added in v0.4.2
type EffectivePolicy struct {
// Precedence is the effective scope ordering low→high (later wins scalars).
Precedence []AuthorityScope
// Locks are the effective absolute constraints, each tagged with its owner.
Locks []ProfileLock
// Permissions is the effective Decision-2 cap (nil ⇒ no restriction).
Permissions *OverridePermissions
// Replaced records the scope that issued a replace (Q4), or "" when none did.
Replaced AuthorityScope
}
EffectivePolicy is the Phase-1 merged layering policy.
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 LayeringPolicy ¶ added in v0.4.2
type LayeringPolicy struct {
// Scope is the authority scope that owns this policy (set by the loader).
Scope AuthorityScope `json:"-"`
// Precedence orders scopes low→high for Phase-2 value resolution (later wins
// scalar conflicts). Empty ⇒ fall back to the canonical AUTHORITY-RANK order.
Precedence []AuthorityScope `json:"precedence,omitempty"`
// Locks are the absolute constraints this scope emits (Decision 4).
Locks []ProfileLock `json:"locks,omitempty"`
// OverridePermissions is the Decision-2 three-state cap. A nil pointer is the
// OMITTED state; presence is preserved (see OverridePermissions).
OverridePermissions *OverridePermissions `json:"override_permissions,omitempty"`
// Mode is the Q4 replace-mode marker (Decision 3). Empty ⇒ narrow.
Mode PolicyMode `json:"mode,omitempty"`
}
LayeringPolicy is a scope-attached policy unit (§2.3 Phase 1): it governs how profile fragments merge across scopes. A higher-authority scope's policy binds lower ones — its precedence wins, its locks are absolute, and its override_permissions cap what lower scopes may set. Scope is SOURCE-derived (set by the loader from the owning layer/source), never authored on the unit.
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 LockCollision ¶ added in v0.4.2
type LockCollision struct {
Field string `json:"field"`
Attempted any `json:"attempted"`
Winning any `json:"winning"`
Owner AuthorityScope `json:"owner"`
OwnerRank int `json:"owner_rank"`
Kind string `json:"kind"`
}
LockCollision records a rejected lower-scope write surfaced through `da config explain` (§15.9 item 2). It carries the attempted value, the winning (locked) value, and the owning scope.
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 ManifestSpec ¶ added in v0.4.2
type ManifestSpec struct {
// Sources are the canonical source refs the manifest pulls
// (<source>:<path>@<version>, §15 D2). They are NAMES only — a private source
// carries no credential (F6/NEW-FORK-B): auth is threaded ambient-first at
// init --from, never embedded here.
Sources []string `json:"sources,omitempty"`
// Binds are the refs of the layering_policy + profile units that bind (R2b).
// Empty ⇒ the manifest binds whatever policy the scope chain resolves; a
// non-empty set narrows the bound fragments to exactly those refs.
Binds []string `json:"binds,omitempty"`
// ProjectSet is the OPTIONAL ref of a kind:project-set unit (R2c/D6). The
// project-set IS the home-config portable identity registry (built by L3); the
// manifest only references it. Empty ⇒ a manifest with no project-set, which is
// valid and resolves (item 3).
ProjectSet string `json:"project_set,omitempty"`
}
ManifestSpec is the AUTHORED payload of a kind:manifest unit (R2). It carries only REFERENCES — never re-defined copies — of the units it binds (D2/D6) and NO secrets, NO self-declared authority (authority is source-derived, D4/F6). This lenient typed shape is what AgentsRC round-trips; the loud fail-closed validation (manifest->manifest edge, self-blessing authority, force-allow) runs at resolve time in decodeManifest (R10), mirroring the layering_policy split.
func (*ManifestSpec) UnmarshalJSON ¶ added in v0.4.2
func (s *ManifestSpec) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes a manifest spec through the SAME fail-closed gate the resolve path uses (FIX C): a forbidden or unpinned field on the typed AgentsRC `manifests` map is rejected at load, never silently dropped before Save re-emits the typed field (the schema-usage.md typed-field/ExtraFields rule).
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 OverridePermissions ¶ added in v0.4.2
type OverridePermissions struct {
// contains filtered or unexported fields
}
OverridePermissions is the Decision-2 three-state permission map. The pointer itself distinguishes the three intents:
- a nil *OverridePermissions ⇒ OMITTED (no opinion; inherit-higher).
- a non-nil value with an empty byScope map ⇒ LOCKDOWN ({}): no scope may change anything.
- a non-nil value with a non-empty map ⇒ ALLOWLIST: a present scope may change exactly its listed field-paths ("*" = all); a scope absent from the map may change nothing.
The presence check (absent key vs present-empty-map) is preserved through a custom JSON round-trip so the schema and resolver both honor it.
func NewOverridePermissions ¶ added in v0.4.2
func NewOverridePermissions(m map[AuthorityScope][]string) *OverridePermissions
NewOverridePermissions builds an allowlist/lockdown permission map. A nil or empty map is the lockdown state ({}); a non-empty map is an allowlist.
func (OverridePermissions) MarshalJSON ¶ added in v0.4.2
func (o OverridePermissions) MarshalJSON() ([]byte, error)
MarshalJSON emits the bare scope→fields map (or {} for lockdown), so a round-trip preserves the present-but-empty (lockdown) state distinctly from an omitted field (the omitempty on the pointer handles omitted).
func (*OverridePermissions) UnmarshalJSON ¶ added in v0.4.2
func (o *OverridePermissions) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes the scope→fields map. A present `{}` decodes to a non-nil empty map (lockdown); the omitted case never reaches here because the field is a pointer.
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 PolicyLockSpec ¶ added in v0.4.2
type PolicyLockSpec struct {
// ValueLocks pins a field path to a value. A lower-authority scope that
// writes a different value has its write rejected (the lock wins).
ValueLocks map[string]json.RawMessage `json:"value_locks,omitempty"`
// DenyLocks subtracts set members, each "field:member" (e.g.
// "skills:risky-skill"). deny-overrides: a lower-scope union-add of the
// member is dropped.
DenyLocks []string `json:"deny_locks,omitempty"`
// ForceAllow is ALWAYS invalid. Any entry is a resolve-time validation
// error — the no-force-allow invariant.
ForceAllow []string `json:"force_allow,omitempty"`
}
PolicyLockSpec is a layer's declared §15 D1a authority-axis locks. A layer may value-lock a field (pin its value, rejecting lower-scope writes) or deny-lock a set member (force it out, with deny-overrides). force_allow is INVALID — its presence is a validation error (there is no force-allow: a lower scope can never punch a capability through a higher deny).
func (*PolicyLockSpec) IsZero ¶ added in v0.4.2
func (p *PolicyLockSpec) IsZero() bool
IsZero reports whether the spec declares nothing.
type PolicyMode ¶ added in v0.4.2
type PolicyMode string
PolicyMode is the Q4 named replace-mode marker (Decision 3). The default is narrow (merge that may only tighten); replace supersedes the inherited precedence + permissions as a visible, named act.
const ( // PolicyModeNarrow (the default, also the zero value when authored absent) is // monotone-narrowing: a higher scope may tighten precedence/permissions and // add locks, never broaden. PolicyModeNarrow PolicyMode = "narrow" // PolicyModeReplace declares "this policy supersedes, not merges": the // replacing scope's precedence + override_permissions wholly replace the // inherited ones. Locks still accumulate (they are absolute invariants and a // replace can never drop a lower scope's lock — that would broaden). PolicyModeReplace PolicyMode = "replace" )
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 ProfileConflict ¶ added in v0.4.2
type ProfileConflict struct {
Field string `json:"field"`
Scope AuthorityScope `json:"scope"`
Winner string `json:"winner"`
Refs []string `json:"refs"`
}
ProfileConflict is a same-scope value conflict (Decision 6): two fragments at the same authority disagree on a field; both refs are surfaced.
type ProfileContext ¶ added in v0.4.2
type ProfileContext struct {
Role string
AppType string
Stage string
Harness string
ScopeChain []AuthorityScope
}
ProfileContext is the dispatch context resolution runs against. ScopeChain is the set of authority scopes present, low→high; a profile or policy whose scope is not in the chain does not contribute.
type ProfileDriftKind ¶ added in v0.4.2
type ProfileDriftKind string
ProfileDriftKind classifies one field's drift between a projection and the freshly resolved bundle.
const ( // DriftChanged means the field is present on both sides with different values // (the resolution moved a value the projection still carries the old form of). DriftChanged ProfileDriftKind = "changed" // DriftAdded means the field is present in the resolved bundle but ABSENT from // the projection (the resolution grew a field the stale projection is missing). DriftAdded ProfileDriftKind = "added" // DriftRemoved means the field is present in the projection but ABSENT from the // resolved bundle (the projection carries a field the resolution dropped). DriftRemoved ProfileDriftKind = "removed" )
type ProfileDriftResult ¶ added in v0.4.2
type ProfileDriftResult struct {
// HasDrift is true when the projection is stale: the source digest moved OR a
// field-level difference exists between the projection and the resolved bundle.
HasDrift bool `json:"has_drift"`
// DigestMatch reports whether the projection's recorded source digest still
// equals the freshly resolved digest (Decision 7) — the cheap primary signal.
DigestMatch bool `json:"digest_match"`
// ProjectedDigest is the projection's recorded source digest (stale when drift).
ProjectedDigest string `json:"projected_digest"`
// ResolvedDigest is the freshly resolved bundle digest.
ResolvedDigest string `json:"resolved_digest"`
// Changes is every drifted leaf, sorted by path. Empty when the projection is
// byte-current with the resolution.
Changes []ProfileFieldDrift `json:"changes,omitempty"`
}
ProfileDriftResult is the outcome of comparing a projection against the freshly resolved profile. HasDrift is the single boolean `da config verify` branches on; Changes enumerates every drifted leaf in deterministic (sorted-path) order.
func DetectProfileDrift ¶ added in v0.4.2
func DetectProfileDrift(projection ProfileProjection, resolved ResolvedProfile) ProfileDriftResult
DetectProfileDrift compares a materialized projection against the profile the engine now resolves and reports the drift (R7). Drift is signalled two ways, both surfaced: the recorded source digest no longer matches the fresh resolution digest (the cheap Decision-7 check), AND/OR a field-level difference exists between the projection's bundle and the resolved bundle. HasDrift is the disjunction so a tampered projection whose digest was left stale-equal is still caught by the value diff.
func DetectProfileDriftFromSnapshot ¶ added in v0.4.2
func DetectProfileDriftFromSnapshot(projection ProfileProjection, snap *Snapshot, role, appType, stage, harness string) (ProfileDriftResult, error)
DetectProfileDriftFromSnapshot resolves the given dispatch context on the live §15 + L1 substrate and compares a stored projection against it — the single readback path `da config verify` calls to flag a stale projection without the caller re-implementing resolution. A resolve error (a malformed policy or a fatal authority violation) propagates fail-closed, exactly as the engine raises it.
type ProfileFieldDrift ¶ added in v0.4.2
type ProfileFieldDrift struct {
// Path is the dot-path of the drifted leaf.
Path string `json:"path"`
// Kind classifies the drift (changed / added / removed).
Kind ProfileDriftKind `json:"kind"`
// Projected is the value the materialized projection carries (nil for added).
Projected any `json:"projected,omitempty"`
// Resolved is the value the engine now resolves (nil for removed).
Resolved any `json:"resolved,omitempty"`
}
ProfileFieldDrift is one drifted leaf: its dot-path, the kind of drift, and the two sides' values so a caller renders a precise "what changed" diagnostic.
type ProfileKind ¶ added in v0.4.2
type ProfileKind string
ProfileKind labels a profile category for inspectability (§2.2). The kind governs which selector keys are meaningful and which bundle-merge discipline applies; it does NOT introduce a second resolution path.
const ( // ProfileKindAppType re-expresses today's execution_profile.by_app_type // entries: selector key app_type, bundle = the AppTypeProfile facets, merged // by deep map-merge (matching the legacy execution_profile merge for zero // behavioral diff, §5). ProfileKindAppType ProfileKind = "app_type" // ProfileKindStage re-expresses today's stage_profiles entries: selector keys // stage (and role where relevant), bundle = the StageProfile, deep map-merge. ProfileKindStage ProfileKind = "stage" // ProfileKindAgentCapability is the new kind for runtime-role capability // bundles (tools/skills/hooks/mcp): additive sets union, deny subtracts. ProfileKindAgentCapability ProfileKind = "agent-capability" )
type ProfileLock ¶ added in v0.4.2
type ProfileLock struct {
// Field is the dot-path the lock governs (e.g. "tools.allow", "model").
Field string `json:"field"`
// Deny lists set members forced OUT of Field and forbidden from lower-scope
// re-grant (the deny-lock). Mutually exclusive with Value.
Deny []string `json:"deny,omitempty"`
// Value pins Field to a fixed scalar (the value-lock). A lower scope writing
// a different value is rejected.
Value json.RawMessage `json:"value,omitempty"`
// Selector optionally scopes the lock to a context (empty = all contexts).
Selector ProfileSelector `json:"selector,omitempty"`
// Owner is the authority scope that declared the lock (set by the resolver,
// not authored). Higher owner binds lower scopes.
Owner AuthorityScope `json:"-"`
}
ProfileLock is a layering-policy lock in the profile engine. It reuses the §15 lock vocabulary — deny-lock and value-lock ONLY, no force-allow (Decision 4) — extended with an optional selector tail so a lock can be context-scoped (e.g. deny Edit/Write only @role:reviewer). A lock is absolute: permission never beats it (Decision 4).
type ProfileLockDelta ¶ added in v0.4.2
type ProfileLockDelta struct {
// Key is the profile's namespaced addressable key (authored profiles carry the
// "authored:" prefix), matching how the lock units are keyed.
Key string
Status ProfileLockStatus
LockDigest string
LiveDigest string
}
ProfileLockDelta is one profile unit's reproducibility record: its namespaced key, the classification, and the two digests so a caller renders a one-line diagnostic.
func ProfileLockReproducibility ¶ added in v0.4.2
func ProfileLockReproducibility(locked map[string]LockedUnit, set ProfileSet) []ProfileLockDelta
ProfileLockReproducibility compares a ProfileSet against the profile units recorded in a lock and reports, per ref, whether the resolution reproduces from the lock (R2). A lock whose profile units all report ProfileLockOK certifies that the effective bundle is reproducible WITHOUT re-resolving — the §15 content-hash staleness model applied to profiles. Non-profile lock entries are ignored. Records are keyed by the profile's NAMESPACED key (so an authored and a synthesized fragment sharing a ref are tracked independently, never aliased) and returned sorted by key for deterministic rendering.
type ProfileLockStatus ¶ added in v0.4.2
type ProfileLockStatus string
ProfileLockStatus classifies one profile unit's reproducibility state against the lock.
const ( // ProfileLockOK means the current fragment's content digest matches the lock — // the prior resolution reproduces from the lock without re-deriving the unit. ProfileLockOK ProfileLockStatus = "ok" // ProfileLockMissing means a current fragment has no entry in the lock (a new // profile that was never locked). ProfileLockMissing ProfileLockStatus = "missing-from-lock" // ProfileLockStale means the locked entry's digest no longer matches the // current fragment (the fragment was edited since it was locked). ProfileLockStale ProfileLockStatus = "stale" // ProfileLockExtra means the lock carries a profile unit no longer present in // the current set (a removed fragment whose lock entry lingers). ProfileLockExtra ProfileLockStatus = "extra-in-lock" )
type ProfileProjection ¶ added in v0.4.2
type ProfileProjection struct {
// SourceDigest is ResolvedProfile.Digest at projection time — the pin the
// drift check compares a fresh resolution against.
SourceDigest string `json:"source_digest"`
// Refs is the sorted set of absolute refs that contributed to the projected
// bundle, captured so a provenance change (Decision 7) registers as drift even
// when values coincide.
Refs []string `json:"contributing_refs"`
// Bundle is the canonical (deep-copied) effective fragment that was
// materialized. It is independent of the source ResolvedProfile so a later
// mutation of either side does not silently alias the other.
Bundle map[string]any `json:"bundle"`
}
ProfileProjection is the materialized rendering of a resolved profile. It carries the SOURCE digest (the resolved bundle's Decision-7 digest at projection time) and the contributing refs so a later check can tell whether the inputs that produced it have since moved (R7) — the projection is downstream of the resolution, never its authority (R8).
func ProjectProfile ¶ added in v0.4.2
func ProjectProfile(resolved ResolvedProfile) ProfileProjection
ProjectProfile materializes a resolved profile into a projection (R8). The bundle is DEEP-COPIED so the projection is a self-contained snapshot — editing the source resolution afterward cannot mutate an already-materialized projection, which is what lets drift detection compare the two independently.
func (ProfileProjection) Allowlist ¶ added in v0.4.2
func (p ProfileProjection) Allowlist(path string) []string
Allowlist extracts the string-set at a dot-path leaf of the projected bundle (e.g. "tools.allow", "skills.allow") in sorted order — the concrete shape a harness allowlist projection (the orchestrator Skill-scoping dogfood, §5.3) consumes. A path that is absent, or whose leaf is not a string array, yields an empty slice so a missing projection reads as "grants nothing" rather than panicking.
type ProfileSelector ¶ added in v0.4.2
type ProfileSelector struct {
Role string `json:"role,omitempty"`
AppType string `json:"app_type,omitempty"`
Stage string `json:"stage,omitempty"`
Harness string `json:"harness,omitempty"`
}
ProfileSelector constrains the dispatch context a fragment applies to. Each present key is matched EXACTLY; an absent (empty) key is a wildcard (Decision 5). The zero value matches every context.
type ProfileSet ¶ added in v0.4.2
type ProfileSet struct {
Profiles []ConfigProfile
Policies []LayeringPolicy
}
ProfileSet is the parsed input: all profiles and all layering policies.
func ProfileSetFromSnapshot ¶ added in v0.4.2
func ProfileSetFromSnapshot(snap *Snapshot) (ProfileSet, error)
ProfileSetFromSnapshot derives the profile engine input from a resolved snapshot. Each present layer contributes its execution_profile.by_app_type and stage_profiles entries as kind:profile units and, when present, its layering_policy unit — all carried at the layer's EFFECTIVE authority scope (base scope, upgraded by any §15 authority_grant the stack confers, FIX 3). A malformed layering_policy or authority_grants block on any layer is a fail-closed error (R9), surfaced rather than silently dropped.
type Project ¶
type Project struct {
// RepoID is the canonical git repo_id used as the portable rebind key.
// It is recorded ONLY when the project's git origin is unambiguous (R12);
// otherwise it is empty and rebinding falls back to the logical id (the
// registry map key).
RepoID string `json:"repo_id,omitempty"`
}
Project is one row of the SYNCED portable identity registry (§15 D13/A2 project-set unit). The stable id is the map key; RepoID is the portable rebind key (FORK-1 hybrid). It carries NO absolute path — paths live in the machine-local binding table (Binding), never in the synced config.json.
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 ResolvedLockInfo ¶ added in v0.4.2
type ResolvedLockInfo struct {
Field string `json:"field"`
Kind string `json:"kind"`
Owner AuthorityScope `json:"owner"`
Deny []string `json:"deny,omitempty"`
Value any `json:"value,omitempty"`
}
ResolvedLockInfo is the explain-surfaced shape of a binding lock.
type ResolvedManifest ¶ added in v0.4.2
type ResolvedManifest struct {
// Ref is the manifest's absolute identity.
Ref string `json:"ref"`
// Scope is the source-derived authority the manifest resolved at.
Scope AuthorityScope `json:"scope"`
// Sources is the sorted referenced source-set the manifest pulls (R3 step 1).
Sources []string `json:"sources"`
// Policy is the bound layering policy + resolved fragment, produced by the L1
// ResolveProfile engine — composed, not forked (R3 step 2 / DC6).
Policy ResolvedProfile `json:"policy"`
// ProjectSet is the referenced project-set ref, or "" when the manifest has
// none (item 3 — a manifest without a project-set still resolves).
ProjectSet string `json:"project_set,omitempty"`
// HasProjectSet distinguishes "no project-set referenced" from a future empty
// ref, so consumers (L3 init --from) branch explicitly rather than on "".
HasProjectSet bool `json:"has_project_set"`
// Digest is the transitive-pin digest over the manifest ref, the referenced
// ref-set, and the resolved policy digest (F5): "same digest ⇒ identical
// setup" (R4).
Digest string `json:"digest"`
}
ResolvedManifest is the resolution output for one manifest (item 2): its referenced source-set, the bound layering policy (resolved through the L1 engine), the optional project-set ref, and the transitive-pin digest (F5).
func ResolveManifest ¶ added in v0.4.2
func ResolveManifest(m ConfigManifest, set ProfileSet, ctx ProfileContext) (ResolvedManifest, error)
ResolveManifest resolves one manifest unit on the §15 + L1 substrate (item 2). It does NOT fork resolution: the bound layering policy is produced by the L1 ResolveProfile engine over the profiles/policies the manifest binds, and the source-set + project-set ref are carried through unchanged. The transitive-pin digest (F5) covers the manifest ref, the referenced ref-set, AND the resolved policy digest — so it moves whenever a referenced ref OR a referenced unit's resolved version changes, giving "same manifest digest ⇒ identical setup" (R4).
A fatal L1 authority violation (force-allow, self-blessing, overlapping lock) propagates as an error fail-closed, exactly as the L1 engine raises it — the manifest reuses that invariant rather than re-checking it.
func ResolveManifestFromSnapshot ¶ added in v0.4.2
func ResolveManifestFromSnapshot(snap *Snapshot, ref, role, appType, stage, harness string) (ResolvedManifest, error)
ResolveManifestFromSnapshot derives the manifest + profile sets from a resolved snapshot, locates the manifest by absolute ref, and resolves it against the snapshot's scope chain — the single readback path that resolves a manifest on the live §15 + L1 substrate (composes both engines, forks neither). A ref that names no declared manifest is a loud not-found error, never a silent empty resolve.
func ResolveUserScopeManifests ¶ added in v0.4.2
func ResolveUserScopeManifests() ([]ResolvedManifest, error)
ResolveUserScopeManifests resolves every kind:manifest unit declared in the resolved user scope to its referenced source-set, bound layering policy, and optional project-set ref — the inputs `init --from` reports and reproduces a whole setup from (D-D/R1). It COMPOSES the engines, forking none: §15 source/scope resolution builds the snapshot, the L1 policy engine resolves the bound fragments inside ResolveManifest, and L2's ResolveManifest carries the source-set + project-set ref through unchanged.
An empty result is valid — a home may declare no manifest and still adopt (item 3 of ResolvedManifest). A fail-closed authority/validation error from any engine (force-allow, self-blessing, malformed ref) propagates rather than resolving silently to nothing.
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 ResolvedProfile ¶ added in v0.4.2
type ResolvedProfile struct {
// Bundle is the effective config fragment for the context.
Bundle map[string]any `json:"bundle"`
// Contributing is the sorted set of absolute refs that contributed.
Contributing []string `json:"contributing_refs"`
// Locks is the effective binding lock set (owner-tagged), as resolved by the
// §15 authority pass.
Locks []ResolvedLockInfo `json:"locks"`
// Collisions is the §15 lock collisions: each lower-scope write a value-lock or
// deny-lock rejected, with attempted/winning/owner provenance.
Collisions []LockCollision `json:"collisions,omitempty"`
// Permissions is the effective permission map (scope→fields), nil-omitted.
Permissions map[AuthorityScope][]string `json:"permissions,omitempty"`
// Conflicts records same-scope value conflicts: both contributors are shown,
// not just the winner (Decision 6).
Conflicts []ProfileConflict `json:"conflicts,omitempty"`
// PolicyMode is "replace" when a scope superseded the inherited policy (Q4),
// else "narrow".
PolicyMode PolicyMode `json:"policy_mode"`
// ReplacedBy names the scope that issued a replace, when PolicyMode==replace.
ReplacedBy AuthorityScope `json:"replaced_by,omitempty"`
// Digest hashes bundle + contributing refs + effective policy version
// (Decision 7).
Digest string `json:"digest"`
}
ResolvedProfile is the Phase-2 output for a context.
func ResolveProfile ¶ added in v0.4.2
func ResolveProfile(set ProfileSet, ctx ProfileContext) (ResolvedProfile, error)
ResolveProfile runs both phases for a context. Input order of profiles and policies does NOT affect the output (H1). It is THIN over the §15 substrate: it SELECTS the matching fragments and orders them on the VALUE axis, then hands the value-merged bundle to the §15 authority pass (runAuthorityPass + applyValueLocks/applyDenyLocks) for the lock/grant axis — it does not re-implement locks, grants, or deny-provenance. A fatal authority violation (force-allow, overlapping lock, self-blessing grant) is returned as an error, fail-closed, exactly as the §15 layer resolver does.
func ResolveProfileContext ¶ added in v0.4.2
func ResolveProfileContext(snap *Snapshot, role, appType, stage, harness string) (ResolvedProfile, error)
ResolveProfileContext is the high-level entry the explain surface calls: derive the engine input from the snapshot and resolve the given dispatch context against the snapshot's scope chain. It is the single readback path that makes `da config explain` the profile truth surface (R6).
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 ScopeOrdering ¶ added in v0.4.2
type ScopeOrdering struct {
// AuthorityRank is the Phase-1 total order (highest authority first): who
// may emit locks/caps. deny-overrides; higher binds lower.
AuthorityRank []AuthorityScope `json:"authority_rank"`
// ValuePrecedence is the Phase-2 value-merge order (lowest precedence
// first): whose VALUE wins within the locks Phase 1 leaves surviving. This
// is the verbatim preservation of the original D1 chain.
ValuePrecedence []AuthorityScope `json:"value_precedence"`
}
ScopeOrdering names the TWO co-existing orderings the §15 D1a scope axis carries. They are deliberately two explicitly-named fields (§15.9 item 6, F1.1) — NOT one reused `scope_chain` — because they govern different questions and run in two resolver phases.
func CanonicalScopeOrdering ¶ added in v0.4.2
func CanonicalScopeOrdering() ScopeOrdering
CanonicalScopeOrdering returns the one canonical §15 D1a ordering. It is a fixed substrate constant, NOT a manifest-overridable field: letting a lower scope redefine authority_rank would be an authority-escalation hole, so the orderings live in code, not in user-writable config.
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"`
// LockCollisions holds the §15 D1a authority-pass rejections: a lower-scope
// write a higher-scope value-lock or deny-lock rejected. Each entry carries
// the attempted value, the winning (locked) value, and the owning scope so
// `da config explain` can surface why the lower write did not win. Always
// non-nil so JSON marshals to [].
LockCollisions []LockCollision `json:"lock_collisions"`
// AuthorityViolations holds non-fatal §15 D1a authority-pass notes (an inert
// public grant, a zero-authority lock that bound nothing). Fatal violations
// (self-blessing, force-allow) abort the resolve instead of landing here.
// Always non-nil so JSON marshals to [].
AuthorityViolations []AuthorityViolation `json:"authority_violations"`
}
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 UserScopeSnapshot ¶ added in v0.4.2
UserScopeSnapshot resolves the user-scope configuration: product defaults merged with the user-local layer (~/.agents/.agentsrc.json), with NO project in the chain. There is no project being resolved during `init --from`, so the repo-local layer is deliberately absent — unlike FlatResolver.Resolve, which requires a repo-local manifest and is fatal without one. A missing user-local layer is not an error (a home may scaffold one lazily); the snapshot then carries only product defaults.
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
// ProfileUnits is the kind:profile contribution (R2): the resolved profile
// fragments projected to LockedUnit entries (ProfileLockUnits), keyed by the
// profile's namespaced key. It is folded INTO the persisted "units" section by
// WriteUnitsLock so a kind:profile unit is a first-class lock entry alongside
// layer/artifact units — making a profile resolution reproducible from the lock
// without re-resolving. Nil for a caller that records no profiles (the section
// then carries only Units, exactly as before).
ProfileUnits map[string]LockedUnit
}
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
- authority.go
- authority_apply.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
- homeconfig_init.go
- layer_schema.go
- local_source.go
- lock_units.go
- lockstatus.go
- manifest.go
- migrate.go
- overlay.go
- paths.go
- precondition_resolve.go
- profile.go
- profile_drift.go
- profile_lockfile.go
- profile_locks.go
- profile_migration.go
- profile_projection.go
- profile_resolver.go
- profile_snapshot.go
- proposals.go
- resolve_locked.go
- resolver.go
- snapshot.go
- staleness.go
- unit_kinds.go
- verify_layers.go