protocol

package
v1.28.6 Latest Latest
Warning

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

Go to latest
Published: Aug 15, 2026 License: Apache-2.0 Imports: 40 Imported by: 0

Documentation

Overview

bootpack_guards.go — the boot-owned mutation guards for the operator pack verbs.

A boot-declared operator skill baseline (internal/skills/bootpacks) is READ-ONLY from the control plane: the `agent_config.agent_packs.*` verbs govern the agent's DURABLE agent_packs revision, and a canonical name the boot baseline owns for the exact (tenant, agent) pair must never be reintroduced into that durable revision by any write path. The guards here are the single choke point the write verbs consume BEFORE any mutation:

  • upsert refuses a boot-owned name even when the submitted body hashes identically to the boot entry — an equal hash proves nothing, boot wins (the baseline is edited in the boot config and applied on the next deployment, never through the control plane);
  • every commit path (initial, response-loss replay, prepared/committing resume, and the publication/activation write itself) re-checks fresh ownership, so no proposal path can smuggle a boot-owned name into the durable revision;
  • remove may delete an ACTUAL legacy durable revision shadow (the pre-baseline durable copy of a now-boot-owned name) while leaving boot, but a boot-only name is a typed read-only refusal — never a false success;
  • the pure GuardBootOwnedRevision helper is what the generic rollback door invokes before repointing the active pointer at any revision that contains a boot-owned name.

The injected reader (the seam)

The guards consume a NARROW read-only reader, BootOwnership, keyed by exact (tenant, agent, canonical name). The eager immutable bootpacks.Index satisfies it directly (internal/skills/bootpacks — `OwnsName`). A nil reader means no boot baseline is bound on this runtime: every guard is inert and the verbs keep their exact pre-baseline behavior.

The reader is injected PER REQUEST through the context seam (WithBootOwnership / bootOwnershipFromContext), so the guard code holds no Service state, no package-level mutable state, and is safe for N concurrent requests under -race. The integration owner wires the concrete reader at the handler boundary — or, once the Service gains its boot-ownership field + option, re-points the SINGLE read inside bootOwnershipFromContext at that field (every verb goes through it).

Package protocol implements the admin-scoped `agent_config.*` Protocol methods the Console agent-config control plane consumes:

  • agent_config.get — read an agent's active config revision.
  • agent_config.set_revision — write a new immutable revision.
  • agent_config.list_revisions — the agent's revision chain.
  • agent_config.diff — server-side compare of two revisions.
  • agent_config.rollback — repoint the active pointer.
  • agent_config.skills.{list,upsert,delete} — skills control (the first consumer of the registry primitive; see skills.go).

Every write is admin-scoped (the verified `auth.ScopeAdmin` claim, enforced at the wire handler — the agent-config authorization model) and identity-mandatory. A config edit applies to the agent's NEXT run (next-turn projection — never mid-flight, per the concurrent-reuse contract).

The seam (CLAUDE.md §4.4)

The Service depends on the narrow `agentcfg.Registry` interface (the StateStore-backed concrete satisfies it) plus an optional `skills.SkillStore` for the durable/shared skills consumer and a narrow `SessionPersonalSkillController` for the agent-owned session tier; tests inject fakes. The Service owns wire validation + the wire↔domain mapping; the registry owns persistence + the revision events; the SkillStore owns the skill rows + the skill events.

Identity is mandatory (CLAUDE.md §6 rule 9)

Every method takes the wire request's IdentityScope. An incomplete triple fails closed with ErrIdentityRequired. The handler overlays the verified identity onto the request body, so a caller cannot target another identity's config through this surface.

Concurrent reuse

A constructed *Service is immutable after NewService and safe to share across N goroutines: it holds only the registry / skill-store references + a clock + logger. Per-call state lives in arguments and locals.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrAuthRequired — the attacher signals the MCP server needs
	// authorization. The service parks on the unified pause/resume primitive.
	// Concrete attachers wrap this so the service reaches it via errors.Is.
	ErrAuthRequired = errors.New("agentcfg/protocol: mcp connection requires authorization")
	// ErrConnectionAttachUnavailable — add_mcp_connection was called but no
	// ConnectionAttacher is wired on this runtime (→ 501).
	ErrConnectionAttachUnavailable = errors.New("agentcfg/protocol: mcp connection attach not wired on this runtime")
	// ErrStdioNotAllowed — a stdio add named a command absent from the
	// fail-closed allowlist (→ 403). Adding a stdio server is the most
	// privileged action; the allowlist is the §7 RCE gate.
	ErrStdioNotAllowed = errors.New("agentcfg/protocol: stdio mcp connection command is not allowlisted")
	// ErrInvalidConnection — the connection descriptor failed validation
	// (empty name, unknown transport, missing/incoherent command/URL) (→ 400).
	ErrInvalidConnection = errors.New("agentcfg/protocol: invalid mcp connection descriptor")
	// ErrCoordinatorUnavailable — an auth-required attach fired but no
	// pause/resume Coordinator is wired (→ 500). Fail loud rather than drop
	// the auth requirement silently (CLAUDE.md §13).
	ErrCoordinatorUnavailable = errors.New("agentcfg/protocol: pause/resume coordinator not wired for the mcp oauth path")
)

Add-connection sentinel errors. The wire handler maps each onto a canonical Protocol Code + HTTP status; in-process callers compare with errors.Is.

View Source
var (
	// ErrAgentPacksReadOnly rejects the generic whole-payload authoring door.
	// Pack bodies are accepted only by the dedicated verbs, which apply the
	// server provenance stamp and the pack invariants before persistence.
	ErrAgentPacksReadOnly = errors.New("agentcfg/protocol: agent packs require the dedicated pack verbs")
	// ErrAgentPacksInvalid — a pack verb received a malformed item, a
	// non-agent scope, a smuggled origin, a caller-supplied origin_ref, an
	// over-bounded pack, or an empty/over-bounded intent. Fails closed
	// before any revision write.
	ErrAgentPacksInvalid = errors.New("agentcfg/protocol: invalid agent pack")
	// ErrAgentPackProposeUnavailable — propose was called but no
	// AgentPackProposer is wired on this runtime (→ 501 at the wire edge).
	// The deterministic pack verbs stay live regardless.
	ErrAgentPackProposeUnavailable = errors.New("agentcfg/protocol: agent pack proposer not wired on this runtime")
	// ErrAgentPackProposalUnavailable means the durable proposal ledger was not
	// wired. A proposal must never be represented by forgeable client text.
	ErrAgentPackProposalUnavailable = errors.New("agentcfg/protocol: durable agent pack proposal ledger not wired")
	// ErrAgentPackProposalInvalid means the proposal token is absent, expired,
	// consumed, or bound to different server-side inputs.
	ErrAgentPackProposalInvalid = errors.New("agentcfg/protocol: invalid or consumed agent pack proposal")
	// ErrAgentPackHashMismatch — commit's ReviewedHash does not equal the
	// canonical content hash of the submitted body. The commit is refused
	// and NOTHING is persisted (the two-phase CAS half).
	ErrAgentPackHashMismatch = errors.New("agentcfg/protocol: pack commit hash does not match the reviewed hash")
	// ErrAgentPackProvenanceMismatch — commit's Provenance does not match
	// the deterministic proposal stamp for (agent, reviewed hash). Refused
	// before any write — a commit must echo the exact proposal it reviewed.
	ErrAgentPackProvenanceMismatch = errors.New("agentcfg/protocol: pack commit provenance does not match the proposal stamp")
	// ErrAgentPackNotFound — remove named a pack item the active revision
	// does not contain. Fails loud so a stale remove can never silently
	// no-op (dangling-membership prevention).
	ErrAgentPackNotFound = errors.New("agentcfg/protocol: pack item not found")
)

Agent-pack sentinel errors. The wire handler maps each onto a canonical Protocol code; in-process callers compare with errors.Is.

View Source
var (
	// ErrPreviewIdentityRequired — the request carried no verified identity
	// on ctx, an incomplete target triple, or an empty effective agent id.
	// Fails closed (identity is mandatory).
	ErrPreviewIdentityRequired = errors.New("agentcfg/protocol: composition preview requires a complete verified identity and effective agent")
	// ErrPreviewMisconfigured — the service was constructed without a
	// mandatory dependency (the agent-config reader or the boot-pack reader).
	ErrPreviewMisconfigured = errors.New("agentcfg/protocol: composition preview missing a mandatory dependency")
	// ErrPreviewSessionReachDenied — a PRESENT signed session_reach claim
	// does not contain the target session. Loud — the settled reach contract.
	ErrPreviewSessionReachDenied = errors.New("agentcfg/protocol: composition preview session reach denied")
	// ErrPreviewAgentReachDenied — the caller's signed agent_reach does not
	// contain the effective agent, or no reach is established on ctx (the
	// gate fails closed; an unwired gate is an honest "cannot verify reach",
	// never a silent widening).
	ErrPreviewAgentReachDenied = errors.New("agentcfg/protocol: composition preview agent reach denied")
)

Composition-preview sentinel errors. In-process callers compare with errors.Is; the wire handler maps each onto a canonical Protocol code.

View Source
var (
	// ErrConnectionNotFound — remove_mcp_connection named a connection that is
	// not in the agent's active revision (never runtime-added, or already
	// removed). No revision recorded, no event emitted (→ 404).
	ErrConnectionNotFound = errors.New("agentcfg/protocol: mcp connection not found in the agent's active revision")
	// ErrBootDeclaredConnection — remove_mcp_connection named a server that is
	// declared in the boot yaml (`tools.mcp_servers`). Distinct from
	// ErrConnectionNotFound: the verb governs revisioned state only; a
	// boot-declared server is edited in yaml + restart, not removed through
	// the control plane. No revision recorded, no event emitted (→ 400).
	ErrBootDeclaredConnection = errors.New("agentcfg/protocol: connection is boot-declared (yaml) — edit tools.mcp_servers and restart; the control plane governs runtime-added connections only")
)

Remove-connection sentinel errors. The wire handler maps each onto a canonical Protocol Code + HTTP status; in-process callers compare with errors.Is.

View Source
var (
	// ErrIdentityRequired — the request carried an incomplete identity
	// triple or an empty agent id. Fails closed.
	ErrIdentityRequired = errors.New("agentcfg/protocol: identity scope incomplete")
	// ErrMisconfigured — NewService was called with a nil registry.
	ErrMisconfigured = errors.New("agentcfg/protocol: NewService missing a mandatory dependency")
	// ErrSkillsUnavailable — a skills method was called but its SkillStore or
	// session-personal controller was not wired into the Service.
	ErrSkillsUnavailable = errors.New("agentcfg/protocol: skills control not wired on this runtime")
	// ErrUnknownModel — a set_llm_params (or a set_revision carrying an
	// LLMParams.Model) named a model with no configured ModelProfile. The
	// per-agent model is validated at set time (parity with the tenant
	// model-swap), so an invalid model can never be persisted — fail loud,
	// never a silent fallback (CLAUDE.md §13).
	ErrUnknownModel = errors.New("agentcfg/protocol: unknown model (no configured ModelProfile)")
	// ErrInvalidLLMParams — a set_llm_params (or a set_revision carrying an
	// LLMParams section) supplied an out-of-range sampling value (temperature
	// outside [0,2], non-positive max-tokens, or an unknown reasoning-effort).
	// Validated at set time so an invalid value never reaches a run (parity
	// with runs.set_overrides), fail loud (CLAUDE.md §13).
	ErrInvalidLLMParams = errors.New("agentcfg/protocol: invalid LLM parameters")
	// ErrInvalidHooks — a set_revision carrying a hooks section supplied an
	// invalid run-completion hook (a negative timeout_ms). Validated at set
	// time so an invalid value is rejected loud BEFORE any registry write —
	// parity with the yaml validator's negative-timeout rejection; the
	// normalizer's negative→0 coercion is defence-in-depth behind this gate,
	// never the primary posture (CLAUDE.md §13).
	ErrInvalidHooks = errors.New("agentcfg/protocol: invalid hooks section")
	// ErrInvalidToolExposureLoading — a set_tool_exposure (or a set_revision
	// carrying a ToolExposure section) supplied a server_loading_modes /
	// tool_loading_modes entry with an empty key or a value outside
	// "always"|"deferred". Validated at set time BEFORE any registry write
	// so an invalid override can never be persisted — no revision, no
	// event — fail loud, never a silent drop (CLAUDE.md §13).
	ErrInvalidToolExposureLoading = errors.New("agentcfg/protocol: invalid tool-loading-mode override")
	// ErrInvalidNaming — a set_revision carrying a naming section supplied an
	// invalid session auto-naming policy: a negative after_turns / repeat_every
	// / max_repetitions, a max_title_len outside [8,200], a repeat_every > 0
	// with max_repetitions < 1 (no unlimited value exists), or an unknown
	// model. Validated at set time BEFORE any registry write so an invalid
	// policy can never be persisted — fail loud, never a silent clamp
	// (CLAUDE.md §13). The model is validated via the same validateModel path
	// as set_llm_params.
	ErrInvalidNaming = errors.New("agentcfg/protocol: invalid naming section")
	// ErrInvalidExtraSystemBlocks — a set_extra_system_blocks (or a
	// set_revision carrying a blocks section) supplied a block with an
	// empty / out-of-charset name, an empty body, or a DUPLICATE name.
	// Uniqueness is what makes remove-by-name well defined — it is the
	// whole composability property the section exists for — so a duplicate
	// is refused loud BEFORE any registry write rather than silently
	// de-duplicated (CLAUDE.md §13). The message names the offender and,
	// for a duplicate, BOTH offending positions.
	ErrInvalidExtraSystemBlocks = errors.New("agentcfg/protocol: invalid extra system blocks section")
	// ErrSignedCapabilityPairReadOnly rejects every generic authoring door for
	// immutable signed-pair state. It is a client error, not a silently
	// ignored field: callers must use register_oauth_mcp_capability.
	ErrSignedCapabilityPairReadOnly = errors.New("agentcfg/protocol: signed oauth mcp capability pair is server-owned and read-only")
	// ErrSignedCapabilityUnavailable means this Runtime was not booted with an
	// explicitly enabled signed-capability trust anchor or private preparation seams.
	ErrSignedCapabilityUnavailable = errors.New("agentcfg/protocol: signed oauth mcp capability registration is not wired or boot-authorized")
	// ErrSignedCapabilityPairExists rejects a second pair for an agent until the
	// paired-removal lifecycle lands; generic provider/connection writers never
	// compose with a signed pair.
	ErrSignedCapabilityPairExists = errors.New("agentcfg/protocol: signed oauth mcp capability pair already exists")
	// ErrInvalidSignedCapabilityDescriptor is the closed descriptor validation
	// failure for the signed capability's HTTP-only MCP connection shape.
	ErrInvalidSignedCapabilityDescriptor = errors.New("agentcfg/protocol: invalid signed oauth mcp capability descriptor")
)

Sentinel errors the Service returns. The wire handler maps each onto a canonical Protocol Code + HTTP status; in-process callers compare with errors.Is.

View Source
var (
	// ErrInvalidLLMProvider — the descriptor failed validation (empty name /
	// provider, empty/non-remote credential_source, empty inference_broker)
	// (→ 400).
	ErrInvalidLLMProvider = errors.New("agentcfg/protocol: invalid llm provider descriptor")
	// ErrLLMProviderBrokerUnknown — the descriptor's inference_broker resolves
	// to no boot-declared broker (→ 400). No admin-writable field determines a
	// sink, so an unknown broker is a loud client error, never a silent accept.
	ErrLLMProviderBrokerUnknown = errors.New("agentcfg/protocol: inference_broker resolves to no boot-declared broker")
	// ErrLLMProviderInstallUnavailable — set_llm_provider was called but no
	// LLMProviderInstaller is wired on this runtime (→ 501).
	ErrLLMProviderInstallUnavailable = errors.New("agentcfg/protocol: llm provider install not wired on this runtime")
)

Inference-provider install sentinel errors. The wire handler maps each onto a canonical Protocol Code + HTTP status.

View Source
var (
	// ErrInvalidProvider — the provider descriptor failed validation (empty
	// name, wrong driver, empty/wrong credential_source, empty credential_broker)
	// (→ 400).
	ErrInvalidProvider = errors.New("agentcfg/protocol: invalid oauth provider descriptor")
	// ErrBootDeclaredProvider — set/remove_oauth_provider named a boot-declared
	// (yaml) provider — a DISTINCT loud error (boot wins; edit yaml + restart)
	// (→ 400).
	ErrBootDeclaredProvider = errors.New("agentcfg/protocol: oauth provider is boot-declared (edit yaml + restart, not the control plane)")
	// ErrProviderNotFound — remove_oauth_provider named a provider absent from
	// the agent's revisioned installed-provider set (→ 404).
	ErrProviderNotFound = errors.New("agentcfg/protocol: oauth provider not found in the agent's installed set")
	// ErrProviderInstallUnavailable — set/remove_oauth_provider was called but
	// no ProviderInstaller is wired on this runtime (→ 501).
	ErrProviderInstallUnavailable = errors.New("agentcfg/protocol: oauth provider install not wired on this runtime")
	// ErrProviderBrokerUnknown — the descriptor's credential_broker resolves to
	// no boot-declared broker (→ 400). The installer wraps its own broker error;
	// this sentinel lets the wire handler classify it as a client error.
	ErrProviderBrokerUnknown = errors.New("agentcfg/protocol: credential_broker resolves to no boot-declared broker")
	// ErrWireDescriptorNotAllowed — a provider descriptor (set_oauth_provider or
	// an add_mcp_connection inline binding) carried a credential-sink field
	// (token_url / audience / remote) while the fail-closed
	// tools.allow_wire_oauth_descriptor opt-in was OFF (→ 400). The default
	// zero-URL name-only posture is unchanged; the reject names the offending
	// field + the opt-in key.
	ErrWireDescriptorNotAllowed = errors.New("agentcfg/protocol: wire-carried oauth-provider descriptor is not allowed (the fail-closed tools.allow_wire_oauth_descriptor opt-in is off)")
)

Provider install/uninstall sentinel errors. The wire handler maps each onto a canonical Protocol Code + HTTP status.

View Source
var (
	// ErrUserSkillImportMisconfigured — the service was constructed without
	// a mandatory dependency (importer / artifact store / token sealer /
	// commit ledger / skill store / registry / capability policy).
	ErrUserSkillImportMisconfigured = errors.New("agentcfg/protocol: user skill import missing a mandatory dependency")
	// ErrUserSkillImportIdentityRequired — the request carried no verified
	// identity on ctx, an incomplete triple, or an empty effective agent id.
	ErrUserSkillImportIdentityRequired = errors.New("agentcfg/protocol: user skill import requires a complete verified identity and effective agent")
	// ErrUserSkillImportSessionReachDenied — a PRESENT signed session_reach
	// claim does not contain the caller's session.
	ErrUserSkillImportSessionReachDenied = errors.New("agentcfg/protocol: user skill import session reach denied")
	// ErrUserSkillImportAgentReachDenied — the effective agent is outside
	// the caller's signed agent_reach, or no reach is established on ctx
	// (the gate fails closed; an unwired gate is an honest "cannot verify
	// reach", never a silent widening).
	ErrUserSkillImportAgentReachDenied = errors.New("agentcfg/protocol: user skill import effective-agent reach denied")
	// ErrUserSkillImportArtifactNotFound — the artifact id does not resolve
	// under the caller's exact (tenant, user, session) triple. Non-oracular:
	// a foreign / erased / cross-session reference and a never-uploaded id
	// return the same typed not-found.
	ErrUserSkillImportArtifactNotFound = errors.New("agentcfg/protocol: user skill import artifact not found or not caller-owned")
	// ErrUserSkillImportArtifactChanged — the re-resolved artifact bytes do
	// not match the recorded digest/size the claims pinned.
	ErrUserSkillImportArtifactChanged = errors.New("agentcfg/protocol: user skill import artifact changed after validation")
	// ErrUserSkillImportPackageInvalid — the artifact is not a valid
	// complete skill package (archive / path / MIME / SKILL.md / support-ref
	// / frontmatter violations, including every authority-bearing
	// frontmatter field). Wraps the canonical importer / skillpkg sentinel.
	ErrUserSkillImportPackageInvalid = errors.New("agentcfg/protocol: user skill import artifact is not a valid complete skill package")
	// ErrUserSkillImportProposalInvalid — the proposal token is unknown,
	// consumed, foreign, or stale: oversize, malformed base64, failed
	// authentication, unknown schema, malformed claims, or bound to
	// different server-side inputs (actor, agent, name, reviewed hash,
	// expected config hash).
	ErrUserSkillImportProposalInvalid = errors.New("agentcfg/protocol: invalid, consumed, foreign, or stale user skill import proposal token")
	// ErrUserSkillImportExpired — the proposal token's review window elapsed
	// before an explicit commit.
	ErrUserSkillImportExpired = errors.New("agentcfg/protocol: user skill import proposal token expired")
	// ErrUserSkillImportHashMismatch — the reviewed package hash the commit
	// echoes does not equal the package the claims pinned (a changed review
	// is refused before any write).
	ErrUserSkillImportHashMismatch = errors.New("agentcfg/protocol: user skill import reviewed package hash mismatch")
	// ErrUserSkillImportPolicyRevoked — the capability policy snapshot
	// changed between validate and commit (the review is no longer
	// authoritative).
	ErrUserSkillImportPolicyRevoked = errors.New("agentcfg/protocol: user skill import capability policy revoked (the reviewed snapshot changed)")
	// ErrUserSkillImportConfigMoved — the caller's user-scope config base
	// moved between validate and commit (the expected content hash is no
	// longer active).
	ErrUserSkillImportConfigMoved = errors.New("agentcfg/protocol: user skill import config base moved (the expected content hash is no longer active)")
	// ErrUserSkillImportCeilingChanged — the effective archive/SKILL.md
	// ceilings changed between validate and commit (the reviewed ceiling
	// snapshot is no longer current).
	ErrUserSkillImportCeilingChanged = errors.New("agentcfg/protocol: user skill import configured ceilings changed after validation")
	// ErrUserSkillImportReplaceRequired — a different package already wins
	// the target key and the commit did not carry explicit replacement
	// consent.
	ErrUserSkillImportReplaceRequired = errors.New("agentcfg/protocol: user skill import replacement requires explicit consent")
	// ErrUserSkillImportConcurrentWinner — the target key is held by a
	// different package version than the one this commit's claims
	// reviewed/wrote. One winner only: this commit refuses rather than
	// overwrite.
	ErrUserSkillImportConcurrentWinner = errors.New("agentcfg/protocol: user skill import target is held by a different winner")
)

User-skill-import sentinel errors. In-process callers compare with errors.Is; the wire handler maps each onto a canonical Protocol code.

View Source
var ErrBootPackOwned = errors.New("agentcfg/protocol: pack name is boot-declared and read-only to the control plane")

ErrBootPackOwned refuses a control-plane mutation whose target canonical pack name is boot-declared for the exact (tenant, agent) pair. The boot baseline is read-only from the pack verbs: the operator edits the boot config and restarts. The refusal is typed (errors.Is) and fires BEFORE any revision, proposal, or store write — no partial effect, no false success.

View Source
var ErrConnectionOwnerMismatch = errors.New("agentcfg/protocol: connection is registered to a different owner — a live connection write applies to the caller's own connection")

ErrConnectionOwnerMismatch — the named connection is attached in the live MCP registry under a DIFFERENT (tenant, agent) owner than the caller's, or is boot-declared (untagged). A live connection write applies to the caller's OWN registration; a name owned elsewhere is refused as an authorization failure (→ 403 / CodeScopeMismatch) and the accompanying revision write is rolled back, so the call leaves no observable effect. It is DISTINCT from ErrDiscoveryTargetNotLive (the caller owns the declaration but the server is not attached yet, which degrades to a revision-only write).

View Source
var ErrDiscoveryOriginsNotHTTP = errors.New("agentcfg/protocol: connection is a stdio transport — OAuth-discovery origins apply only to http connections")

ErrDiscoveryOriginsNotHTTP — set_mcp_discovery_origins named a stdio-transport connection. A stdio server speaks over a subprocess pipe with no HTTP 401 challenge and no discovery walk, so an allow-list is meaningless — refused, never stored (→ 400).

View Source
var ErrDiscoveryTargetNotLive = errors.New("agentcfg/protocol: connection not attached in the live MCP registry — allowance recorded in the revision, applied on next reconcile")

ErrDiscoveryTargetNotLive — the named connection EXISTS in the active revision but is not attached in the live MCP registry (its server was never reached, or it is awaiting the next run-start reconcile). The applier adapter translates the driver's server-not-found into this sentinel so the setter can DEGRADE — record the allowance in the revision with applied_live=false, so the run-start allowance-reconcile applies it once the server comes online — instead of failing the write and rolling the revision back. This matches the nil-applier path (revision-only) and the sibling revision-only verbs. It is DISTINCT from ErrConnectionNotFound (the connection is absent from the revision entirely, which still fails loud).

View Source
var ErrSessionOverlayUnavailable = errors.New("agentcfg/protocol: session safe-subset control not wired on this runtime")

ErrSessionOverlayUnavailable — a session-safe method was called but no session-overlay store was wired into the Service.

View Source
var ErrWireInjectionNotAllowed = errors.New("agentcfg/protocol: wire-carried credential-injection mapping is not allowed (the fail-closed tools.allow_wire_injection opt-in is off)")

ErrWireInjectionNotAllowed — a connection descriptor carries a per-user credential-injection mapping (`injection`), which is accepted only behind the fail-closed `tools.allow_wire_injection` opt-in, but the opt-in is off. The wire handler maps it onto a canonical Protocol Code + 400; in-process callers compare with errors.Is.

Functions

func AgentPackAuthoringProposerSchema added in v1.27.0

func AgentPackAuthoringProposerSchema() string

AgentPackAuthoringProposerSchema returns the canonical closed proposer output contract.

func AgentPackAuthoringSystemMessage added in v1.27.0

func AgentPackAuthoringSystemMessage(policyJSON []byte) string

AgentPackAuthoringSystemMessage is the canonical system message represented by the policy bytes supplied to the proposer.

func GuardBootOwnedRevision added in v1.28.0

func GuardBootOwnedRevision(owner BootOwnership, tenantID, agentID string, items []skills.AgentPackItem) error

GuardBootOwnedRevision is the pure target-revision guard the generic rollback door invokes before repointing the active pointer at any revision whose agent_packs section contains a boot-owned canonical name. It is pure: no Service receiver, no context, no I/O — the caller supplies the reader and the target items. It returns the typed ErrBootPackOwned naming the first owned canonical name, or nil when the owner is nil, the items are empty, or no item is boot-owned.

func SafeReason added in v1.24.0

func SafeReason(err error) string

SafeReason is the exported door onto [safeReason] for the OTHER attach caller in the runtime: the run-start re-attach leg, which reports a refused or unreachable declared connection on its own canonical event and must scrub that reason through the SAME implementation this package's add path uses. One scrubber, two call sites — a second copy would drift on the next pattern added (CLAUDE.md §13).

func WithBootOwnership added in v1.28.0

func WithBootOwnership(ctx context.Context, owner BootOwnership) context.Context

WithBootOwnership returns a context carrying the injected boot-ownership reader, so the mutation guards can consume it without a Service field. A nil reader (or nil ctx) is a no-op: the guards stay inert.

Types

type AgentConfigReader added in v1.28.0

type AgentConfigReader interface {
	Active(ctx context.Context, id identity.Quadruple, agentID string, scope agentcfg.ConfigScope) (agentcfg.Revision, bool, error)
	RetirementStatus(ctx context.Context, id identity.Quadruple, agentID string) (agentcfg.RetirementStatus, bool, error)
}

AgentConfigReader is the read-only slice of the durable agent-config registry the preview service needs: the FRESH active revision (read at every preview) plus the retirement gate. A RetirementRegistry satisfies it; the interface exists so tests inject a narrow fake and the production assembler wires the real registry without touching Service construction. Both methods are reads — the preview structurally cannot write a revision.

type AgentPackAuthoringPolicy added in v1.27.0

type AgentPackAuthoringPolicy struct {
	ID             string   `json:"id"`
	Version        string   `json:"version"`
	Instructions   string   `json:"instructions"`
	ProposerSchema string   `json:"proposer_schema"`
	PermittedTools []string `json:"permitted_tools,omitempty"`
	PermittedNS    []string `json:"permitted_ns,omitempty"`
	PermittedTags  []string `json:"permitted_tags,omitempty"`
}

AgentPackAuthoringPolicy is server-owned input to the proposer. Its hash binds both the stable policy and the exact visible capability snapshot.

type AgentPackDraft added in v1.27.0

type AgentPackDraft struct {
	// Item is the drafted body. The service re-validates + re-hashes it
	// (the proposer's validation is never trusted by itself).
	Item skills.AgentPackItem
	// Warnings are non-fatal review notes (e.g. a required tool that is not
	// currently run-visible — filter metadata only, never a grant).
	Warnings []string
}

AgentPackDraft is the proposer's output: a bounded, validated pack item body plus optional review warnings.

type AgentPackProposer added in v1.27.0

type AgentPackProposer interface {
	// Draft turns a bounded operator intent into a bounded, validated pack
	// item body for the selected agent. `model` is the configured model the
	// service resolved (empty = the proposer's own default). Implementations
	// MUST honour ctx cancellation and MUST NOT persist anything.
	Draft(ctx context.Context, q identity.Quadruple, agentID, model, intent string, policy AgentPackAuthoringPolicy) (AgentPackDraft, error)
}

AgentPackProposer is the governed two-phase authoring seam. The concrete (injected at the cmd/harbor + devstack boundary) owns the LLM call and uses the model the service resolves from the agent's active revision (the versioned policy). The Service depends only on this interface; a nil proposer leaves propose failing loud with ErrAgentPackProposeUnavailable.

type AttachRequest

type AttachRequest struct {
	// Identity is the verified caller triple (the attach runs under it; the
	// driver stamps it on transport-side events).
	Identity identity.Identity
	// AgentID is the agent whose config revision owns this runtime-added
	// connection. With Identity.TenantID it forms the (tenant, agent)
	// reconcile-view owner tag the attacher stamps on the registry entry so a
	// run-start reconcile scopes to its own owner. Registration metadata, never
	// an isolation key.
	AgentID string
	// Name is the unique MCP source id.
	Name string
	// Transport is "stdio" or "http".
	Transport agentcfg.MCPTransport
	// Command is the stdio argv (argv[0] is the binary; no shell).
	Command []string
	// URL is the http(s) endpoint.
	URL string
	// Headers are operator-supplied auth headers used ONLY for the live
	// attach. SECRET — never persisted (CLAUDE.md §7).
	Headers map[string]string
	// OAuthProvider is the non-secret provider NAME to bind for per-identity
	// southbound bearer injection (empty leaves the connection on its static
	// Headers). The attacher resolves it against the declared registry.
	OAuthProvider string
	// OAuthProviderOverride is an unpublished provider instance used only by
	// this private preparation. It never enters the shared provider set until
	// the durable desired state has landed.
	OAuthProviderOverride toolauth.OAuthProvider
	// OwnOAuthProvider transfers teardown ownership of OAuthProviderOverride to
	// the prepared MCP connection. It is used only for a pair-private binding.
	OwnOAuthProvider bool
	// ToolAllowlist and ToolDenylist restrict the server-side tool names that
	// may be projected into the live catalog. Empty allowlist means all except
	// explicitly denied names.
	ToolAllowlist []string
	ToolDenylist  []string
	// ConnectTimeoutMS bounds connect plus initial discovery. RequestTimeoutMS
	// becomes the default per-request tool policy for the live connection.
	ConnectTimeoutMS int
	RequestTimeoutMS int
	// DescriptorFingerprint, when set by a server-owned signed pair, commits the
	// complete connection policy. Generic connections derive their fingerprint
	// from the ordinary descriptor fields.
	DescriptorFingerprint string
	// MetaAnnotations is the non-secret operator `_meta` annotation set the
	// attacher carries onto the live connection's per-call `_meta`.
	MetaAnnotations map[string]string
	// OAuthDiscoveryAllowedOrigins is the non-secret per-connection cross-origin
	// allow-list for OAuth-requirement discovery fetches. The attacher carries
	// it into the config.MCPServerConfig it builds so the live registry snapshots
	// it (closing the wiring gap — the walker's allowance input now flows from
	// the add request through to the registry, no longer inert for a
	// runtime-added connection). Empty leaves the authorization-server hop
	// needs-allowance.
	OAuthDiscoveryAllowedOrigins []string
	// Injection is the non-secret per-user credential-INJECTION mapping for a
	// receiver-style server (nil when the connection binds none). The attacher
	// carries it into the config.MCPServerConfig it builds so the shared injection
	// engine sources + injects the acting principal's credential per outbound call.
	// NON-SECRET (a broker name + a target key/form); the pulled value is resolved
	// per-call from the ctx identity and never rides this request.
	Injection *agentcfg.MCPCredentialInjectionDescriptor
	// ArtifactByteEligible is the operator's declaration that this
	// connection MAY receive artifact bytes through egress substitution.
	// NON-SECRET. The attacher carries it into the config.MCPServerConfig
	// it builds so the driver's shared egress engine is armed for this
	// connection — a field on the descriptor that nothing carries forward
	// is inert, which is the wiring-gap shape a discovery allow-list
	// already hit on this exact path.
	ArtifactByteEligible bool
	// ArtifactParams is the non-secret per-tool artifact-parameter
	// mapping. The attacher carries it into the config.MCPServerConfig so
	// the boot path and the runtime-add path share ONE egress engine
	// rather than growing a second. Requires ArtifactByteEligible.
	ArtifactParams map[string][]string
}

AttachRequest is the input to a ConnectionAttacher. It carries the non-secret descriptor PLUS the optional operator-supplied auth headers used ONLY for the live transport — the attacher never persists them.

type AuthorityBoundPreparedConnection added in v1.26.0

type AuthorityBoundPreparedConnection interface {
	PreparedConnection
	ActivateIf(ctx context.Context, prove func(context.Context) error) error
	// ActivateUnder hands the exact local publication callback to admit. The
	// caller uses this to hold a durable operation-slot fence across catalog and
	// registry publication without putting network preparation inside the fence.
	ActivateUnder(ctx context.Context, admit func(context.Context, func() error) error) error
}

AuthorityBoundPreparedConnection is the mandatory signed-capability publication seam. ActivateIf establishes an exact, non-dispatchable provider reservation before prove runs, then publishes only if that same reservation is still current. Exact teardown can invalidate and close the reservation; callers must treat a non-implementing prepared connection as unavailable, never fall back to ordinary Activate.

type BootOwnership added in v1.28.0

type BootOwnership interface {
	// OwnsName reports whether name is a boot-declared canonical pack name
	// for the exact (tenantID, agentID) pair. Implementations canonicalize
	// the name (lowercase, trimmed) before the lookup, so callers may pass a
	// raw or already-canonical name. Implementations MUST be safe for
	// concurrent use.
	OwnsName(tenantID, agentID, name string) bool
}

BootOwnership is the narrow, injected, read-only authority over which canonical pack names the boot baseline owns for an exact (tenant, agent) pair. The eager immutable bootpacks.Index satisfies it directly (internal/skills/bootpacks — `OwnsName`). A nil reader means no baseline is bound on this runtime and every guard is inert.

type BootPackReader added in v1.28.0

type BootPackReader interface {
	Lookup(tenantID, agentID string) ([]bootpacks.Entry, bool)
}

BootPackReader is the frozen eager boot-pack index surface the preview composes from. *bootpacks.Index satisfies it. Lookup NEVER rereads the boot files: the baseline is frozen at boot, and config removal is represented by the absence of the key in the next index.

type Clock

type Clock func() time.Time

Clock is the time source the Service stamps response timestamps from.

type CompositionPreviewAdminPayload added in v1.28.0

type CompositionPreviewAdminPayload struct {
	events.SafeSealed
	// Actor is the verified admin/fleet identity at the request edge.
	Actor identity.Identity
	// Target is the same-tenant triple whose composition was previewed.
	Target identity.Identity
	// AgentID is the effective boot-agent whose composition was previewed.
	AgentID string
	// Method is the Protocol method that carried the widened read.
	Method string
}

CompositionPreviewAdminPayload is the typed SafePayload published on the canonical audit.admin_scope_used event when an elevated (admin or console:fleet) caller performs a widened composition preview of a same-tenant user. SafePayload by construction: every field is a bounded identity component, the effective agent id, and the Protocol method name — no caller-supplied bytes and no composition content reach the bus.

type CompositionPreviewItem added in v1.28.0

type CompositionPreviewItem struct {
	// Name is the canonical (lowercase, trimmed) operator-tier name.
	Name string
	// SemanticHash is the canonical attachment-free content hash of Skill
	// (skills.CanonicalContentHash) — the semantic identity the strict
	// merge and every set hash use.
	SemanticHash string
	// Source is the strict-merge provenance marker: exactly
	// "boot" | "revision" | "both".
	Source skills.OperatorTierSource
	// Skill is the deep-copied composed skill body (the boot body is
	// retained when the item is both).
	Skill skills.Skill
}

CompositionPreviewItem is ONE composed effective-operator-tier item: the canonical name, the canonical attachment-free semantic content hash, the strict-merge provenance marker (boot|revision|both), and the deep-copied composed skill body.

type CompositionPreviewOption added in v1.28.0

type CompositionPreviewOption func(*CompositionPreviewService)

CompositionPreviewOption configures NewCompositionPreviewService.

func WithPreviewAgentReach added in v1.28.0

func WithPreviewAgentReach(a auth.AgentReachAuthorizer) CompositionPreviewOption

WithPreviewAgentReach wires the canonical effective-agent gate. The effective boot-agent must be a member of the caller's verified agent_reach. Unsupplied (or nil) FAILS CLOSED: no preview is served (an unwired gate is an honest "cannot verify reach", never a silent widening). The production assembler wires auth.NewAgentReachAuthorizer().

func WithPreviewBus added in v1.28.0

func WithPreviewBus(b events.EventBus) CompositionPreviewOption

WithPreviewBus wires the canonical events.EventBus the service publishes the widened-operations audit.admin_scope_used event onto. A nil bus is treated as "WithPreviewBus not supplied" — the widened preview still works, but the audit observation is logged at Info instead of published (the admin action is NEVER fully silent).

func WithPreviewLogger added in v1.28.0

func WithPreviewLogger(l *slog.Logger) CompositionPreviewOption

WithPreviewLogger sets the slog.Logger the service logs widened previews and audit-emit failures to. A nil logger routes to slog.Default().

func WithPreviewRedactor added in v1.28.0

func WithPreviewRedactor(r audit.Redactor) CompositionPreviewOption

WithPreviewRedactor wires the audit.Redactor the service runs the audit payload through before publishing. A nil redactor is treated as "WithPreviewRedactor not supplied" (the payload is SafePayload by construction).

func WithPreviewSessionReach added in v1.28.0

func WithPreviewSessionReach(a auth.SessionReachAuthorizer) CompositionPreviewOption

WithPreviewSessionReach wires the canonical signed-session-reach gate. A PRESENT session_reach claim must contain the target session; an absent claim preserves dynamic selection (the gate encodes that distinction). Unsupplied (or nil) leaves the transport edge as the enforcement point.

type CompositionPreviewRequest added in v1.28.0

type CompositionPreviewRequest struct {
	TenantID  string
	UserID    string
	SessionID string
	AgentID   string
}

CompositionPreviewRequest names the target of a read-only composition preview. The caller's VERIFIED identity comes from ctx; the target triple may differ from the caller's only for an elevated (admin/console:fleet) caller, and only within the caller's tenant.

type CompositionPreviewResponse added in v1.28.0

type CompositionPreviewResponse struct {
	// Outcome is one of available | unavailable | conflict | retired.
	Outcome PreviewOutcome
	// ConflictName is the first (canonical-sorted) offending canonical
	// name when Outcome is conflict, "" otherwise.
	ConflictName string
	// BootPackSetHash is the deterministic set hash over the boot baseline
	// entries only ("" when the boot baseline is empty).
	BootPackSetHash string
	// CombinedHash is the deterministic set hash over the unique combined
	// operator-tier items ("" when the tier is empty).
	CombinedHash string
	// RevisionHash is the deterministic set hash over the active-revision
	// pack items only ("" when no revision pack is bound).
	RevisionHash string
	// RevisionID is the fresh active revision read for this preview ("" when
	// no active revision exists).
	RevisionID string
	// ContentHash is the fresh active revision's content hash ("" when no
	// active revision exists).
	ContentHash string
	// Items are the effective items in deterministic canonical-name order.
	Items []CompositionPreviewItem
	// Widened is true when this preview was an elevated (admin or
	// console:fleet) same-tenant widened read, which was audited before the
	// composition read.
	Widened bool
}

CompositionPreviewResponse is the immutable, deterministic preview result. Every item and slice is a deep copy; callers may mutate their copy without affecting the service or another caller's result.

type CompositionPreviewService added in v1.28.0

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

CompositionPreviewService implements the read-only effective-composition preview. It is immutable after construction and safe for concurrent reuse by N goroutines.

func NewCompositionPreviewService added in v1.28.0

func NewCompositionPreviewService(registry AgentConfigReader, bootIndex BootPackReader, opts ...CompositionPreviewOption) (*CompositionPreviewService, error)

NewCompositionPreviewService builds the read-only preview over the two mandatory read seams: the agent-config reader (fresh active revision + retirement gate) and the frozen boot-pack index. A nil seam fails loud with ErrPreviewMisconfigured rather than building a service that would nil-panic on the first request. The returned *CompositionPreviewService is immutable after construction and safe for concurrent use by N goroutines.

func (*CompositionPreviewService) CompositionPreview added in v1.28.0

CompositionPreview resolves the read-only effective-composition preview for the requested target under the caller's verified ctx identity, scopes, and signed reach. See the package doc for the authorization model.

type ConnectionAttacher

type ConnectionAttacher interface {
	Attach(ctx context.Context, req AttachRequest) error
}

ConnectionAttacher is the compatibility seam for live-first runtime adds: dial → `initialize` handshake → discover → register, fail-loud per step. It is the seam that keeps the concrete MCP driver out of this package (the §4.4 boundary): the cmd/harbor + devstack boundary injects a concrete that builds a `config.MCPServerConfig` and calls the MCP driver's `Attach`.

Attach returns:

  • nil on a successful attach (online — the server's tools are registered on the live catalog);
  • an error wrapping ErrAuthRequired when the server needs authorization (the service parks on the unified pause/resume primitive);
  • any other error when an attach step failed (the service records the `failed` lifecycle, never a half-attached server).

type ConnectionDetacher added in v1.25.0

type ConnectionDetacher interface {
	// DetachConnection deregisters the named source's tools from the live
	// catalog + MCP registry and closes its transport, scoped to the
	// (tenant, agentID) owner the attach stamped. Idempotent: a source
	// already gone is a no-op.
	DetachConnection(ctx context.Context, tenant, agentID, name string) error
}

ConnectionDetacher is the compensating twin of the compatibility ConnectionAttacher seam: it tears down a server that older live-first callers attached before a revision write failed.

It exists because the add door's two side effects are not one transaction. The attach is live and irreversible-by-itself; the revision write can be refused after it (an expected-revision precondition whose base moved is the reachable case). Without this seam a refused write would leave a dialed, handshaken, registered server that NO revision names — and therefore one `agent_config.remove_mcp_connection` answers ErrConnectionNotFound for and the run-start reconcile never sees, because the reconcile diffs the LIVE owner view against the DECLARED set and an undeclared-but-live server is exactly what it would detach only if it also appeared in the owner view of a later run. A live server nothing can remove is worse than a failed add.

The concrete (wired at the cmd/harbor + devstack boundary) is the same object that satisfies ConnectionAttacher, so attach and compensating detach share one registry + one catalog and cannot drift apart. Optional — a nil detacher makes the compensation report the leak loudly instead of pretending it happened (CLAUDE.md §13).

type ConnectionPreparer added in v1.25.0

type ConnectionPreparer interface {
	PrepareConnection(ctx context.Context, req AttachRequest) (PreparedConnection, error)
}

ConnectionPreparer performs the network-dependent half of an MCP attach without publishing tools or a registry entry. The service persists desired state between PrepareConnection and PreparedConnection.Activate.

type ConnectionState

type ConnectionState string

ConnectionState is the explicit attach lifecycle state surfaced on the response + the lifecycle events. The set is closed.

const (
	// ConnectionStatePending — the attach has begun (dial → handshake →
	// discover → register). The transient initial state.
	ConnectionStatePending ConnectionState = "pending"
	// ConnectionStateOnline — the attach completed; the server's tools are
	// registered on the live catalog.
	ConnectionStateOnline ConnectionState = "online"
	// ConnectionStateFailed — an attach step failed; no revision recorded,
	// no half-attached server registered.
	ConnectionStateFailed ConnectionState = "failed"
	// ConnectionStateAuthRequired — the attach parked on the unified
	// pause/resume primitive awaiting authorization. An accepted resume re-reads
	// the exact durable descriptor, privately prepares it, then activates it.
	ConnectionStateAuthRequired ConnectionState = "auth_required"
)

The canonical attach lifecycle states.

type DiscoveryOriginApplier added in v1.14.0

type DiscoveryOriginApplier interface {
	// SetOAuthDiscoveryOrigins FULL-REPLACES the named connection's allow-list on
	// the live registry and returns the prior set so the caller computes the
	// granted / revoked delta. Identity-mandatory for authorization (the registry
	// stays bare-name). A revoke also prunes the recorded requirement's
	// now-unallowed authorization-server entries.
	//
	// (tenant, agentID) is the caller's resolved OWNER — the same (tenant, agent)
	// pair the ProviderInstaller seam carries, and the same tag the attach path
	// stamps on the live registration. The replacement lands only on a
	// registration carrying that tag, so the write applies to the caller's OWN
	// connection; a name owned by someone else is refused with
	// ErrConnectionOwnerMismatch, and a name the owner declares but has not
	// attached yet degrades to ErrDiscoveryTargetNotLive.
	SetOAuthDiscoveryOrigins(ctx context.Context, tenant, agentID, name string, origins []string) (prev []string, err error)
}

DiscoveryOriginApplier is the driver-agnostic seam the discovery-allowance write (and the run-start allowance-reconcile) use to apply a connection's OAuth-discovery cross-origin allow-list to the LIVE MCP registry. The concrete (wired at the cmd/harbor + devstack boundary) delegates to the process-global bare-name registry's SetOAuthDiscoveryOrigins mutator; keeping it an injected interface preserves this package's §4.4 boundary (no concrete MCP driver import here).

type ExactConnectionDetacher added in v1.26.0

type ExactConnectionDetacher interface {
	DetachExactConnection(ctx context.Context, tenant, agentID, name, descriptorFingerprint string) error
}

ExactConnectionDetacher is the mandatory teardown seam for a signed pair. The complete descriptor fingerprint is proved at the registry/catalog linearization point before any source is withdrawn.

type ExactConnectionTeardownFence added in v1.26.0

type ExactConnectionTeardownFence interface {
	Seal()
	Cancel(ctx context.Context) error
}

ExactConnectionTeardownFence is the process-local admission receipt that spans desired-state removal. Seal is called once pair absence is durable; Cancel is called only when the CAS is proven not to have committed.

type ExactConnectionTeardownFencer added in v1.26.0

type ExactConnectionTeardownFencer interface {
	BeginExactConnectionTeardown(tenant, agentID, name, descriptorFingerprint string) (ExactConnectionTeardownFence, error)
}

ExactConnectionTeardownFencer prevents a matching private preparation from publishing between the final durable authority proof and pair removal CAS. It is a companion to ExactConnectionDetacher so existing non-signed detach implementations do not acquire lifecycle ceremony they cannot use.

type LLMProviderInstaller added in v1.17.0

type LLMProviderInstaller interface {
	// InstallLLMProvider validates the descriptor's inference_broker against
	// the boot broker set, builds + connects the broker-pull source, and
	// installs the binding owner-tagged under (tenant, agentID). An unknown
	// broker or a connect failure fails loud. A re-install by the same owner
	// replaces (and closes) the prior binding (the rotate path).
	InstallLLMProvider(ctx context.Context, tenant, agentID string, desc agentcfg.LLMProviderDescriptor) error
	// UninstallLLMProvider removes the named binding and CLOSES it — the live
	// key is zeroed so a subsequently-bound call fails loud rather than serving
	// the old key. A missing name is an idempotent no-op.
	UninstallLLMProvider(ctx context.Context, name string) error
}

LLMProviderInstaller is the driver-agnostic seam the set_llm_provider verb (and the run-start provider reconcile) uses to install / uninstall a zero-URL, broker-pull inference provider on the LIVE owner-tagged set. The concrete (wired at the cmd/harbor + devstack boundary) resolves the descriptor's inference_broker against the boot broker set, builds the InferenceKeySource over the runtime's shared LiveKey, connects it (fail-loud), and installs it owner-tagged; keeping it an injected interface preserves this package's §4.4 boundary (no concrete LLM-credential construction here).

type Option

type Option func(*Service)

Option configures NewService.

func WithAgentPackCatalog added in v1.27.0

func WithAgentPackCatalog(c tools.ToolCatalog) Option

WithAgentPackCatalog supplies the server-owned visible capability snapshot used by governed authoring. A missing catalog is fail-closed.

func WithAgentPackGrantedScopes added in v1.27.0

func WithAgentPackGrantedScopes(scopes []string) Option

WithAgentPackGrantedScopes supplies the boot operator authorization scopes used by the same run-start catalog projection as the planner.

func WithAgentPackProposalState added in v1.27.0

func WithAgentPackProposalState(store state.StateStore) Option

WithAgentPackProposalState wires the durable, identity-scoped single-use proposal ledger. Without it, governed pack authoring fails closed.

func WithAgentPackProposer added in v1.27.0

func WithAgentPackProposer(p AgentPackProposer) Option

WithAgentPackProposer wires the governed two-phase authoring seam: `agent_config.agent_packs.propose` drafts a bounded pack skill body from an operator intent. A nil proposer leaves propose returning ErrAgentPackProposeUnavailable (→ 501 at the wire edge) — the deterministic pack verbs (upsert / remove / list) and the governed commit stay live regardless.

func WithAllowWireInjection added in v1.21.0

func WithAllowWireInjection(allow bool) Option

WithAllowWireInjection sets the effective DEV-ONLY, fail-closed opt-in that permits add_mcp_connection to carry a per-user credential-INJECTION mapping (the `injection` object) for a receiver-style MCP server over the wire. The caller passes (tools.allow_wire_injection config flag) OR (the HARBOR_ALLOW_WIRE_INJECTION boot env). It is INDEPENDENT of WithAllowWireOAuthDescriptor. Default (option not applied) is false / fail-closed — a connection carrying any injection field is rejected. Injected at the cmd/harbor + devstack boundary; never Protocol-writable.

func WithAllowWireOAuthDescriptor added in v1.18.0

func WithAllowWireOAuthDescriptor(allow bool) Option

WithAllowWireOAuthDescriptor sets the effective DEV-ONLY, fail-closed opt-in that permits set_oauth_provider / add_mcp_connection to carry a FULL OAuth provider binding over the wire (token_url / audience / scopes / remote{}). The caller passes (tools.allow_wire_oauth_descriptor config flag) OR (the HARBOR_ALLOW_WIRE_OAUTH_DESCRIPTOR boot env). Default (option not applied) is false / fail-closed — a wire descriptor carrying any credential-sink field is rejected, the zero-URL name-only posture unchanged. Injected at the cmd/harbor + devstack boundary; never Protocol-writable.

func WithBootDeclaredMCPServers added in v1.11.0

func WithBootDeclaredMCPServers(names []string) Option

WithBootDeclaredMCPServers records the set of MCP server names declared in the boot yaml (`tools.mcp_servers[].name`). `remove_mcp_connection` rejects a name in this set with ErrBootDeclaredConnection (distinct from the unknown-name error) — a boot-declared server is not revisioned state and is edited in yaml + restart. An empty / nil list leaves every unknown name a plain not-found. Injected at the cmd/harbor + devstack boundary from the loaded config so the verb (and the run-start reconcile, which never detaches a boot server) share one authoritative set.

func WithBootDeclaredOAuthProviders added in v1.14.0

func WithBootDeclaredOAuthProviders(names []string) Option

WithBootDeclaredOAuthProviders sets the set of OAuth provider names declared in the boot yaml. set_oauth_provider / remove_oauth_provider reject a name in this set with ErrBootDeclaredProvider (boot wins; edit yaml + restart). An empty / nil list leaves every unknown name a plain not-found (for remove) or installable (for set). Injected at the cmd/harbor + devstack boundary.

func WithBootLifecycleEnsurer added in v1.26.0

func WithBootLifecycleEnsurer(defaultAgentID string, ensure agentcfg.BootLifecycleEnsurer) Option

WithBootLifecycleEnsurer wires the production bootstrap authority into the session/user handler path. The handler calls it only after signed reach has authorized the effective target; this option itself never creates a named caller-selected agent.

func WithBus

func WithBus(b events.EventBus) Option

WithBus wires the EventBus the tool-exposure consumer publishes the `mcp.connection.paused` / `.resumed` overlay events through. A nil bus leaves those events unpublished (the revision is still recorded — the generic `agent.config.revised` still fires from the registry).

func WithClock

func WithClock(c Clock) Option

WithClock injects the time source. Defaults to time.Now.

func WithConnectionAttacher

func WithConnectionAttacher(a ConnectionAttacher) Option

WithConnectionAttacher is a compatibility option for older deterministic embedders. Harbor production wiring uses WithConnectionPreparer; when the supplied concrete also implements ConnectionPreparer, that stronger seam is selected automatically.

func WithConnectionDetacher added in v1.25.0

func WithConnectionDetacher(d ConnectionDetacher) Option

WithConnectionDetacher wires the concrete that tears a just-attached MCP server back down when `agent_config.add_mcp_connection`'s revision write fails after the attach succeeded. A nil detacher leaves that compensation logging the residual live server loudly instead of silently leaking it. The concrete (which imports the MCP driver) is the same object wired as the attacher; this package depends only on the interface.

func WithConnectionPreparer added in v1.25.0

func WithConnectionPreparer(p ConnectionPreparer) Option

WithConnectionPreparer wires the unpublished prepare/persist/activate MCP transaction seam. Harbor's production assembler uses this option.

func WithCoordinator

func WithCoordinator(c pauseresume.Coordinator) Option

WithCoordinator wires the unified pause/resume primitive an auth-required MCP attach parks on. A nil coordinator leaves an auth-required attach failing loud with ErrCoordinatorUnavailable (never a silent drop).

func WithDiscoveryOriginApplier added in v1.14.0

func WithDiscoveryOriginApplier(a DiscoveryOriginApplier) Option

WithDiscoveryOriginApplier wires the concrete that applies a connection's OAuth-discovery cross-origin allow-list to the LIVE MCP registry for `agent_config.set_mcp_discovery_origins` (and the run-start allowance-reconcile). A nil applier leaves the write recording the revision but reporting applied_live=false. The concrete (which imports the MCP driver) is injected at the cmd/harbor + devstack boundary; this package depends only on the interface.

func WithInferenceBrokers added in v1.17.0

func WithInferenceBrokers(names []string) Option

WithInferenceBrokers sets the set of boot-declared inference-broker names (`llm.inference_brokers[].name`). set_llm_provider rejects a descriptor whose inference_broker is not in this set with ErrLLMProviderBrokerUnknown (400) — no admin-writable field determines a credential sink (the credential-plane invariant). An empty / nil list leaves EVERY broker name unknown (the fail-closed default). Injected at the cmd/harbor + devstack boundary from the loaded config.

func WithLLMProviderInstaller added in v1.17.0

func WithLLMProviderInstaller(a LLMProviderInstaller) Option

WithLLMProviderInstaller wires the concrete that installs / uninstalls a Protocol-installed, zero-URL broker-pull inference provider binding live into the owner-tagged provider set for `agent_config.set_llm_provider`. A nil installer leaves the verb returning ErrLLMProviderInstallUnavailable (→ 501). The concrete (which resolves the boot inference broker + builds the InferenceKeySource) is injected at the cmd/harbor + devstack boundary; this package depends only on the interface.

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger sets the slog.Logger. A nil logger routes to slog.Default().

func WithProviderInstaller added in v1.14.0

func WithProviderInstaller(a ProviderInstaller) Option

WithProviderInstaller wires the concrete that installs / uninstalls a Protocol-installed, zero-URL OAuth provider live into the owner-tagged provider set for `agent_config.set_oauth_provider` / `remove_oauth_provider` (and the run-start provider reconcile). A nil installer leaves the two verbs returning ErrProviderInstallUnavailable (→ 501). The concrete (which imports the auth package + resolves the boot credential broker) is injected at the cmd/harbor + devstack boundary; this package depends only on the interface.

func WithRunSnapshotGate added in v1.26.0

func WithRunSnapshotGate(g *runsnapshot.Gate) Option

WithRunSnapshotGate wires the process-local admission/drain gate shared with the RunLoopDriver. Retirement requires this gate before installing a tombstone so destructive runtime cleanup can never overtake a pre-tombstone immutable run snapshot.

func WithSessionOverlay

func WithSessionOverlay(st sessionoverlay.Store) Option

WithSessionOverlay wires the session-scoped safe-subset overlay store so the NON-admin `agent_config.session.*` verbs are live. A nil store leaves those methods returning ErrSessionOverlayUnavailable (→ 501 at the wire edge). The store is keyed by the caller's real (tenant, user, session) triple, so a session's overlay is invisible to another session.

func WithSessionPersonalSkillController added in v1.26.0

func WithSessionPersonalSkillController(controller SessionPersonalSkillController) Option

WithSessionPersonalSkillController wires the sole authority for `agent_config.session.skills.*` and the dynamic personal-name projection on every session-overlay response. A nil controller leaves those reads/writes failing loud with ErrSkillsUnavailable (→ 501 at the wire edge); the Service never falls back to SkillStore or persists Overlay.PersonalSkills.

func WithSignedOAuthMCPCapabilityAuthorities added in v1.26.0

func WithSignedOAuthMCPCapabilityAuthorities(authorities map[string]SignedOAuthMCPCapabilityAuthority) Option

WithSignedOAuthMCPCapabilityAuthorities wires the immutable boot-declared signed-capability trust anchors. The map is copied, so callers cannot mutate a compiled Service after construction. An empty map is the fail-closed default.

func WithSignedOAuthMCPOperationState added in v1.26.0

func WithSignedOAuthMCPOperationState(store state.StateStore) Option

WithSignedOAuthMCPOperationState wires the runtime StateStore into the signed-capability recovery ledger. A missing or invalid store leaves the production registration surface fail-closed.

func WithSkillStore

func WithSkillStore(st skills.SkillStore) Option

WithSkillStore wires the SkillStore so the `agent_config.skills.*` methods are live. A nil store leaves the skills methods returning ErrSkillsUnavailable (→ 501 at the wire edge).

func WithStdioAllowlist

func WithStdioAllowlist(commands []string) Option

WithStdioAllowlist sets the fail-closed allowlist of permitted stdio commands (matched on argv[0]) for `agent_config.add_mcp_connection`. A stdio add whose command[0] is absent is rejected with ErrStdioNotAllowed. An empty / nil allowlist rejects EVERY stdio add (the secure default); http adds are unaffected. Adding a stdio server runs an operator-supplied command (an RCE surface) — this gate is the §7 fail-closed boundary.

func WithValidModels

func WithValidModels(models []string) Option

WithValidModels sets the set of model names with a configured ModelProfile. `set_llm_params` (and a `set_revision` carrying an LLMParams.Model) reject a model outside this set with ErrUnknownModel — parity with the tenant model-swap, validated at SET time so an invalid model can never be persisted (never a silent run-start fallback).

An empty / nil set means model-pinning is UNAVAILABLE: every non-empty model set fails loud (the binary always wires at least the bound default model, so an empty set signals a misconfiguration, not "accept anything"). This matches the governance tenant-override policy's fail-loud-on-empty stance; it deliberately DIFFERS from runs.set_overrides (the one-shot session swap), which accepts any model when no set is configured.

type PreparedConnection added in v1.25.0

type PreparedConnection interface {
	Activate(ctx context.Context) error
	Close(ctx context.Context) error
}

PreparedConnection owns one connected, discovered, unpublished MCP connection. Activate publishes it once; Close drains it on refusal.

type PreparedOAuthProvider added in v1.25.0

type PreparedOAuthProvider interface {
	Binding() toolauth.OAuthProvider
	Publish(ctx context.Context) error
	Commit(ctx context.Context)
	Rollback(ctx context.Context) error
	Close(ctx context.Context) error
}

PreparedOAuthProvider owns one unpublished provider instance.

type PreviewOutcome added in v1.28.0

type PreviewOutcome string

PreviewOutcome is the typed outcome of one composition preview.

const (
	// PreviewOutcomeAvailable — the composition resolved: deterministic
	// items (possibly empty) plus the deterministic set hashes.
	PreviewOutcomeAvailable PreviewOutcome = "available"
	// PreviewOutcomeUnavailable — there is nothing to compose for the
	// target (no boot baseline AND no active durable revision), or the
	// caller is not entitled to the target. Foreign / cross-tenant /
	// missing are non-oracular: the response is identical, so nothing
	// about the target is revealed.
	PreviewOutcomeUnavailable PreviewOutcome = "unavailable"
	// PreviewOutcomeConflict — the strict composer refused a typed
	// boot/revision conflict: a canonical name whose semantic content
	// differs across (or within) the boot baseline and the active revision.
	// Never a silent last-write-wins overwrite.
	PreviewOutcomeConflict PreviewOutcome = "conflict"
	// PreviewOutcomeRetired — the effective agent's terminal lifecycle
	// tombstone is installed; the composition is no longer readable.
	PreviewOutcomeRetired PreviewOutcome = "retired"
)

Preview outcomes. Each response carries exactly one.

type ProviderInstaller added in v1.14.0

type ProviderInstaller interface {
	// InstallProvider validates the descriptor's credential_broker against the
	// boot broker set, builds the broker-pull provider, and installs it into the
	// owner-tagged provider set under (tenant, agentID). An unknown broker, a
	// boot-name collision, or another owner's install collision fails loud. A
	// re-install by the same owner replaces (and closes) the prior instance.
	InstallProvider(ctx context.Context, tenant, agentID string, desc agentcfg.OAuthProviderDescriptor) error
	// UninstallProvider removes the named provider from the owner-tagged set and
	// CLOSES it. The (tenant, agentID) pair is the caller's resolved owner; the
	// set refuses a cross-owner drop at its own boundary (defense in depth). A
	// missing name is an idempotent no-op.
	UninstallProvider(ctx context.Context, tenant, agentID, name string) error
}

ProviderInstaller is the driver-agnostic seam the provider install / uninstall verbs (and the run-start provider reconcile) use to install / uninstall a Protocol-installed, zero-URL OAuth provider on the LIVE owner-tagged provider set. The concrete (wired at the cmd/harbor + devstack boundary) resolves the descriptor's credential_broker against the boot broker set, builds the tokenexchange provider, and installs it owner-tagged; keeping it an injected interface preserves this package's §4.4 boundary (no concrete auth-provider construction here).

type ProviderPreparer added in v1.25.0

type ProviderPreparer interface {
	PrepareProvider(ctx context.Context, tenant, agentID string, desc agentcfg.OAuthProviderDescriptor) (PreparedOAuthProvider, error)
}

ProviderPreparer builds an unpublished OAuth provider for an MCP prepare. The provider can be used privately during dial/discovery, then published reversibly after the durable revision lands.

type Service

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

Service implements the admin-scoped agent-config methods.

func NewService

func NewService(registry agentcfg.Registry, opts ...Option) (*Service, error)

NewService builds the agent-config Service over a Registry. registry is mandatory — a nil fails loud with ErrMisconfigured rather than building a Service that would nil-panic on the first request (CLAUDE.md §5).

The returned *Service is immutable after construction and safe for concurrent use by N goroutines.

func (*Service) AddMCPConnection

AddMCPConnection adds a NEW MCP server connection. See the file doc for the full lifecycle, secret-hygiene, and stdio-gate contract.

func (*Service) AgentPacksCommit added in v1.27.0

AgentPacksCommit is the GOVERNED second phase: CAS-bind the EXACT reviewed hash and atomically persist body + membership in ONE revision. The submitted body must hash to exactly ReviewedHash (a changed body — and therefore a changed hash / scope / capability annotation / provenance — is refused and NOTHING is persisted), the Provenance must echo the deterministic proposal stamp, and the expected-revision token must still match (the cross-write CAS half).

func (*Service) AgentPacksList added in v1.27.0

AgentPacksList returns the agent's active pack (full items, canonical order) under the caller's verified identity. A missing revision or an absent pack section yields an empty list — the valid "no pack" state.

The list is DURABLE-revision authoring state only: it reflects exactly the active revision's agent_packs section. Boot-declared baseline names (internal/skills/bootpacks) are never stored in a revision, so a boot-only name never appears here; a legacy durable shadow of a now-boot-owned name does appear (until removed), exactly like any other revisioned item.

func (*Service) AgentPacksPropose added in v1.27.0

AgentPacksPropose is the GOVERNED first phase: draft a bounded pack skill body from a bounded operator intent. The draft uses the agent's configured model (from the active revision's llm_params, validated against the configured ModelProfiles) under the versioned revision policy named by ExpectedContentHash — a stale expected revision is refused before any drafting. Propose persists a durable receipt unless dry_run is requested. It returns the canonical draft body, its content hash (the hash the operator reviews), capability warnings, and the deterministic provenance stamp the commit must echo.

func (*Service) AgentPacksRemove added in v1.27.0

AgentPacksRemove DETERMINISTICALLY drops ONE pack item by name. A missing name fails loud with ErrAgentPackNotFound (a stale remove can never silently no-op). Sibling config sections are preserved.

func (*Service) AgentPacksUpsert added in v1.27.0

AgentPacksUpsert DETERMINISTICALLY adds or replaces ONE pack item: body + membership are persisted atomically in a single revision. The item is validated (shape + bounds + the read-only origin/scope fields), its origin_ref is server-stamped, and the composed pack must stay within skills.MaxAgentPackItems. Sibling config sections are preserved.

func (*Service) Diff

Diff returns the server-side compare of two existing revisions.

func (*Service) EnsureBootLifecycle added in v1.26.0

func (s *Service) EnsureBootLifecycle(ctx context.Context, scope prototypes.IdentityScope, agentID string) error

EnsureBootLifecycle materialises the boot-declared default for a verified request identity. It is intentionally a no-op for named agents: their lifecycle is explicit configuration authority, never request-created state.

func (*Service) Get

Get reads the agent's active config revision.

func (*Service) ListRevisions

ListRevisions returns the agent's revision chain, newest-first.

func (*Service) RegisterOAuthMCPCapability added in v1.26.0

RegisterOAuthMCPCapability is the bounded production registration operation. The provider is prepared privately and handed directly to MCP preparation; it is never installed in the generic ProviderSet.

func (*Service) RemoveMCPConnection added in v1.11.0

RemoveMCPConnection removes a runtime-added MCP connection by name. See the file doc for the full contract (revision + residue prune, the two distinct loud errors, the projection-boundary teardown, token retention).

func (*Service) RemoveOAuthMCPCapability added in v1.26.0

RemoveOAuthMCPCapability advances the one signed-capability pair-lifetime receipt from published through desired-state removal, catalog withdrawal, teardown, and its anti-replay tombstone. It intentionally accepts no authority envelope: removal is authorized by the verified admin caller and the frozen exact pair receipt, so expiry or verifier-key rotation can never strand a live bearer.

func (*Service) RemoveOAuthProvider added in v1.14.0

RemoveOAuthProvider uninstalls a Protocol-installed OAuth provider by name. See the file doc for the full contract.

func (*Service) Retire added in v1.26.0

Retire installs or replays the terminal agent lifecycle tombstone. It is an admin control-plane operation; data-plane reach is intentionally not part of this service contract and is therefore never inferred from the request.

func (*Service) Rollback

Rollback repoints the active pointer to an existing revision.

A rollback is the GENERIC activation door: it makes an existing revision active again, so a target revision whose agent_packs section contains a boot-declared canonical name must never pass through it. Boot wins over any durable state — including when the target's pack content hashes identically to the boot entry (an equal hash proves nothing; the baseline is edited in the boot config and applied on the next deployment, never through the control plane). The pure GuardBootOwnedRevision helper is consulted on the TARGET revision, using the same exact (tenant, effective agent) boot-ownership source the pack verbs consume, immediately BEFORE the registry repoint: a refused rollback activates nothing and mutates no revision. With no reader bound on the request (no boot baseline on this runtime) the guard is inert and the door keeps its exact pre-baseline behavior.

func (*Service) SessionSetSourceDisables

SessionSetSourceDisables records the session's narrow-only disable set (servers + tools). There is intentionally no enable field: the set names what the session wants OFF, and the run-start projection unions it into the admin exclusion set — so a session edit can only narrow the admin-allowed exposure, never widen it.

func (*Service) SessionSetUserPrompt

SessionSetUserPrompt sets ONLY the session's user prompt layer. The session-writable shape carries no base field, so this can never alter the operator base — base-unwritable-by-session is structural.

func (*Service) SessionSkillsDelete

SessionSkillsDelete logically deletes a personal skill under the caller's real triple through the injected controller's one-CAS mutation. It reloads the authoritative tier for the response and never writes the legacy overlay name field.

func (*Service) SessionSkillsList

SessionSkillsList lists the session's skills (metadata only) under the caller's real triple and selected agent. It intentionally returns only the controller's ScopeSession tier; ScopeUser composition belongs to Directory and the general skill tools.

func (*Service) SessionSkillsUpsert

SessionSkillsUpsert upserts an EPHEMERAL personal skill under the caller's real triple through the injected controller's one-CAS mutation. The skill scope is FORCED to session — a session personal skill never promotes to the agent/tenant scope. The response reloads the authoritative tier and derives names dynamically; it never mutates legacy Overlay.PersonalSkills.

func (*Service) SetExtraSystemBlocks added in v1.25.0

SetExtraSystemBlocks records a new config revision pinning the supplied ORDERED list of named additive prompt blocks as a desired-state replace of the blocks section. Every sibling section of the active revision is carried forward unchanged.

The supplied order is preserved end to end — no map is the CARRIER, and nothing on the write → normalise → hash → project → render path sorts the list — so a re-ordering is a real new revision with a different content hash and a visible diff.

Stated that precisely because the broader "no map appears on the path" is FALSE and would mislead a future author: the duplicate-name check above and normalizeNamedBlocks both build a local `seen` map. Neither determines order — they are membership sets over an already-ordered slice — and that distinction is the actual invariant. What must never happen is a map becoming the carrier, because map iteration order is not a composition order.

func (*Service) SetLLMParams

SetLLMParams records a new config revision pinning the supplied per-agent LLM-parameter section (model / temperature / max-tokens / reasoning-effort) as a desired-state replace of the LLM-params section. A set Model is validated against the configured ModelProfiles (an unknown model is rejected with ErrUnknownModel). The prompt-layer + skills + tool-exposure + connection + hooks sections of the active revision are carried forward unchanged.

func (*Service) SetLLMProvider added in v1.17.0

SetLLMProvider installs (upserts) / rotates a ZERO-URL, broker-pull inference provider binding. See the file doc for the full contract.

func (*Service) SetMCPDiscoveryOrigins added in v1.14.0

SetMCPDiscoveryOrigins FULL-REPLACES a runtime-added MCP connection's OAuth-discovery cross-origin allow-list. See the file doc for the full contract (revision + owner-scoped live apply + revoke-prune, the four distinct loud errors, the fail-closed audit, the rollback reconcile path).

func (*Service) SetOAuthProvider added in v1.14.0

SetOAuthProvider installs (upserts) a ZERO-URL, broker-pull OAuth provider. See the file doc for the full contract (the zero-URL invariant, revision + live install + fail-closed audit, the boot-declared loud error).

func (*Service) SetPromptLayers

SetPromptLayers records a new config revision pinning the supplied layered system prompt (operator base and/or user layer) as a desired-state replace of the prompt-layer section. The skills + tool-exposure + connections + llm-params + hooks sections of the active revision are carried forward unchanged.

func (*Service) SetRevision

SetRevision writes a new immutable revision and advances the active pointer.

func (*Service) SetToolExposure

SetToolExposure records a new config revision pinning the supplied MCP-exposure desired state (paused servers + disabled tools) and emits an `mcp.connection.paused` / `.resumed` event for each server whose pause state changed relative to the prior active revision. The skills + prompt-layer + connections + llm-params + hooks sections of the active revision are carried forward unchanged — every sibling section survives a tool-exposure edit.

func (*Service) SkillsDelete

SkillsDelete deletes a skill from the SkillStore and records the membership change as a config revision.

func (*Service) SkillsList

SkillsList returns the agent's skills (metadata only) from the SkillStore under the caller's identity.

func (*Service) SkillsUpsert

SkillsUpsert upserts a skill into the SkillStore and records the membership change as a config revision. A pack-overwrite refusal surfaces as the typed `skills.ErrPackOverwriteRefused` (mapped to CodeInvalidRequest at the wire edge) — never a silent overwrite.

func (*Service) UserDiff added in v1.6.0

UserDiff returns the server-side compare of two existing revisions of the caller's own variant.

func (*Service) UserGet added in v1.6.0

UserGet reads the caller's own durable config variant active revision.

func (*Service) UserListRevisions added in v1.6.0

UserListRevisions returns the caller's own variant revision chain, newest-first.

func (*Service) UserRollback added in v1.6.0

UserRollback repoints the caller's own variant active pointer to an existing revision WITHOUT mutating any revision. Serialised per-owner.

func (*Service) UserSetRevision added in v1.6.0

UserSetRevision writes a new immutable revision of the caller's durable variant from the bounded safe-subset payload and advances the active pointer. Serialised per-owner (scope, tenant, real-user, agent) so distinct users never contend.

func (*Service) UserSkillsDelete added in v1.21.0

UserSkillsDelete deletes one of the caller's durable user-scope personal skills and removes its name from the user-scope membership revision.

func (*Service) UserSkillsList added in v1.21.0

UserSkillsList lists the caller's durable user-scope personal skills (metadata only) under their real (tenant, user). The SkillStore resolves ScopeUser rows across the caller's sessions, so the list is durable and conversation-independent.

func (*Service) UserSkillsUpsert added in v1.21.0

UserSkillsUpsert upserts a DURABLE personal skill at user scope and records its name in the caller's user-scope config revision membership. The skill scope is FORCED to skills.ScopeUser — a session caller cannot widen the visibility scope, and the durable rung is keyed (tenant, user).

type SessionPersonalSkillController added in v1.26.0

type SessionPersonalSkillController interface {
	SessionSkills(ctx context.Context, id identity.Quadruple, agentID string) ([]skills.Skill, error)
	UpsertSessionSkill(ctx context.Context, id identity.Quadruple, agentID string, skill skills.Skill) error
	DeleteSessionSkill(ctx context.Context, id identity.Quadruple, agentID, name string) error
}

SessionPersonalSkillController is the sole mutation and read authority for one selected agent's session-personal skill tier. UpsertSessionSkill and DeleteSessionSkill each perform one controller-owned CAS; callers must not pair them with a legacy SkillStore or Overlay.PersonalSkills write. SessionSkills returns only ScopeSession rows for the supplied real identity triple and selected agent. Implementations preserve cutover and unstable-read sentinels so the Protocol edge can map them to their canonical 409 codes.

type SignedCapabilityProviderPreparer added in v1.26.0

type SignedCapabilityProviderPreparer interface {
	PrepareSignedCapabilityProvider(ctx context.Context, broker string, binding toolauth.SignedCapabilityExchangeBinding, scopes []string) (PreparedOAuthProvider, error)
}

SignedCapabilityProviderPreparer constructs the pair-owned provider used by signed capability flow. It deliberately has no Publish-to-ProviderSet operation: the MCP prepared connection receives the private binding directly and its catalog activation is the sole data-plane publication point.

type SignedOAuthMCPCapabilityAuthority added in v1.26.0

type SignedOAuthMCPCapabilityAuthority struct {
	Broker               string
	Issuer               string
	Keys                 SignedOAuthMCPKeySet
	ScopeCeiling         []string
	MaxAuthorityLifetime time.Duration
}

SignedOAuthMCPCapabilityAuthority is one immutable boot-declared trust anchor. Broker is the request-visible selector, but all authority is fixed by this construction-time value.

func (SignedOAuthMCPCapabilityAuthority) Verify added in v1.26.0

Verify validates a signed envelope against this fixed trust anchor. The unverified parse reads only kid/alg to select a key from the already-pinned KeySet; all claims and the signature are then verified by agentcfg's asymmetric, exact-binding verifier.

type SignedOAuthMCPKeySet added in v1.26.0

type SignedOAuthMCPKeySet interface {
	KeyByID(kid string) (crypto.PublicKey, string, error)
}

SignedOAuthMCPKeySet resolves a JWT kid only from a boot-configured trust anchor. It is deliberately the narrow subset of the Protocol auth key-set seam so the registration service never discovers a verifier from the wire.

type SignedOAuthMCPReconciler added in v1.26.0

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

SignedOAuthMCPReconciler resumes only durable signed-capability pair operations for one exact tenant and agent. It deliberately enumerates revision history rather than operation records: an opaque operation receipt alone is not authority to attach or detach anything.

func NewSignedOAuthMCPReconciler added in v1.26.0

func NewSignedOAuthMCPReconciler(registry agentcfg.Registry, store state.StateStore, preparer ConnectionPreparer, detacher ConnectionDetacher, providers SignedCapabilityProviderPreparer) (*SignedOAuthMCPReconciler, error)

NewSignedOAuthMCPReconciler constructs the single recovery seam shared by boot and run-start. Every side-effecting dependency is mandatory so an incomplete runtime fails closed instead of guessing at live state.

func (*SignedOAuthMCPReconciler) ReconcileSignedOAuthMCPCapability added in v1.26.0

func (r *SignedOAuthMCPReconciler) ReconcileSignedOAuthMCPCapability(ctx context.Context, q identity.Quadruple, agentID string) error

ReconcileSignedOAuthMCPCapability converges a single exact agent slot. A foreign/corrupt/expired receipt is never dispatched: it is returned to the caller, leaving the fence's prior active revision authoritative.

type UserSkillImportCapability added in v1.28.0

type UserSkillImportCapability interface {
	Policy(ctx context.Context, id identity.Identity, agentID string) (UserSkillImportPolicy, error)
}

UserSkillImportCapability resolves the CURRENT capability policy snapshot for an effective agent under the caller's verified identity. The production adapter (agentConfigCapabilityPolicy) projects the configured tool catalog through the canonical ActivePlannerCatalogView; tests inject a fixed fake. The returned value is a deep copy.

func NewUserSkillImportCapabilityPolicy added in v1.28.0

func NewUserSkillImportCapabilityPolicy(registry agentcfg.Registry, overlay sessionoverlay.Store, catalog tools.ToolCatalog, grantedScopes []string) UserSkillImportCapability

NewUserSkillImportCapabilityPolicy builds the production capability-policy adapter — the canonical ActivePlannerCatalogView projection over the configured tool catalog under the caller's verified identity — and returns it through the UserSkillImportCapability interface, so cross-package composition never needs the private concrete type. Policy semantics are unchanged: the adapter is immutable after construction, a nil catalog fails loud on the first Policy call, and no writes of any kind happen. The granted-scope ceiling slice is defensively copied: mutating the caller's backing array after construction cannot change the adapter's behavior.

type UserSkillImportCommitRequest added in v1.28.0

type UserSkillImportCommitRequest struct {
	// ProposalToken echoes the opaque proposal token from validate.
	ProposalToken string
	// AgentID is the effective agent (must equal the claims').
	AgentID string
	// Name is the reviewed canonical package/skill name (must equal the
	// claims'; used to address the target key).
	Name string
	// ReviewedPackageHash is the versioned package hash the caller
	// reviewed (must equal the claims').
	ReviewedPackageHash string
	// ExpectedContentHash echoes the expected config content hash from
	// validate (must equal the claims').
	ExpectedContentHash string
	// Replace is the explicit replacement consent. A different package
	// already at the target key is refused without it.
	Replace bool
}

UserSkillImportCommitRequest is the bounded second-phase input: the proposal token, the reviewed package hash, the reviewed canonical name, the expected config content hash, and the explicit replacement consent.

type UserSkillImportCommitResponse added in v1.28.0

type UserSkillImportCommitResponse struct {
	// Receipt is the exact versioned receipt of the atomic write — the
	// conditional-compensation handle for THIS unit/version only.
	Receipt skills.InstalledPackageReceipt
	// Skill is the stored skill (ScopeUser, effective agent, canonical
	// name) as installed.
	Skill skills.Skill
	// PackageHash is the written versioned package hash.
	PackageHash string
	// Replayed is true when the terminal result was recognized from an
	// already-landed commit (response-loss replay) and no second package
	// write happened.
	Replayed bool
}

UserSkillImportCommitResponse is the terminal commit result: the exact versioned receipt of the ONE atomic package+membership write, the stored skill summary (deep copy), and the Replayed flag (true when the result was recognized from a prior landed commit rather than written by this call).

type UserSkillImportConfigReader added in v1.28.0

type UserSkillImportConfigReader interface {
	Active(ctx context.Context, id identity.Quadruple, agentID string, scope agentcfg.ConfigScope) (agentcfg.Revision, bool, error)
	RetirementStatus(ctx context.Context, id identity.Quadruple, agentID string) (agentcfg.RetirementStatus, bool, error)
}

UserSkillImportConfigReader is the read-only slice of the durable agent-config registry the import service needs: the FRESH active user-scope revision (the expected-config-hash base) plus the retirement gate. A RetirementRegistry satisfies it; the interface exists so tests inject a narrow fake and the production assembler wires the real registry. Both methods are reads — the import service structurally cannot write a config revision.

type UserSkillImportOption added in v1.28.0

type UserSkillImportOption func(*UserSkillImportService)

UserSkillImportOption configures NewUserSkillImportService.

func WithImportAgentReach added in v1.28.0

func WithImportAgentReach(a auth.AgentReachAuthorizer) UserSkillImportOption

WithImportAgentReach wires the canonical effective-agent gate. The effective agent must be a member of the caller's verified agent_reach. Unsupplied (or nil) FAILS CLOSED: no import is served (an unwired gate is an honest "cannot verify reach", never a silent widening). The production assembler wires auth.NewAgentReachAuthorizer().

func WithImportClock added in v1.28.0

func WithImportClock(c Clock) UserSkillImportOption

WithImportClock injects the time source. Defaults to time.Now.

func WithImportLogger added in v1.28.0

func WithImportLogger(l *slog.Logger) UserSkillImportOption

WithImportLogger sets the slog.Logger. A nil logger routes to slog.Default().

func WithImportProposalTTL added in v1.28.0

func WithImportProposalTTL(d time.Duration) UserSkillImportOption

WithImportProposalTTL bounds the review window between validate and an explicit commit. Unsupplied (or non-positive) uses the default TTL.

func WithImportSessionReach added in v1.28.0

func WithImportSessionReach(a auth.SessionReachAuthorizer) UserSkillImportOption

WithImportSessionReach wires the canonical signed-session-reach gate. A PRESENT session_reach claim must contain the caller's session; an absent claim preserves dynamic selection. Unsupplied (or nil) leaves the transport edge as the enforcement point.

type UserSkillImportPolicy added in v1.28.0

type UserSkillImportPolicy struct {
	// ID is the policy envelope identity.
	ID string `json:"id"`
	// Version is the policy envelope version.
	Version string `json:"version"`
	// PermittedTools are the run-visible tool names of the effective agent.
	PermittedTools []string `json:"permitted_tools,omitempty"`
	// PermittedNS are the run-visible namespaces (currently empty — the
	// catalog projection exposes tool names only; kept for the closed
	// shape so a future projection cannot widen the review).
	PermittedNS []string `json:"permitted_ns,omitempty"`
	// PermittedTags are the run-visible tags (same note as PermittedNS).
	PermittedTags []string `json:"permitted_tags,omitempty"`
}

UserSkillImportPolicy is the server-owned capability snapshot the import validates RequiredTools / RequiredNS / RequiredTags against (as applicability metadata — non-fatal warnings) and the claims bind by hash. The permitted sets are the effective agent's run-visible catalog projection — the SAME projection the operator pack authoring policy uses.

type UserSkillImportProposalSealer added in v1.28.0

type UserSkillImportProposalSealer interface {
	Seal(plaintext []byte) ([]byte, error)
	Open(ciphertext []byte) ([]byte, error)
}

UserSkillImportProposalSealer seals / opens the opaque proposal-token envelope. The seal input is the bounded versioned claims JSON; the open output is the authenticated claims JSON. The interface is deliberately the same shape as internal/tools/auth.Sealer (the AES-256-GCM envelope sealer), so the production assembler wires the real sealer and tests wire a deterministic dev sealer. The sealer is mandatory and fails construction loud when missing: a token that cannot be authenticated is never treated as review state.

type UserSkillImportReview added in v1.28.0

type UserSkillImportReview struct {
	// Name is the CANONICAL package/skill name (the stored target-key
	// identity).
	Name string
	// Title is the human-readable title (may be empty).
	Title string
	// Trigger is the planner-visible match cue.
	Trigger string
	// TaskType is the planner-facing task class (may be empty).
	TaskType string
	// Tags are the search/classification tags.
	Tags []string
	// StepCount is the ordered procedural step count.
	StepCount int
	// RequiredTools / RequiredNS / RequiredTags are the applicability
	// metadata (never grants).
	RequiredTools []string
	RequiredNS    []string
	RequiredTags  []string
	// SupportFiles is the ordered normalized support manifest (canonical
	// path, MIME, exact size, digest per entry). A resource-free package
	// carries an empty manifest.
	SupportFiles []UserSkillImportSupportSummary
	// ContentHash is the canonical stored-row content hash of the skill
	// AS STORED (ScopeUser, effective agent, canonical name).
	ContentHash string
	// PackageHash is the versioned reviewed package hash
	// ("v1:<64-hex>") — the hash the caller reviews and Commit echoes.
	PackageHash string
}

UserSkillImportReview is the closed, bounded, normalized review of one parsed package. Every field is server-derived from the canonical package; none of them can be submitted back to Commit as authority (Commit carries only the opaque proposal token, the reviewed hash, the expected config hash, the reviewed canonical name, and the replace consent).

type UserSkillImportService added in v1.28.0

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

UserSkillImportService implements the two-phase verified-caller import. It is immutable after construction and safe for concurrent reuse by N goroutines: it holds only the injected seams, the reach gates, and a clock + logger; per-call state lives in arguments, and cross-process serialization rides the durable conditional primitives.

func NewUserSkillImportService added in v1.28.0

NewUserSkillImportService builds the two-phase import service over the seven mandatory seams: the production importer, the caller-owned ArtifactStore, the token sealer, the StateStore commit ledger, the mandatory SkillStore package surface, the read-only agent-config reader, and the capability-policy projection. A nil seam fails loud with ErrUserSkillImportMisconfigured rather than building a service that would nil-panic on the first request. The returned *UserSkillImportService is immutable after construction and safe for concurrent use by N goroutines.

func (*UserSkillImportService) Commit added in v1.28.0

Commit performs the explicit second phase: it authenticates and strictly decodes the sealed proposal token and refuses every stale form (oversize / malformed base64 / failed authentication / unknown schema / malformed claims / cross actor-agent-session / expired / echo mismatch), re-runs every identity / reach / retirement / artifact / importer / config / policy / ceiling / boot-owned check, and then performs THE ONE conditional PutInstalledPackage write (the atomic package+membership unit), serialized through the token-derived commit ledger. Response-loss replay returns the same terminal result without a second write; a competing winner is never overwritten.

func (*UserSkillImportService) Validate added in v1.28.0

Validate performs the ZERO-write first phase: verified identity + signed reach, retirement gate, the caller-owned immutable artifact read, THE production importer/validator parse, the capability-policy review (warnings, never grants), the user-scope config base snapshot, and the sealing of the versioned claims into the opaque proposal token. No SkillStore body/package write, no agent-config membership write, and no StateStore proposal-ledger write happens.

type UserSkillImportSupportSummary added in v1.28.0

type UserSkillImportSupportSummary struct {
	Path   string
	Mime   string
	Size   int64
	Digest string
}

UserSkillImportSupportSummary is ONE bounded entry of the normalized support-manifest review: canonical path, MIME, exact size, digest.

type UserSkillImportValidateRequest added in v1.28.0

type UserSkillImportValidateRequest struct {
	// ArtifactID is the content-addressed ref of the caller-owned package
	// artifact (zip archive or single SKILL.md document).
	ArtifactID string
	// AgentID is the effective agent the reviewed package would be bound
	// to. Agent reach must be signed.
	AgentID string
}

UserSkillImportValidateRequest names the bounded input of the first phase: the caller-owned immutable artifact ref (the `artifacts.put` output under the caller's exact triple) and the effective agent. No tenant, user, session, scope, origin, or audience is selectable.

type UserSkillImportValidateResponse added in v1.28.0

type UserSkillImportValidateResponse struct {
	// ProposalToken is the opaque sealed token the commit echoes. It is
	// base64url of the sealer envelope over the versioned claims.
	ProposalToken string
	// Review is the closed normalized review.
	Review UserSkillImportReview
	// Warnings are non-fatal review notes (e.g. a required tool that is
	// not currently run-visible — applicability metadata only).
	Warnings []string
	// PackageHash is the reviewed versioned package hash
	// (== Review.PackageHash).
	PackageHash string
	// ExpectedContentHash is the caller's user-scope config content hash
	// at validate time ("-" when the caller has no active user revision).
	// Commit requires the echo to match the claims.
	ExpectedContentHash string
	// ExpiresAt bounds the review window; a commit after this time is
	// refused.
	ExpiresAt time.Time
}

UserSkillImportValidateResponse is the first-phase outcome: the opaque sealed proposal token, the closed review, the reviewed hashes, the expected config content hash the caller must echo on commit, the expiry, and the non-fatal warnings. Zero durable skill/package/membership/proposal-ledger mutation happened — the review state rides entirely inside the token.

Jump to

Keyboard shortcuts

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