toolprotocol

package
v0.1.4 Latest Latest
Warning

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

Go to latest
Published: Feb 24, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package toolprotocol implements the epack Tool Protocol v1. This package provides types and utilities for tool execution, result handling, and run directory management.

Index

Constants

View Source
const (
	StatusSuccess = "success"
	StatusFailure = "failure"
	StatusPartial = "partial"
)

Status values for result.json

View Source
const CurrentProtocolVersion = 1

CurrentProtocolVersion is the current tool protocol version.

View Source
const CurrentSchemaVersion = 1

CurrentSchemaVersion is the current result.json schema version.

Variables

This section is empty.

Functions

func ComputeFileDigest

func ComputeFileDigest(path string) (string, error)

ComputeFileDigest computes the SHA256 digest of a file in the protocol format. Uses internal/digest package for consistent formatting.

func ComputeStatus

func ComputeStatus(errors []ErrorEntry, warnings []ErrorEntry, exitCode int) string

ComputeStatus determines the status based on errors, warnings, and exit code. This implements the precedence rules from the protocol spec.

func CreateRunDir

func CreateRunDir(baseDir, toolName string, withPack bool) (runID, runDir string, err error)

CreateRunDir creates a run directory with collision handling. Returns the run ID used and the full path to the run directory.

For pack-based runs: baseDir/<pack>.epack/tools/<tool>/<run-id>/ For packless runs: baseDir/runs/<tool>/<run-id>/

SECURITY: Uses safefile.MkdirAllPrivate to prevent symlink-based attacks where an attacker creates a symlink at .epack or runs/ pointing to an arbitrary directory. This ensures all directory creation stays within baseDir.

func EnrichResultFromEnv

func EnrichResultFromEnv(r *Result) bool

EnrichResultFromEnv populates Identity and RunContext fields from environment variables. This is non-destructive: it won't overwrite values the tool already set. Returns true if any fields were modified.

func FormatDependencyErrors

func FormatDependencyErrors(missing []DependencyError) string

FormatDependencyErrors formats missing dependencies into a human-readable error message.

func FormatTimestamp

func FormatTimestamp(t time.Time) string

FormatTimestamp formats a time in the normative protocol format. Uses internal/timestamp package for centralized format enforcement.

func GenerateRunID

func GenerateRunID() string

GenerateRunID generates a unique run ID in the format: YYYY-MM-DDTHH-MM-SS-uuuuuuZ-NNNNNN

The counter guarantees monotonicity within a single process. Cross-process uniqueness is handled by mkdir-retry in CreateRunDir.

func LatestRunDir

func LatestRunDir(packSidecar, toolName string) string

LatestRunDir returns the path to the most recent run directory for a tool. Returns empty string if no runs exist.

func NormalizeExitCode

func NormalizeExitCode(toolExitCode int) (wrapperExitCode int, toolCode *int)

NormalizeExitCode normalizes a tool exit code according to protocol rules. Returns (wrapper exit code, tool exit code pointer). Tool codes 0-9 pass through unchanged. Codes ≥10 are normalized to 1.

func ParseTimestamp

func ParseTimestamp(s string) (time.Time, error)

ParseTimestamp parses a timestamp in the normative protocol format. Uses internal/timestamp package for strict format validation.

func ValidateDigest

func ValidateDigest(d string) error

ValidateDigest checks if a digest string matches the required format. Uses internal/digest package for centralized format validation.

func ValidateOutputPath

func ValidateOutputPath(runDir, outputPath string) error

ValidateOutputPath validates an output path according to protocol rules. Returns nil if valid, or an error describing the problem.

SECURITY: Uses safefile.ValidateRegularFile for symlink-aware validation. This ensures that:

  • The path is relative and doesn't escape runDir
  • No symlinks exist in the path from runDir to the file
  • The final path is a regular file (not dir, symlink, device, etc.)

func ValidateResult

func ValidateResult(r *Result) error

ValidateResult checks if a result has all required fields. Returns typed errors with MissingRequiredField code for programmatic handling.

func WriteResultAtomic

func WriteResultAtomic(runDir string, result *Result) error

WriteResultAtomic writes a result.json atomically using tmp+rename.

SECURITY: Uses safefile.OpenForWrite to prevent symlink attacks. This uses O_NOFOLLOW to atomically refuse to follow symlinks, preventing an attacker from swapping result.json.tmp or result.json with a symlink to write to arbitrary locations.

Note: We don't validate the entire runDir path for symlinks because: 1. CreateRunDir already uses safefile.MkdirAllPrivate which refuses symlinks 2. System symlinks (e.g., /var -> /private/var on macOS) are legitimate 3. OpenFileNoFollow provides atomic symlink protection at file creation time

Types

type Capabilities

type Capabilities struct {
	Name            string `json:"name"`
	Version         string `json:"version"`
	ProtocolVersion int    `json:"protocol_version"`
	Description     string `json:"description,omitempty"`
	RequiresPack    bool   `json:"requires_pack"`
	Network         bool   `json:"network,omitempty"`

	// Tool dependencies (wrapper may check these before invocation)
	RequiresTools   []string `json:"requires_tools,omitempty"`   // Tools that must run first (e.g., ["index"])
	RequiresOutputs []string `json:"requires_outputs,omitempty"` // Output files that must exist (e.g., ["index/outputs/embeddings.json"])

	// Optional fields for future registry integration
	Publisher       string   `json:"publisher,omitempty"`
	Repo            string   `json:"repo,omitempty"`
	SigningIdentity string   `json:"signing_identity,omitempty"`
	Commands        []string `json:"commands,omitempty"`
}

Capabilities represents the JSON returned by a tool's --capabilities flag.

type DependencyError

type DependencyError struct {
	Tool   string // Required tool name (empty if checking output directly)
	Output string // Required output path (if checking requires_outputs)
}

DependencyError represents a missing tool dependency.

func CheckDependencies

func CheckDependencies(caps *Capabilities, packSidecar string) []DependencyError

CheckDependencies verifies that required tools have been run and required outputs exist. Returns a list of missing dependencies. Empty list means all dependencies are satisfied.

packSidecar is the path to <pack>.epack directory. For packless runs, pass empty string (dependencies cannot be checked).

type ErrorEntry

type ErrorEntry struct {
	Code    string `json:"code"`
	Message string `json:"message"`
	Path    string `json:"path,omitempty"`
	Details any    `json:"details,omitempty"`
}

ErrorEntry is a structured error or warning.

type IdentityInfo

type IdentityInfo struct {
	Workspace string `json:"workspace,omitempty"`  // Workspace/org context
	Actor     string `json:"actor,omitempty"`      // User, service account, or CI identity
	ActorType string `json:"actor_type,omitempty"` // "user", "service", or "ci"
	AuthMode  string `json:"auth_mode,omitempty"`  // "interactive", "api_key", or "ci_token"
}

IdentityInfo contains optional identity metadata for audit trails. This enables tracking who initiated a run and how they authenticated.

type OutputEntry

type OutputEntry struct {
	Path      string `json:"path"`
	MediaType string `json:"media_type"`
	Digest    string `json:"digest,omitempty"`
	Bytes     int64  `json:"bytes,omitempty"`
}

OutputEntry describes a file produced by the tool.

type ResolvedFromInfo

type ResolvedFromInfo struct {
	Registry   string `json:"registry,omitempty"`   // Registry name (e.g., "github", "locktivity")
	Descriptor string `json:"descriptor,omitempty"` // Original descriptor (e.g., "owner/repo@^1.0.0")
}

ResolvedFromInfo captures where the tool was resolved from. This provides traceability back to the registry/source.

type Result

type Result struct {
	SchemaVersion int           `json:"schema_version"`
	Wrapper       WrapperInfo   `json:"wrapper"`
	Tool          ToolInfo      `json:"tool"`
	RunID         string        `json:"run_id"`
	PackPath      string        `json:"pack_path,omitempty"`   // Omit for packless runs
	PackDigest    string        `json:"pack_digest,omitempty"` // Omit for packless runs
	StartedAt     string        `json:"started_at"`
	CompletedAt   string        `json:"completed_at"`
	DurationMs    int64         `json:"duration_ms"`
	ExitCode      int           `json:"exit_code"`
	ToolExitCode  *int          `json:"tool_exit_code"` // null if tool never ran
	Status        string        `json:"status"`         // success, failure, partial
	Inputs        any           `json:"inputs"`         // Tool inputs (may be empty {})
	Outputs       []OutputEntry `json:"outputs"`
	Errors        []ErrorEntry  `json:"errors"`
	Warnings      []ErrorEntry  `json:"warnings"`

	// Optional fields for run metadata and provenance.
	// These are populated by tools or orchestration systems that need to track
	// identity, CI context, or sync state. Tools may leave these empty.
	Sync       *SyncMetadata   `json:"sync,omitempty"`        // Sync/ledger state
	Identity   *IdentityInfo   `json:"identity,omitempty"`    // Actor identity for audit trails
	RunContext *RunContextInfo `json:"run_context,omitempty"` // CI/environment context
	RunDigest  string          `json:"run_digest,omitempty"`  // Cryptographic hash of run for deduplication

	// Supply chain provenance fields (populated by wrapper from lockfile).
	// These fields are always set when running a locked tool.
	Signing      *SigningIdentity  `json:"signing,omitempty"`       // Sigstore signing identity
	ResolvedFrom *ResolvedFromInfo `json:"resolved_from,omitempty"` // Resolution provenance
}

Result represents the result.json schema for tool runs. All fields marked as "Yes" in Required are always present.

func NewResult

func NewResult(toolName, toolVersion, wrapperVersion string) *Result

NewResult creates a Result with required fields and correct schema version. This is the recommended way to create Result structs to ensure invariants are met.

The returned Result has:

  • SchemaVersion set to CurrentSchemaVersion
  • Wrapper info populated
  • Tool info populated with protocol version
  • RunID generated
  • Timestamps set
  • Empty but non-nil slices for Outputs, Errors, Warnings

Callers should set additional fields like PackPath, ExitCode, etc.

func ReadResult

func ReadResult(path string) (*Result, error)

ReadResult reads and parses a result.json file. Unknown fields are preserved (forward compatibility). SECURITY:

  • Enforces size limit to prevent memory exhaustion from malicious tool output.
  • Validates no duplicate JSON keys to prevent ambiguous field overrides.

type RunContextInfo

type RunContextInfo struct {
	CI         bool   `json:"ci,omitempty"`          // True if running in CI environment
	CIProvider string `json:"ci_provider,omitempty"` // e.g., "github-actions", "gitlab-ci"
	Repo       string `json:"repo,omitempty"`        // Source repository URL
	Commit     string `json:"commit,omitempty"`      // Git commit SHA
	Branch     string `json:"branch,omitempty"`      // Git branch or ref
	RunnerOS   string `json:"runner_os,omitempty"`   // e.g., "linux", "darwin"
	RunnerArch string `json:"runner_arch,omitempty"` // e.g., "amd64", "arm64"
}

RunContextInfo contains optional CI/environment metadata. This enables tracking the execution environment for provenance.

type SigningIdentity

type SigningIdentity struct {
	Issuer              string `json:"issuer,omitempty"`                // OIDC issuer (e.g., "https://token.actions.githubusercontent.com")
	Subject             string `json:"subject,omitempty"`               // Certificate subject (e.g., workflow path)
	SourceRepositoryURI string `json:"source_repository_uri,omitempty"` // Source repo from Sigstore cert
	SourceRepositoryRef string `json:"source_repository_ref,omitempty"` // Source ref from Sigstore cert
}

SigningIdentity contains cryptographic signing information from the lockfile. This provides supply chain provenance for the tool binary.

type SyncMetadata

type SyncMetadata struct {
	LedgerID  *string `json:"ledger_id"`           // External ledger/database ID after sync
	SyncedAt  *string `json:"synced_at"`           // Timestamp when run was synced
	Workspace string  `json:"workspace,omitempty"` // Workspace/org context
}

SyncMetadata contains optional metadata for run synchronization. This enables tracking which runs have been synced to external systems.

type ToolInfo

type ToolInfo struct {
	Name            string `json:"name"`
	Version         string `json:"version"`
	ProtocolVersion int    `json:"protocol_version"`
}

ToolInfo contains metadata about the tool that was executed.

func NewToolInfo

func NewToolInfo(name, version string, protocolVersion int) ToolInfo

NewToolInfo creates a ToolInfo from capabilities or defaults.

type WrapperInfo

type WrapperInfo struct {
	Name    string `json:"name"`
	Version string `json:"version"`
}

WrapperInfo contains metadata about the wrapper that orchestrated the run.

func NewWrapperInfo

func NewWrapperInfo(version string) WrapperInfo

NewWrapperInfo creates a WrapperInfo with the standard wrapper name.

Jump to

Keyboard shortcuts

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