provenance

package
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Jul 30, 2026 License: MIT Imports: 5 Imported by: 0

Documentation

Overview

Package provenance defines a versioned, JSON-marshalable schema for recording what an AI coding agent did to a repository and why, per ADR-0032 ("Agent-First Development Experience"), P1: "Add provenance and handoff JSON".

This package is a standalone data schema. It intentionally does not import github.com/mediusfy/modulex or any other package in this repository: a future `modulex agent handoff` CLI (out of scope for this package), a CI step, or any other tool can depend on provenance alone to produce or consume an Envelope, without pulling in the runtime library.

Building a handoff artifact

env := provenance.Envelope{
    SchemaVersion: provenance.SchemaVersion,
    Repository: provenance.RepoState{
        Path:   "/repo",
        Branch: "MOD-66-provenance-handoff-json",
        Commit: "abc1234",
        Dirty:  false,
    },
    Agent: provenance.AgentInfo{
        Name: "claude",
        Tool: "claude-code",
    },
    CreatedAt: time.Now().UTC(),
}
env.Redact()
if err := env.Validate(); err != nil {
    return err
}
b, err := json.Marshal(env)

See docs/planning/provenance-handoff-schema.md for the full guide, including the redaction and validation guarantees and their limits.

Index

Constants

View Source
const SchemaVersion = "1.0.0"

SchemaVersion is the current version of the Envelope schema, using a plain semver string (not a Go module path or API version) because the schema is consumed as data, not as a Go API: it may be read by non-Go tooling (a future CLI, CI steps, MCP clients) that has no notion of Go module compatibility rules. Bump the minor version for backward-compatible additions (new optional fields) and the major version for breaking changes (renamed/removed/retyped fields), and document the change in CHANGELOG.md whenever Envelope's shape changes.

Variables

This section is empty.

Functions

This section is empty.

Types

type AgentInfo

type AgentInfo struct {
	// Name identifies the agent (e.g. "claude", "codex", "kimi").
	Name string `json:"name"`
	// Version is the agent/model version, if known.
	Version string `json:"version,omitempty"`
	// Tool identifies the host tool or SDK (e.g. "claude-code").
	Tool string `json:"tool,omitempty"`
	// ToolVersion is the host tool's version, if known.
	ToolVersion string `json:"tool_version,omitempty"`
	// CLIVersion is the version of the modulex agent CLI (or equivalent)
	// that generated this envelope, if any.
	CLIVersion string `json:"cli_version,omitempty"`
}

AgentInfo records which agent, tool, and CLI produced the envelope.

type Approval

type Approval struct {
	// Action names what was approved (e.g. "push", "release").
	Action     string    `json:"action"`
	ApprovedBy string    `json:"approved_by"`
	ApprovedAt time.Time `json:"approved_at"`
	// Notes is free text. Redact before persisting; see Envelope.Redact.
	Notes string `json:"notes,omitempty"`
}

Approval records a human approval granted for an elevated action (push, release, deletion, infrastructure change, etc.), per ADR-0032's human-approval boundary.

type ChangeType

type ChangeType string

ChangeType describes how a file was affected.

const (
	ChangeAdded    ChangeType = "added"
	ChangeModified ChangeType = "modified"
	ChangeDeleted  ChangeType = "deleted"
	ChangeRenamed  ChangeType = "renamed"
)

type CommandClass

type CommandClass string

CommandClass classifies a command by the kind of impact it can have, per ADR-0032's requirement to "classify commands by filesystem, network, destructive, and approval impact."

const (
	// ClassSafe commands are read-only and side-effect-free.
	ClassSafe CommandClass = "safe"
	// ClassMutating commands write to the local filesystem/repository.
	ClassMutating CommandClass = "mutating"
	// ClassNetworked commands perform network I/O.
	ClassNetworked CommandClass = "networked"
	// ClassDestructive commands can delete or irreversibly overwrite data.
	ClassDestructive CommandClass = "destructive"
	// ClassApprovalRequired commands require explicit human approval before
	// running (e.g. push, release, infrastructure changes).
	ClassApprovalRequired CommandClass = "approval_required"
)

type CommandResult

type CommandResult struct {
	// Name is the command or Make target invoked (e.g. "make test").
	Name           string       `json:"name"`
	Args           []string     `json:"args,omitempty"`
	Classification CommandClass `json:"classification"`
	Status         Status       `json:"status"`
	// ExitCode is the process exit code. It is meaningless (and should be
	// ignored by consumers) when Status is StatusSkipped,
	// StatusUnavailable, or StatusApprovalRequired, so it is a pointer:
	// nil means "no exit code", distinct from an explicit 0 (success).
	ExitCode *int `json:"exit_code,omitempty"`
	// Duration is wall-clock time the command took to run, encoded as a
	// nanosecond count (time.Duration's default JSON encoding). Zero for
	// commands that never ran.
	Duration time.Duration `json:"duration_ns,omitempty"`
	// EnvironmentNeeds lists tools, services, or credentials the command
	// required (e.g. "docker", "GITHUB_TOKEN"), without ever including
	// their values.
	EnvironmentNeeds []string `json:"environment_needs,omitempty"`
	// Output is a free-text capture of command output. Redact before
	// persisting; see Envelope.Redact.
	Output string `json:"output,omitempty"`
	// Reason explains a StatusSkipped/StatusUnavailable/
	// StatusApprovalRequired status. Required (non-empty) in those cases;
	// see Envelope.Validate.
	Reason string `json:"reason,omitempty"`
}

CommandResult records one command the agent ran: its classification, outcome, exit code, duration, and any environment requirements. Output is free text and MUST be passed through Envelope.Redact (or independently scrubbed) before persisting or transmitting the envelope; see the package doc and docs/planning/provenance-handoff-schema.md.

type Envelope

type Envelope struct {
	SchemaVersion string    `json:"schema_version"`
	Repository    RepoState `json:"repository"`
	Agent         AgentInfo `json:"agent"`
	// Changes lists every file the agent touched, in the order they were
	// changed.
	Changes []FileChange `json:"changes,omitempty"`
	// Commands lists every command the agent ran, in execution order.
	Commands []CommandResult `json:"commands,omitempty"`
	// Verification lists every verification step (focused, full, boundary,
	// compatibility, security, secret-scan, changelog, ...), in the order
	// they were run.
	Verification []VerificationResult `json:"verification,omitempty"`
	// Approvals lists human approvals granted for elevated actions, if any.
	Approvals []Approval `json:"approvals,omitempty"`
	// Rollback describes rollback availability/status for this change. Nil
	// means rollback was not assessed (distinct from an assessed-but-
	// unavailable RollbackStatus{Available: false}).
	Rollback *RollbackStatus `json:"rollback,omitempty"`
	// CreatedAt is when this envelope was produced.
	CreatedAt time.Time `json:"created_at"`
}

Envelope is the top-level, versioned provenance/handoff record for one unit of agent work. It is designed to be marshaled to JSON and attached to a PR description, a CI artifact, an MCP response, or an audit log.

json.Marshal(envelope) produces a deterministic field order because every field is either a scalar or an ordered slice — there are no map types in this schema, so there is nothing that needs sorting before marshaling (contrast with the core module's Manager.Diagnostics/ModuleContract, which do sort map-derived slices for the same determinism goal; this package does not import or reference that code).

func (*Envelope) Redact

func (e *Envelope) Redact()

Redact scrubs secret-shaped values from every free-text field in the envelope (command args/output/reason, verification message/reason, approval notes, rollback notes), replacing matches in place with redactionMarker ("[REDACTED]").

This is a best-effort pattern-based safety net (see secretPatterns' doc comment), not a guarantee that no secret can leak through. Callers should still avoid putting secrets into these fields in the first place, per docs/planning/agent-safety-policy.md.

func (*Envelope) Validate

func (e *Envelope) Validate() error

Validate checks that the envelope is structurally well-formed and free of unredacted secret-shaped values, returning a single error (via errors.Join) naming every problem found, or nil if the envelope is valid.

Structural checks:

  • SchemaVersion, Repository.Path, Repository.Commit, and CreatedAt are required (non-empty/non-zero).
  • Every CommandResult and VerificationResult with Status == StatusSkipped or StatusUnavailable must carry a non-empty Reason.

Secret checks: every free-text field (command args/output/reason, verification message/reason, approval notes, rollback notes) is scanned with the same best-effort detection Redact uses. A caller should call Redact before Validate; Validate exists as a backstop so an envelope with a live secret in it cannot be marshaled/persisted without an explicit error, even if Redact was skipped. See Redact's doc comment for the limits of this detection.

type FileChange

type FileChange struct {
	Path string `json:"path"`
	// OldPath is set for ChangeRenamed to record the file's previous path.
	OldPath string     `json:"old_path,omitempty"`
	Type    ChangeType `json:"type"`
	// Hash is a content hash of the file after the change, in
	// "<algorithm>:<hex>" form (e.g. "sha256:abc123..."). Empty if not
	// computed (e.g. for a deleted file).
	Hash string `json:"hash,omitempty"`
}

FileChange records one changed file and, when available, a content hash of the resulting artifact (e.g. "sha256:<hex>") so a reviewer or CI system can verify the handoff matches the actual diff.

type RepoState

type RepoState struct {
	Path   string `json:"path"`
	Branch string `json:"branch,omitempty"`
	Commit string `json:"commit"`
	// Dirty is true if the worktree had uncommitted changes (staged or
	// unstaged) at the time this envelope was produced.
	Dirty bool `json:"dirty"`
}

RepoState records the repository path, branch, commit, and dirty-worktree state the envelope was produced from.

type RollbackStatus

type RollbackStatus struct {
	// Available is true if a rollback path (e.g. a git revert, a retained
	// patch) exists for this change.
	Available bool `json:"available"`
	// Applied is true if a rollback has actually been performed.
	Applied bool `json:"applied"`
	// Method describes the rollback mechanism (e.g. "git revert <sha>").
	Method string `json:"method,omitempty"`
	// Notes is free text. Redact before persisting; see Envelope.Redact.
	Notes string `json:"notes,omitempty"`
}

RollbackStatus records whether the change can be rolled back and whether a rollback has been applied.

type Status

type Status string

Status is the outcome of a command or verification step. Modeling this as an explicit enum rather than a bool is required so that "did not run" and "ran and passed" are never confused: a missing tool, an unmet approval gate, and an intentional skip are all distinct from both success and failure.

const (
	// StatusPass means the step ran and succeeded.
	StatusPass Status = "pass"
	// StatusFail means the step ran and failed.
	StatusFail Status = "fail"
	// StatusSkipped means the step was intentionally not run (e.g. out of
	// scope for this change). Requires a non-empty Reason.
	StatusSkipped Status = "skipped"
	// StatusUnavailable means the step could not be run (e.g. a required
	// tool or service was missing). Requires a non-empty Reason.
	StatusUnavailable Status = "unavailable"
	// StatusApprovalRequired means the step is gated behind human approval
	// and has not (yet) been approved.
	StatusApprovalRequired Status = "approval_required"
)

type VerificationCategory

type VerificationCategory string

VerificationCategory groups a VerificationResult by the kind of check it represents, per ADR-0032's "focused and full verification results" and "boundary, compatibility, security, and secret-scan results." Modeling these as a Category field on one VerificationResult type (rather than separate top-level slices per category) keeps the schema flat and lets new categories be added without a breaking schema change.

const (
	VerificationFocused       VerificationCategory = "focused"
	VerificationFull          VerificationCategory = "full"
	VerificationBoundary      VerificationCategory = "boundary"
	VerificationCompatibility VerificationCategory = "compatibility"
	VerificationSecurity      VerificationCategory = "security"
	VerificationSecretScan    VerificationCategory = "secret_scan"
	VerificationChangelog     VerificationCategory = "changelog"
)

type VerificationResult

type VerificationResult struct {
	Name     string               `json:"name"`
	Category VerificationCategory `json:"category"`
	Status   Status               `json:"status"`
	Duration time.Duration        `json:"duration_ns,omitempty"`
	// Message is free-text detail (e.g. a failure summary). Redact before
	// persisting; see Envelope.Redact.
	Message string `json:"message,omitempty"`
	// Reason explains a StatusSkipped/StatusUnavailable/
	// StatusApprovalRequired status. Required (non-empty) in those cases;
	// see Envelope.Validate.
	Reason string `json:"reason,omitempty"`
}

VerificationResult records the outcome of one verification step (a focused check, a full repository gate, a boundary/compatibility/security/ secret-scan pass, etc.), distinguished by Category.

Jump to

Keyboard shortcuts

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