Documentation
¶
Overview ¶
Package capability holds the built-in admission rules (the pure leaf): given an Event + Projection it returns a RuleDecision, never writing. It imports rule/projection/contract only — binding->rule translation and runtime wiring live in app. Memory + skill are the two P0 capabilities.
Index ¶
- Constants
- func EmbeddedCatalog() map[string]Capability
- func ImportableKinds(catalog map[string]Capability) []contract.ResourceKind
- func LoadExternal(fsys fs.FS, requiredFields map[contract.ResourceKind][]string) (map[string]Capability, error)
- func RemoteCommitEventType(catalog map[string]Capability, kind contract.ResourceKind) (string, bool)
- func RemoteImportRule(cap Capability, principal contract.ActorID) (rule.Rule, bool)
- func RemoteImportRules(catalog map[string]Capability, principal contract.ActorID) []rule.Rule
- func ResolveCatalog(projectRoot string, requiredFields map[contract.ResourceKind][]string) (map[string]Capability, error)
- func RiskEvidenceGate(cap Capability, principal contract.ActorID) rule.Rule
- func RiskOperatorGate(cap Capability, principal contract.ActorID) rule.Rule
- func ShapeByBudget(cap Capability, fields map[string]any, tier contract.BudgetTier) map[string]any
- func SyncImportSkippedRule(principal contract.ActorID) rule.Rule
- type Capability
- type CapabilitySpec
- type ContentRender
- type FieldSpec
- type Item
- type Limits
- type RenderSpec
- type SyncOptions
- type SyncSpec
- type ValidatorRef
Constants ¶
const ( BudgetWarmItems = 8 BudgetDigestItems = 1 )
Budget item caps per tier (P4b). REDUCER-FREE by construction: a tier bounds the COUNT of items the local mirror renders (most-recent-first), never a model summary (which would be a reducer — out of scope / B1, the no-remote-reducer entry decision). "digest-only" is therefore the minimal recent-context tier (the single latest item), "warm" a bounded recent window, "hot" the full set. A true semantic-summary digest is a sync-abi-v2 / reducer concern, deliberately deferred.
const ( MemoryWriteCandidateObserved = "memory.write_candidate.observed" MemoryWriteProposed = "memory.write.proposed" )
const ( SkillWriteCandidateObserved = "skill.write_candidate.observed" SkillWriteProposed = "skill.write.proposed" )
const SyncImportSkippedObserved = "sync.import_skipped.observed"
SyncImportSkippedObserved is the observation a sync puller ingests for a pulled commit whose resource kind has no import mapping (v1.1 #4): instead of a silent continue, the skip enters the canonical log exactly-once (ExternalID = the six-part pull key + ":skipped") and the deny rule below turns it into a durable sync.diagnostic via the existing pre-gate. Payload: {kind, origin_replica_id, local_decision_id, remote_id}.
Variables ¶
This section is empty.
Functions ¶
func EmbeddedCatalog ¶
func EmbeddedCatalog() map[string]Capability
EmbeddedCatalog returns the first-party capability catalog: the embedded half of ResolveCatalog and the backward-compatible default when no resolved catalog is supplied.
func ImportableKinds ¶
func ImportableKinds(catalog map[string]Capability) []contract.ResourceKind
ImportableKinds returns the resource kinds the catalog imports from Remote Workspace pulls, sorted — the descriptor-derived syncable-kind set (PD6).
func LoadExternal ¶
func LoadExternal(fsys fs.FS, requiredFields map[contract.ResourceKind][]string) (map[string]Capability, error)
LoadExternal compiles every external capability package under fsys. Each TOP-LEVEL directory is one package — `<name>/capability.json` in capability-spec-v1 form, strict-decoded through the same decodeSpec + FromSpec machinery the embedded loader uses — loaded in lexicographic package order (deterministic). Non-directory top-level entries (stray files, .DS_Store) are not packages and are ignored.
fs.ErrNotExist on the root listing is the NORMAL pre-stage-5 install: empty catalog, never an error — a missing .mnemon/loops must not break a single existing installation.
Everything else fails closed with the package path in the message: ① bad JSON / trailing garbage / unknown JSON keys (decodeSpec); ② unknown spec vocabulary and ③ a resource kind outside contract.KindCatalog (FromSpec); ⑥ any hooks/ or skills/ presence (no host projection assets in v1 — deliberately WIDER than loop-package-v1's minimum obligation); ⑦ statically derived header keys that cannot satisfy requiredFields (the kernel SchemaGuard lockstep, at LOAD time); ⑧ unsafe spec surfaces, external only, in two halves — VALUES are scanned (scanExternalSpecText), IDENTIFIERS are pattern-locked (checkExternalSpecIdentifiers); ⑨ directory ≠ name ≠ kind or an off-pattern directory name; ⑪ a kernel-internal reserved kind (externalReservedKinds). Classes ④⑤ (shadowing/dups beyond one package) are enforced by the shared specRegistry here and the four-axis merge in ResolveCatalog; class ⑩ (symlinks) needs the real OS path — fs.FS has no lstat — and lives in ResolveCatalog's screening.
func RemoteCommitEventType ¶
func RemoteCommitEventType(catalog map[string]Capability, kind contract.ResourceKind) (string, bool)
RemoteCommitEventType returns the import observation event type for a pulled commit kind when the catalog imports that kind — the descriptor-derived replacement for the hardcoded kind→type switch.
func RemoteImportRule ¶
RemoteImportRule builds the remote-import admission rule for one importable capability and the sync import principal: it observes the capability's system-derived <kind>.remote_commit.observed event and dispatches to the capability's declared (closed-set) merge strategy. Returns ok=false when the capability is not importable (the caller skips it).
func RemoteImportRules ¶
RemoteImportRules builds the remote-import rules for every importable capability in the catalog, sorted by kind for determinism — the descriptor-derived replacement for the hardcoded memory/skill import-rule list (PD6).
func ResolveCatalog ¶
func ResolveCatalog(projectRoot string, requiredFields map[contract.ResourceKind][]string) (map[string]Capability, error)
ResolveCatalog builds the boot capability catalog: the embedded Builtins plus every external capability package under <projectRoot>/.mnemon/loops — the ONLY external root in v1. It is the one production entry point: symlink screening on the real path (class ⑩), LoadExternal over os.DirFS, then a merge with FOUR-axis shadowing rejection (name, observed type, proposed type, resource kind) — an external package may never shadow an embedded capability, and two externals may not share a kind. A missing .mnemon/loops resolves to the embedded catalog.
func RiskEvidenceGate ¶
func RiskEvidenceGate(cap Capability, principal contract.ActorID) rule.Rule
RiskEvidenceGate is the mid-risk governance gate (P3 three-tier risk): a candidate for this capability's kind must carry a non-empty `evidence` field, else it is DENIED with a durable diagnostic. It is a SEPARATE rule that handles the same observed type as the admission rule; when it denies, rule.Evaluate's deny-priority reduction makes the deny outrank the admission rule's propose, so the write is refused — no new kernel verdict or held state (M1 review correction). It gates on the cap's principal (a foreign principal's event passes through) and emits no proposal.
High-risk (operator-only) gating is deferred to P3e, where its consumer (the high-risk loopdef kind) and its principal model (the human@owner operator binding, G9) are designed together — a high-risk gate without an operator principal to exempt would make a kind ungovernable.
func RiskOperatorGate ¶
func RiskOperatorGate(cap Capability, principal contract.ActorID) rule.Rule
RiskOperatorGate is the high-risk governance gate (P3e): it DENIES the gated principal's candidate with a durable diagnostic — the agent's high-risk proposal lands in the Inbox, and a human/operator (a control-agent principal) re-submits the same candidate through the normal admission path. The assembler builds this gate ONLY for NON-operator (host-agent) principals, so the operator's own high-risk candidate is never gated. Like the evidence gate, the deny outranks the admission propose (rule.Evaluate is deny-priority) — no new kernel verdict or held state (the M1 correction).
func ShapeByBudget ¶
func ShapeByBudget(cap Capability, fields map[string]any, tier contract.BudgetTier) map[string]any
ShapeByBudget returns the resource fields shaped for a context-budget tier: it keeps only the most-recent K items (K per tier; hot = all) and RE-RENDERS the capability's header over the kept subset, so a content-rendered surface — e.g. the memory mirror, which reads the rendered `content` field, not the raw item list — actually shrinks. "Most-recent" = the tail of the item list, whose order is the local append/import sequence (replica-deterministic, so an offline replay reshapes identically — B6). Non-item kinds, an unknown tier, and an already-within-budget set are returned UNCHANGED (exact passthrough preserves updated_by and any header the writer set; unknown fails open to hot — never silently drops data, the closed-set guard lives at config time in ResolveBudgetTier).
This is a pure LOCAL presentation transform: it never reduces on the hub and never alters authority (the grant scope is the security boundary — budget bounds CONTEXT only; B2 remote settles, local decides).
func SyncImportSkippedRule ¶
SyncImportSkippedRule is the legal diagnostic mechanism for skipped kinds: it Handles ONLY the skipped observation, gates on the sync import principal (foreign events pass through), and always denies with a reason naming the kind — the deny is what produces the durable *.diagnostic (S7); no write, no proposal.
Types ¶
type Capability ¶
type Capability struct {
Name string
ObservedType string
ProposedType string
ResourceKind contract.ResourceKind
ItemsField string // resource field holding the item list
Decode func(payload map[string]any) (Item, error)
Header func(items []Item) map[string]any // resource fields besides the item list + updated_by
// RequiredHeader is the kind's kernel-required header fields, derived from the spec (the
// render-produced keys, or the declared `required` subset). The assembler reads it to build the
// assembly-time SchemaGuard so a declared kind's required set has ONE source — the capability.
RequiredHeader []string
// Sync, when Importable, opts the kind into Remote Workspace import under the named (closed-set)
// merge strategy. The sync-import path derives its rules and syncable-kind set from this, so the
// importable kinds are no longer a hardcoded list (PD6).
Sync SyncOptions
// DefaultEnabled opts the kind into governance on every local boot without an explicit --loop
// (P3 coordination package). The app boot grants it to every host-agent principal.
DefaultEnabled bool
// Risk is the kind's governance risk tier ("low"|"mid"|"high"; P3). The assembler builds the
// matching risk-gate rule per binding (mid = evidence required; high = operator-only).
Risk string
Limits Limits
}
Capability is the built-in descriptor that turns config selection into one compiled rule kind. ALL built-in capabilities admit a candidate through the SAME generic append-item-to-resource rule (appendItemRule); they differ only by DATA — the observed/proposed types, the resource kind, the item-list field, how a payload decodes to an Item, and the resource "header" fields a write carries (e.g. memory's rendered content, skill's name). A new capability is a new descriptor + config, not new rule code.
func FromSpec ¶
func FromSpec(spec CapabilitySpec) (Capability, error)
FromSpec compiles a CapabilitySpec into a Capability, fail-closed on everything the spec gets wrong: unknown/missing core fields, a resource kind outside contract.KindCatalog, duplicate field names, unknown validator/render members, bad or extra member params, forward default-from references, list:strings sharing a field with other validators, and render keys colliding with the reserved items/updated_by keys.
The compiled Decode contract (parity-frozen, capability spec v1):
- ONLY declared fields are processed; payload keys outside the declared set NEVER enter the Item (no leakage into governed state).
- For each string field, in declaration order: raw = strings.TrimSpace(stringField(payload, name)); validators run in declared order against the processed value, first error rejects; the processed (trimmed/defaulted) value is what lands in the Item — and EVERY declared string field emits its key (possibly ""), matching the handwritten decoders.
- list:strings is the one exception: it uses stringSliceField's full semantics ([]string / []any dropping non-strings / comma-separated string; trimmed, empties compacted) and OMITS the key when the list is empty.
- Deny messages are protocol surface: "<name> candidate denied: <member message>".
func (Capability) RemoteCommitObserved ¶
func (c Capability) RemoteCommitObserved() string
RemoteCommitObserved is the event type the platform mints for a pulled remote commit of this kind (the system-derived sync-import observation form, capability-spec v2 grammar). The import rule observes it; the puller emits it.
func (Capability) Rule ¶
func (c Capability) Rule(principal contract.ActorID, ref contract.ResourceRef, limits Limits) rule.Rule
Rule builds the capability's admission rule for one principal + resource ref. limits bounds the capability (MaxPayloadBytes; 0 = unbounded — the 1 MiB channel body cap still applies upstream) without changing the compiled kind.
Deviation from the locked Phase-2 signature Rule(..., cfg config.CapabilityConfig) (plan-control-plane.md:241): the same plan locks capability as a rule/projection/contract-only leaf (:51,:61); the leaf wins, and the assembler maps config.CapabilityConfig -> Limits.
type CapabilitySpec ¶
type CapabilitySpec struct {
SchemaVersion int `json:"schema_version"` // capability spec v1
Name string `json:"name"`
ObservedType string `json:"observed_type"`
ProposedType string `json:"proposed_type"`
ResourceKind string `json:"resource_kind"`
ItemsField string `json:"items_field"`
Fields []FieldSpec `json:"fields"`
Render RenderSpec `json:"render"`
// Required SELECTS the kind's kernel-required header fields from the render-produced keys
// (capability-spec v2 §Declared kind). Omitted = every produced key is required; when present,
// each entry must be a render-produced key (a kind cannot require a field its writes never carry).
// It is the single source the assembly-time SchemaGuard derives a user kind's required set from.
Required []string `json:"required,omitempty"`
// Sync declares whether this capability's kind is imported from Remote Workspace pulls, and which
// CLOSED merge strategy the import uses (capability-spec v2 §Sync). Omitted = not importable.
Sync *SyncSpec `json:"sync,omitempty"`
// DefaultEnabled opts the kind into governance on EVERY local boot, without an explicit `--loop`
// (P3: the coordination package is on out of the box; memory/skill stay opt-in). The boot grants
// every host-agent principal the kind's observe + scope, so a default-enabled kind is governable
// from setup alone. Omitted = opt-in (enabled only when named in config.loops / a binding scope).
DefaultEnabled bool `json:"default_enabled,omitempty"`
// Risk is the kind's governance risk tier (P3, CLOSED set): "" / "low" = no gate; "mid" requires
// the candidate to carry non-empty `evidence`; "high" requires an operator (control-agent)
// principal — an agent's high-risk candidate is denied with a durable diagnostic (Inbox) and a
// human re-submits. The tier maps to a generated risk-gate rule (define≠select), never a new
// kernel verdict/state.
Risk string `json:"risk,omitempty"`
}
CapabilitySpec is the DATA form of a built-in capability: what a capability author declares in assets/capabilities/<name>.json. FromSpec compiles it against the two CLOSED catalogs (validators, renders) — a spec can only SELECT compiled members, never define behavior (A3/I8); anything unknown fails closed.
func LoadSpec ¶
func LoadSpec(fsys fs.FS, name string) (CapabilitySpec, error)
LoadSpec reads capabilities/<name>.json from fsys and strictly decodes it into its DATA form, for consumers that need the spec itself rather than the compiled Capability (e.g. the SKILL payload-contract generator). It goes through decodeSpec — the one fail-closed decode path — so there is no second, weaker decoding scheme to drift from it.
type ContentRender ¶
type FieldSpec ¶
type FieldSpec struct {
Name string `json:"name"`
Validators []ValidatorRef `json:"validators,omitempty"`
}
type Item ¶
Item is one decoded, validated candidate as a field map. The generic kind stamps id/actor/ingest_seq onto it and appends it to a resource's item list.
type RenderSpec ¶
type RenderSpec struct {
Content *ContentRender `json:"content,omitempty"` // nil = no rendered content header
Static map[string]string `json:"static,omitempty"` // literal header fields
}
type SyncOptions ¶
type SyncSpec ¶
type SyncSpec struct {
Importable bool `json:"importable"`
Merge string `json:"merge"` // closed set: see syncMergeStrategies
}
SyncSpec is the sync-import descriptor: a kind opts into remote import (Importable) and selects a merge strategy from the CLOSED set. The strategies encapsulate the per-shape append/conflict logic; a kind SELECTS one, it never defines behavior (define≠select).