keyfile

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: May 19, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
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

type Client struct {
	HTTP *http.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

func (c *Client) Validate(ctx context.Context, kf *Keyfile) (*ValidationResult, error)

Validate runs the spec §5 startup handshake:

  1. GET <nexus_url>/api/nexus_id, compare to envelope.
  2. POST <nexus_url>/api/aspect/validate with the encrypted_payload.
  3. 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.

func Load

func Load(path string) (*Keyfile, error)

Load reads and parses an on-disk keyfile. Validates format + version + that all required envelope fields are non-empty. Does NOT touch the network.

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.

Jump to

Keyboard shortcuts

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