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
- func ComputeFileDigest(path string) (string, error)
- func ComputeStatus(errors []ErrorEntry, warnings []ErrorEntry, exitCode int) string
- func CreateRunDir(baseDir, toolName string, withPack bool) (runID, runDir string, err error)
- func EnrichResultFromEnv(r *Result) bool
- func FormatDependencyErrors(missing []DependencyError) string
- func FormatTimestamp(t time.Time) string
- func GenerateRunID() string
- func LatestRunDir(packSidecar, toolName string) string
- func NormalizeExitCode(toolExitCode int) (wrapperExitCode int, toolCode *int)
- func ParseTimestamp(s string) (time.Time, error)
- func ValidateDigest(d string) error
- func ValidateOutputPath(runDir, outputPath string) error
- func ValidateResult(r *Result) error
- func WriteResultAtomic(runDir string, result *Result) error
- type Capabilities
- type DependencyError
- type ErrorEntry
- type IdentityInfo
- type OutputEntry
- type ResolvedFromInfo
- type Result
- type RunContextInfo
- type SigningIdentity
- type SyncMetadata
- type ToolInfo
- type WrapperInfo
Constants ¶
const ( StatusSuccess = "success" StatusFailure = "failure" StatusPartial = "partial" )
Status values for result.json
const CurrentProtocolVersion = 1
CurrentProtocolVersion is the current tool protocol version.
const CurrentSchemaVersion = 1
CurrentSchemaVersion is the current result.json schema version.
Variables ¶
This section is empty.
Functions ¶
func ComputeFileDigest ¶
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 ¶
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 ¶
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 ¶
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 ¶
LatestRunDir returns the path to the most recent run directory for a tool. Returns empty string if no runs exist.
func NormalizeExitCode ¶
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 ¶
ParseTimestamp parses a timestamp in the normative protocol format. Uses internal/timestamp package for strict format validation.
func ValidateDigest ¶
ValidateDigest checks if a digest string matches the required format. Uses internal/digest package for centralized format validation.
func ValidateOutputPath ¶
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 ¶
ValidateResult checks if a result has all required fields. Returns typed errors with MissingRequiredField code for programmatic handling.
func WriteResultAtomic ¶
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 ¶
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 ¶
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 ¶
NewToolInfo creates a ToolInfo from capabilities or defaults.
type WrapperInfo ¶
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.