Documentation
¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrBadKeyfile: on-disk file isn't parseable / wrong format / // wrong version. Pre-network failure. ErrBadKeyfile = errors.New("keyfile: malformed or unsupported keyfile") // ErrNexusMismatch: the server's nexus_id doesn't match the // keyfile envelope's. Possible causes: keyfile is stale (old // Nexus regenerated identity), Nexus URL points at the wrong // host, or DNS poisoning. Treat as fatal — do NOT send the // encrypted payload to a Nexus that can't decrypt it. ErrNexusMismatch = errors.New("keyfile: server nexus_id does not match envelope nexus_id") // ErrValidationRejected: server returned a non-200. Wrapped // errors carry the body for log surfacing. ErrValidationRejected = errors.New("keyfile: server rejected validation") // ErrBadServerResponse: server returned a 200 with a malformed // body (e.g. ok=false, missing JWT). Distinct from // ErrValidationRejected because it indicates a server bug, not a // keyfile issue — agentfunnel should surface this differently // (don't suggest re-minting; complain about Nexus). ErrBadServerResponse = errors.New("keyfile: server returned 200 with bad response shape") )
Sentinels — agentfunnel surfaces specific shapes to the operator (e.g. "wrong nexus" should print a hint pointing at the right URL).
Functions ¶
This section is empty.
Types ¶
type Client ¶
Client performs the spec §5 handshake against a Nexus. Configurable HTTP client lets callers inject a TLS-trust-store-aware transport (e.g. when dialing a self-signed dev cert).
func NewClient ¶
func NewClient() *Client
NewClient returns a Client with a sensible default HTTP client (10-second timeout, default transport — system CAs apply, including any tailscale-issued certs already in the trust store).
func (*Client) Validate ¶
Validate runs the spec §5 startup handshake:
- GET <nexus_url>/api/nexus_id, compare to envelope.
- POST <nexus_url>/api/aspect/validate with the encrypted_payload.
- Decode response, extract aspect_name from the JWT sub claim (parse-only, no signature check — we trust the JWT because we trust the TLS cert + the nexus_id match).
Returns ValidationResult on success; sentinel-wrapped errors on failure so the caller can render hints (ErrNexusMismatch suggests re-minting, ErrValidationRejected with body lets the operator see "revoked, current=N").
type Envelope ¶
type Envelope struct {
NexusURL string `json:"nexus_url"`
NexusID string `json:"nexus_id"`
IssuedAt string `json:"issued_at"`
}
Envelope is the on-disk plaintext routing layer. Mirrors nexus/aspects.Envelope — kept in sync by hand because we don't want the runtime taking a build-time dependency on the nexus package.
type GitHubConfig ¶ added in v0.2.0
type GitHubConfig struct {
// Username is the aspect's GitHub login (e.g. "nexus-anvil").
// Used for matching in tools that take an owner/user argument and
// for log + audit display.
Username string `json:"username"`
// Email is the verified address attached to the GitHub account.
// Set as `user.email` on git commits made by this aspect so PR
// authors render correctly.
Email string `json:"email"`
// PAT is a classic Personal Access Token. Treated as a bearer
// secret. Scopes expected by nexus-github-mcp for v1: `repo`,
// `workflow`, `read:org`.
PAT string `json:"pat"`
// DefaultOrg, when set, is the GitHub organisation used by tools
// that don't specify one explicitly (e.g. "CarriedWorldUniverse").
// Optional.
DefaultOrg string `json:"default_org,omitempty"`
}
GitHubConfig carries the per-aspect GitHub credentials. Plaintext at rest alongside the rest of the keyfile; rotate by minting a fresh classic PAT at github.com/settings/tokens and editing this block.
type IMAPConfig ¶
type IMAPConfig struct {
// Host is the IMAP server hostname (e.g. "mail.darksoft.co.nz").
// Without scheme; the client adds TLS based on Port.
Host string `json:"host"`
// Port defaults to 993 (IMAP+TLS) when zero. 143 (STARTTLS) is
// supported but discouraged for production.
Port int `json:"port,omitempty"`
// Username is the full mailbox address (e.g.
// "nexus@darksoft.co.nz").
Username string `json:"username"`
// Password is the mailbox password (or app-password for providers
// that support them).
Password string `json:"password"`
// DefaultFolder is the folder used when an MCP tool call doesn't
// specify one. Empty → "INBOX".
DefaultFolder string `json:"default_folder,omitempty"`
}
IMAPConfig carries the credentials an aspect uses to read + manage its mailbox via IMAP. Plaintext at rest alongside the rest of the keyfile; the trust boundary is the keyfile itself.
type JiraConfig ¶
type JiraConfig struct {
// Site is the Atlassian Cloud hostname (e.g.
// "carriedworlduniverse.atlassian.net"). Without scheme.
Site string `json:"site"`
// Email is the Atlassian account email paired with APIToken for
// Basic-auth REST calls.
Email string `json:"email"`
// APIToken is a personal API token minted at
// id.atlassian.com/manage-profile/security/api-tokens. Treated
// as a bearer secret.
APIToken string `json:"api_token"`
// ProjectKey is the default Jira project key (e.g. "NEX") used
// when an MCP tool call doesn't specify one. Optional.
ProjectKey string `json:"project_key,omitempty"`
}
JiraConfig carries the Atlassian Cloud credentials an aspect uses to interact with the issue tracker over REST. Plaintext at rest alongside the rest of the keyfile; rotate by re-minting the token in id.atlassian.com and editing the keyfile.
type Keyfile ¶
type Keyfile struct {
Version int `json:"version"`
Format string `json:"format"`
Envelope Envelope `json:"envelope"`
EncryptedPayload string `json:"encrypted_payload"`
// Jira is an optional per-aspect Atlassian/Jira credential block.
// Pure aspect-side config; never sent to nexus. nexus-jira-mcp
// reads it from the same keyfile so the operator stores one file
// per aspect instead of two. When the aspect isn't using Jira the
// block stays absent.
Jira *JiraConfig `json:"jira,omitempty"`
// IMAP is an optional per-aspect mailbox credential block read by
// nexus-imap-mcp. Holds the aspect's mailbox-side credentials (host,
// port, username, password) so the aspect can read, move, and
// expunge mail. Today only shadow uses this — for driving OTP-
// gated flows on behalf of other aspects — but the schema lives on
// every keyfile so future aspect-owned mailboxes are zero-friction.
IMAP *IMAPConfig `json:"imap,omitempty"`
// GitHub is an optional per-aspect GitHub credential block read by
// nexus-github-mcp. Provides the classic PAT + commit identity
// (username + email) so aspect-attributed PRs and commits don't
// collapse to the host's shared `gh` auth. Absent when the aspect
// doesn't drive GitHub directly.
GitHub *GitHubConfig `json:"github,omitempty"`
}
Keyfile is the on-disk JSON document.
type PersonalityBundle ¶
type PersonalityBundle struct {
NexusMD string `json:"nexus_md"`
SoulMD string `json:"soul_md"`
PrimerMD string `json:"primer_md"`
Composed string `json:"composed"`
Version int64 `json:"version"`
UpdatedAt string `json:"updated_at"`
}
PersonalityBundle is what the validation response delivers. Wire shape: see broker/validate_endpoint.go's personalityWire — must stay in sync field-for-field with that struct.
type ValidationResult ¶
type ValidationResult struct {
// AspectName is the aspect_name from the decrypted payload (returned
// by Nexus in the JWT sub claim — but agentfunnel doesn't decode
// the JWT itself; it trusts the Nexus that issued it).
//
// Populated from the keyfile envelope's nexus_id flow indirectly:
// the Nexus echoes the aspect_name through the validation logic,
// but the success response shape doesn't carry it (the JWT does).
// We pull it from the Aspect field below, which is set client-side
// from the JWT's sub claim — see Validate.
AspectName string
// SessionJWT is the bearer agentfunnel uses for /connect and
// subsequent requests.
SessionJWT string
// SessionExpiresAt is when the JWT becomes invalid. agentfunnel
// re-validates before this point to refresh.
SessionExpiresAt time.Time
// Personality is the per-aspect bundle straight from the response.
// Composed is the canonical per-aspect prompt; agentfunnel's
// caller layers CentralNexusMD ABOVE it (per Part 9 decomposition
// spec) — the per-aspect Composed must NOT include central
// content.
Personality PersonalityBundle
// CentralNexusMD is nexus_settings.nexus_md from the Nexus —
// network-wide operational scope shared by every aspect (Part 9).
// Empty when the Nexus isn't running Part 9 (legacy validators).
// agentfunnel layers it above Personality.NexusMD in the composed
// prompt; see runtime/cmd/agentfunnel for the concat logic.
CentralNexusMD string
// CentralVersion lets agentfunnel detect when central content
// changes between re-validations, independent of personality.Version.
CentralVersion int64
// Provider/Model identify the bridle backend.
Provider string
Model string
// MCPProfile is the aspect's resolved MCP-server profile (NEX-169):
// the stored JSON blob from mcp_profiles.profile with every
// ${credential:NAME.field} placeholder substituted with the
// plaintext credential value. Empty when the Nexus has no
// credentials store wired or no profile is configured.
MCPProfile string
// NexusURL is the WS endpoint agentfunnel should dial. Drawn from
// the keyfile envelope, surfaced here for caller convenience.
NexusURL string
// NexusID is the verified Nexus instance ID (envelope == server).
// Useful for log correlation.
NexusID string
}
ValidationResult is the digested output of a successful handshake. agentfunnel uses these fields directly: JWT for the WS bearer, SystemPrompt for funnel.Config, Provider+Model for bridle setup, AspectName for register frame and logging.