verify

package
v0.32.2 Latest Latest
Warning

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

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

Documentation

Overview

Package verify implements the verification check runner for the "build", "test", "page", and "interaction" check kinds: after a turn that changed application code, the runtime (not the model) runs the project's build and test commands, and — for page and interaction checks — starts the app from the manifest's dev command and port and drives a headless browser: page checks open each listed route and fail on console errors, while interaction checks run the plan's scripted browser steps and confirm the expected outcome. Manual items are listed in the result and never gated. It reports a structured result (checks, pass/fail, output excerpts, screenshot references) that the turn-end hook and the final-reply contract consume.

Where commands come from: a check command comes only from (1) the project's starter manifest (.sprout/starter.json) or (2) the project's explicit configuration (configuration.VerificationConfig.BuildCommand / .TestCommand, set by a human in the project layer .sprout/workspace.json or in global config) — never from model output. The Runner's API has no parameter or field that accepts a command: an active plan's acceptance items are read only for their ids and kinds, and their Check field (which may carry model-proposed commands, because plans are written by the model) is ignored. Without an active plan, the build and test commands still run as a baseline.

The package is stdlib plus the pure-data packages (plancontract, startermanifest) and their file-system halves (planstore, starterstore), so it can be imported by the CLI and by WASM builds.

Index

Constants

View Source
const DefaultDevServerTimeout = 30 * time.Second

DefaultDevServerTimeout bounds how long a page check waits for the manifest's dev server to come up on its port: thirty seconds covers a cold dev-server start without stalling a turn.

View Source
const DefaultMaxExcerptBytes = 8 * 1024

DefaultMaxExcerptBytes bounds Check.Excerpt by default: an excerpt keeps the head and the tail of a command's output (failures surface at the tail) plus an explicit truncation marker, so a result stays small even for verbose builds.

View Source
const DefaultTimeout = 5 * time.Minute

DefaultTimeout bounds a single verification check when the Runner has no explicit timeout: five minutes covers a normal build plus test suite without stalling a turn.

View Source
const MaxProbeFileBytes = 1 << 20

MaxProbeFileBytes caps the size of a file the fingerprint hashes: a large file (a binary, a bundle) is represented by its size only, so a quality run never reads a project's entire tree into memory.

View Source
const RequireTestCheckKind plancontract.Kind = "require_test"

RequireTestCheckKind is the check kind for the require-a-test-for-new- behavior check. It is not a plancontract acceptance kind (no plan item carries it): it is a verification-time requirement the run reports as a check, so its result flows through the same reporting and repair loop.

Variables

This section is empty.

Functions

func IsTestFilePath

func IsTestFilePath(p string) bool

IsTestFilePath reports whether a workspace path is a test file, using filename conventions rather than content inspection (so the check stays a pure path predicate). It recognizes the common Go, JavaScript/TypeScript, Python, Ruby, Rust, and Java conventions:

  • a base name ending in "_test" or "_spec" with a known source extension (`foo_test.go`, `foo_spec.rb`);
  • a base name ending in ".test" or ".spec" with a known source extension (`Button.test.tsx`, `Button.spec.ts`);
  • a base named `test_<something>` with a known source extension (`test_foo.py`);
  • a base name of `test`, `tests`, `spec`, or `specs` with a known source extension (`test.ts`);
  • any file under a `test`, `tests`, `__tests__`, `spec`, or `specs` directory at any depth (`test/unit/foo.ts`).

The match is case-insensitive. A path that only contains the word test as a substring (`contest.go`, `latest.ts`) is not a test file.

Types

type Check

type Check struct {
	// Kind is the check's plancontract kind (build or test).
	Kind plancontract.Kind `json:"kind"`
	// Items are the ids of the plan acceptance items this check covers
	// (empty for baseline checks and when the plan declares none).
	Items []string `json:"items,omitempty"`
	// Command is the trusted command that ran, or "" when the check was
	// skipped because no command was configured.
	Command string `json:"command,omitempty"`
	// Skipped marks a check that did not run: no trusted command was
	// available, or the run was cancelled. Skipped checks do not gate
	// the result (see Result.Passed) but are listed in it, so the
	// result always says what could not be verified.
	Skipped bool `json:"skipped,omitempty"`
	// Passed reports whether the check passed. For a skipped check it is
	// false.
	Passed bool `json:"passed,omitempty"`
	// Reason explains a skipped or abnormal check (timeout, executor
	// error, cancellation). It is empty for a clean pass or a normal
	// non-zero exit (the output excerpt carries the evidence there).
	Reason string `json:"reason,omitempty"`
	// Excerpt is a bounded excerpt of the check's combined output
	// (the evidence).
	Excerpt string `json:"excerpt,omitempty"`
	// Routes are the routes a page check attempted, in manifest order
	// (page checks only; empty for every other kind).
	Routes []string `json:"routes,omitempty"`
	// Screenshots are the per-route screenshot file paths, parallel to
	// Routes ("" where a capture failed or the route never reached a
	// browser capture; page checks only).
	Screenshots []string `json:"screenshots,omitempty"`
	// Steps are the scripted browser steps an interaction check ran, in
	// execution order (interaction checks only; empty for every other kind).
	// They come from the plan's interaction acceptance item.
	Steps []plancontract.BrowseStep `json:"steps,omitempty"`
	// Duration is the wall time the check took to execute.
	Duration time.Duration `json:"duration,omitempty"`
}

Check is the outcome of one verification check. It carries the evidence the contract names: which check ran, its pass/fail state, and a bounded excerpt of its output.

func RequireTestCheck

func RequireTestCheck(in RequireTestInput) *Check

RequireTestCheck returns the failing check a require-a-test run reports when the turn added new behavior and neither a test acceptance item exists nor a test file was added or changed, or nil when the requirement is satisfied, disabled, or not applicable (a turn that changed no application code is not new behavior and is never flagged).

A satisfied or not-applicable requirement returns nil rather than a passing check: the check exists only as a gate, so a run that satisfies it adds no check and its pass/fail state is unchanged.

type Commands

type Commands struct {
	// Build is the trusted build command, or "" when none is configured.
	Build string `json:"build,omitempty"`
	// Test is the trusted test command, or "" when none is configured.
	Test string `json:"test,omitempty"`
}

Commands are the trusted build and test commands for one project's verification run: resolved only from the starter manifest and the explicit project configuration. An empty field means "no command for that check" — the runner never guesses one.

type ConfigCommandsProvider

type ConfigCommandsProvider func(root string) (Commands, error)

ConfigCommandsProvider resolves a project's explicit build and test commands — the trusted source a human sets in the project's configuration. Model output never reaches this provider.

func ConfigurationCommands

func ConfigurationCommands(cfg *configuration.Config) ConfigCommandsProvider

ConfigurationCommands returns a ConfigCommandsProvider backed by a merged *configuration.Config — the "explicit project configuration" source of the explicit project configuration: verification.build_command and verification.test_command, set by a human in the project layer (.sprout/workspace.json) or in global config. The provider ignores root: the config is already the merged result for the project.

type DevProcess

type DevProcess struct {
	// contains filtered or unexported fields
}

DevProcess owns one launched dev-server process (the shell that runs the manifest's dev command) and its process group, so a caller can stop the whole tree (the shell and every child it spawned) with one signal. Page checks and the preview manager both drive dev servers through this type, so it is exported: the platform process handling (process groups on Unix, Job Objects on Windows, the no-op WASM stub) lives here and nowhere else.

func StartDevProcess

func StartDevProcess(root, command string, out *bytes.Buffer) (*DevProcess, error)

StartDevProcess starts command through the system shell ("sh -c") in root, capturing combined output to out, in its own process group (pgid == pid) so the whole tree can be signalled to stop.

func (*DevProcess) Stop

func (p *DevProcess) Stop()

Stop SIGKILLs the dev server's process group (the shell and every descendant) so the port is actually freed. A stale pid is harmless: the group is not reaped until Wait returns, so the pid stays allocated to a dead-but-unreaped member and the signal is a no-op.

func (*DevProcess) Wait

func (p *DevProcess) Wait() error

Wait reaps the process, guaranteeing the captured output is complete.

type Executor

type Executor interface {
	// Run executes command in dir (the project root) and returns its
	// outcome. A non-nil error marks a launch/setup failure; a non-zero
	// exit is an Outcome, not an error.
	Run(ctx context.Context, dir, command string) (Outcome, error)
}

Executor executes one trusted verification command and reports its outcome. Implementations must honor ctx (a cancelled or timed-out context stops the command), bound their work, and capture combined output. The command string is trusted input: it comes only from the starter manifest or the explicit project configuration.

type ManifestLoader

type ManifestLoader func(root string) (*startermanifest.StarterManifest, error)

ManifestLoader loads a project's starter manifest. A missing manifest must surface starterstore.ErrNoManifest so the runner can fall back to the explicit project configuration.

type Outcome

type Outcome struct {
	// Passed is true when the command exited 0.
	Passed bool
	// Output is the command's combined stdout and stderr.
	Output string
	// Reason is set when the command did not complete normally
	// (timeout, cancellation); empty for a clean run, pass or fail.
	Reason string
}

Outcome is the raw outcome of executing one verification command.

type PageBrowser

type PageBrowser interface {
	// Open opens url in a headless browser, optionally writing a
	// screenshot to screenshotPath ("" = no screenshot), and reports the
	// observation. A nil error means the route rendered; the caller
	// inspects the observation's ConsoleErrors/PageErrors for failures.
	// errors.Is(err, webcontent.ErrBrowserUnavailable) marks a browser that
	// cannot render here (skipped, not a failure).
	Open(ctx context.Context, url string, screenshotPath string) (*PageObservation, error)
}

PageBrowser is the verify package's narrow browser seam for page checks it opens one route in a headless browser and reports what it observed. The production implementation adapts pkg/webcontent (NewWebcontentPageBrowser); tests inject a fake so a run is deterministic and needs no Chromium.

func NewWebcontentPageBrowser

func NewWebcontentPageBrowser() PageBrowser

NewWebcontentPageBrowser returns the production PageBrowser backed by webcontent.GetGlobalBrowser(). The global browser is lazy (Chromium launches on first use), so this adapter is cheap to construct and WASM-safe: it never launches a browser at construction time.

type PageObservation

type PageObservation struct {
	// URL is the route that was opened.
	URL string
	// Title is the page title after rendering ("" when unknown).
	Title string
	// ConsoleErrors are the console.error-level messages captured on the
	// page (the page's own console.error calls, not the level tag).
	ConsoleErrors []string
	// PageErrors are window errors and unhandled promise rejections.
	PageErrors []string
	// ScreenshotPath is the file the screenshot was saved to, or "" when the
	// capture did not complete (the route is still checked for console
	// errors).
	ScreenshotPath string
}

PageObservation is what a PageBrowser reports for one opened route.

type PlanLoader

type PlanLoader func(root string) (*plancontract.Plan, error)

PlanLoader loads a project's active plan. A missing plan must surface planstore.ErrNoPlan so the runner can run the baseline.

type QualityCheck

type QualityCheck struct {
	// Kind is the step (format or lint).
	Kind QualityKind `json:"kind"`
	// Command is the trusted command that ran, or "" when the step was
	// skipped because no command was configured.
	Command string `json:"command,omitempty"`
	// Skipped marks a step that did not run (no command configured, or the
	// run was cancelled before it started). A skipped step does not gate the
	// result but is listed in it.
	Skipped bool `json:"skipped,omitempty"`
	// Passed reports whether the step passed. For a skipped step it is false.
	Passed bool `json:"passed,omitempty"`
	// Reason explains a skipped or abnormal step (timeout, executor error,
	// cancellation). Empty for a clean pass or a normal non-zero exit (the
	// excerpt carries the evidence there).
	Reason string `json:"reason,omitempty"`
	// Excerpt is a bounded excerpt of the step's combined output.
	Excerpt string `json:"excerpt,omitempty"`
	// Changed reports whether the step rewrote files in the workspace.
	// Reported only for the formatter, whose in-place rewrite is the
	// runtime's own repair (the linter is informational).
	Changed bool `json:"changed,omitempty"`
	// Duration is the wall time the step took to execute.
	Duration time.Duration `json:"duration,omitempty"`
}

QualityCheck is the outcome of one quality step.

type QualityCommands

type QualityCommands struct {
	// Format is the trusted formatter command, or "" when none is configured.
	Format string `json:"format,omitempty"`
	// Lint is the trusted linter command, or "" when none is configured.
	Lint string `json:"lint,omitempty"`
}

QualityCommands are the trusted formatter and linter commands for one project's quality run. An empty field means "no command for that step" — the runner never guesses one.

type QualityConfigCommandsProvider

type QualityConfigCommandsProvider func(root string) (QualityCommands, error)

QualityConfigCommandsProvider resolves a project's explicit formatter and linter commands — the trusted source a human sets in the project's configuration. Model output never reaches this provider.

func QualityConfigurationCommands

func QualityConfigurationCommands(cfg *configuration.Config) QualityConfigCommandsProvider

QualityConfigurationCommands returns a QualityConfigCommandsProvider backed by a merged *configuration.Config — the "explicit project configuration" source of the quality-after-edits step: quality.format_command and quality.lint_command, set by a human in the project layer (.sprout/workspace.json) or in global config. The provider ignores root: the config is already the merged result for the project.

type QualityKind

type QualityKind string

QualityKind names one step of a quality run: the formatter or the linter.

const (
	// QualityFormat is the formatter step. A formatter may rewrite files in
	// place; a rewrite is the runtime's own repair.
	QualityFormat QualityKind = "format"
	// QualityLint is the linter step. It reports findings; a non-zero exit
	// is a failure the caller feeds back to the model.
	QualityLint QualityKind = "lint"
)

type QualityResult

type QualityResult struct {
	// Checks are the step outcomes, in run order (format then lint).
	Checks []QualityCheck `json:"checks"`
	// Errors are run-level findings (a corrupt manifest, a configuration
	// source failure) that prevented some commands from resolving. They are
	// reported, never swallowed.
	Errors []string `json:"errors,omitempty"`
}

QualityResult is the structured outcome of one quality run.

func (*QualityResult) Changed

func (r *QualityResult) Changed() bool

Changed reports whether any step rewrote files (the formatter's in-place repair).

func (*QualityResult) Failed

func (r *QualityResult) Failed() bool

Failed reports whether the run failed: an executed step failed, or a run-level error was recorded. A run whose steps were all skipped did not fail — it checked nothing (see Passed).

func (*QualityResult) Passed

func (r *QualityResult) Passed() bool

Passed reports whether the quality run passed: nothing failed and at least one step actually ran. An all-skipped run passed nothing.

func (*QualityResult) Summary

func (r *QualityResult) Summary() string

Summary renders a compact, deterministic one-line summary of the run.

type QualityRunner

type QualityRunner struct {
	// Manifest loads the project's starter manifest. Default:
	// starterstore.LoadStarterManifest (.sprout/starter.json).
	Manifest ManifestLoader
	// ConfigCommands resolves the project's explicit formatter/linter
	// commands. Nil means "no explicit configuration source".
	ConfigCommands QualityConfigCommandsProvider
	// Exec executes each resolved step command. Default: &ShellExecutor{}.
	Exec Executor
	// Timeout bounds a single step. Default: DefaultTimeout; a non-positive
	// value uses DefaultTimeout.
	Timeout time.Duration
	// MaxExcerptBytes bounds QualityCheck.Excerpt. Default:
	// DefaultMaxExcerptBytes.
	MaxExcerptBytes int
	// Fingerprint bounds the workspace change detection: it walks the root,
	// skipping a fixed set of heavy directories, and hashes each regular
	// file's content up to MaxFileBytes. Nil uses the default fingerprint
	// (defaultFingerprint). Injectable so a test can force a deterministic
	// before/after comparison.
	Fingerprint func(root string) (workspaceFingerprint, error)
}

QualityRunner runs the formatter and linter of a quality-after-edits run.

Every field is injectable so the run is testable without a shell; New() wires the production defaults. A nil field is treated as "absent" rather than panicking — but a QualityRunner without an executor cannot run anything.

func NewQualityRunner

func NewQualityRunner() *QualityRunner

NewQualityRunner returns a QualityRunner with the production defaults: the starter manifest from the project's .sprout/ directory, the shell executor, and the default step and excerpt bounds. The caller wires the explicit project configuration source (QualityConfigurationCommands from a merged configuration.Config) when one exists.

func (*QualityRunner) RunQuality

func (r *QualityRunner) RunQuality(ctx context.Context, root string) (*QualityResult, error)

RunQuality executes the project's formatter then its linter against freshly resolved commands (a snapshot captured immediately before the run) and returns the structured result. See RunQualitySnapshot for the behavior.

func (*QualityRunner) RunQualitySnapshot

func (r *QualityRunner) RunQualitySnapshot(ctx context.Context, root string, snap *QualitySnapshot) (*QualityResult, error)

RunQualitySnapshot executes the project's formatter then its linter against the frozen inputs a turn captured with Snapshot and returns the structured result. A step's command comes from the snapshot, never from a re-read of the manifest: a step with no trusted command is skipped (never guessed).

The formatter runs first so its in-place rewrite is visible to the linter and to the checks the rest of the turn runs; a formatter that rewrote files sets the format check's Changed flag. A formatter failure never aborts the run — the linter still runs and both outcomes are reported.

RunQualitySnapshot returns a non-nil error only for setup problems (no executor, empty root, nil runner, or a nil snapshot). Run-level findings (a corrupt manifest, a configuration source failure) are recorded from the snapshot on the result, never swallowed.

func (*QualityRunner) Snapshot

func (r *QualityRunner) Snapshot(root string) *QualitySnapshot

Snapshot resolves and freezes the project's quality commands. Call it once at the turn's start and pass the result to RunQualitySnapshot for every quality run of the turn. It is a no-op on a nil runner (returns nil): RunQualitySnapshot reports the setup error.

type QualitySnapshot

type QualitySnapshot struct {
	// Commands are the resolved formatter and linter commands.
	Commands QualityCommands
	// Errors are the run-level findings the resolution produced (a corrupt
	// manifest, a configuration source failure).
	Errors []string
}

QualitySnapshot is the frozen input to a quality run: the trusted formatter and linter commands captured once, so every repair round of the turn executes the same commands even if a model edits .sprout/starter.json mid-turn. It mirrors the verification runner's snapshot discipline for the same reason: the model must not be able to change what "clean" means by editing the manifest during a repair round.

type RequireTestInput

type RequireTestInput struct {
	// Enabled gates the check. Off is the default and a complete no-op: the
	// run's checks are unchanged when it is false.
	Enabled bool
	// PlanHasTestItem reports whether the active plan declares at least one
	// acceptance item of kind test. Such an item satisfies the requirement on
	// its own.
	PlanHasTestItem bool
	// ChangedApplicationPaths are the turn's changed paths that count as
	// application code (already filtered by the caller's documentation /
	// bookkeeping rule). An empty set means the turn changed no application
	// code — no new behavior — and the check is not applied.
	ChangedApplicationPaths []string
}

RequireTestInput is the mechanical input the require-a-test-for-new-behavior check reads. The caller (the turn-end hook) supplies the turn's changed application-code paths and whether the active plan declares an acceptance item of kind test; the check never reads the filesystem or the plan file itself.

type Result

type Result struct {
	// Baseline is true when the run happened without an active
	// plan: the build and test commands ran as a baseline.
	Baseline bool `json:"baseline,omitempty"`
	// PlanRevision is the revision of the plan whose acceptance items
	// the checks cover (0 in baseline mode).
	PlanRevision int `json:"plan_revision,omitempty"`
	// Checks are the individual check outcomes, in run order.
	Checks []Check `json:"checks"`
	// Errors are run-level findings (an unreadable plan or manifest, a
	// configuration source failure) that prevented some commands from
	// resolving. They are reported, never swallowed.
	Errors []string `json:"errors,omitempty"`
}

Result is the structured outcome of one verification run. It is plain data: the turn-end hook consumes it, the final-reply contract reports from it, and the progress events record it as a structured event with evidence.

func (*Result) Failed

func (r *Result) Failed() bool

Failed reports whether the run failed: an executed check failed, or a run-level error was recorded. A run whose checks were all skipped did not fail — it verified nothing (see Passed).

func (*Result) Passed

func (r *Result) Passed() bool

Passed reports whether the verification run passed: nothing failed and at least one check actually ran. A run whose checks were all skipped (no trusted command anywhere) passed nothing — partial or vacuous success is never reported as success.

func (*Result) Summary

func (r *Result) Summary() string

Summary renders a compact, deterministic one-line summary of the run for display and for the final reply.

type Runner

type Runner struct {
	// Manifest loads the project's starter manifest. Default:
	// starterstore.LoadStarterManifest (.sprout/starter.json).
	Manifest ManifestLoader
	// Plans loads the active plan. Default: planstore
	// (.sprout/plan.json), not-found as planstore.ErrNoPlan.
	Plans PlanLoader
	// ConfigCommands resolves the project's explicit build/test commands.
	// Nil means "no explicit configuration source".
	ConfigCommands ConfigCommandsProvider
	// Exec executes each resolved check command. Default: &ShellExecutor{}.
	Exec Executor
	// Browser opens routes for page checks. Nil → page
	// checks are skipped with reason "no browser configured". Default:
	// NewWebcontentPageBrowser().
	Browser PageBrowser
	// StepBrowser runs the plan's scripted browser steps for interaction
	// checks. Nil → interaction checks are skipped with
	// reason "no browser configured". Default: NewWebcontentStepBrowser().
	StepBrowser StepBrowser
	// Timeout bounds a single check. Default: DefaultTimeout; a
	// non-positive value uses DefaultTimeout.
	Timeout time.Duration
	// DevServerTimeout bounds how long a page check waits for the manifest's
	// dev server to come up. Default: DefaultDevServerTimeout; a
	// non-positive value uses DefaultDevServerTimeout.
	DevServerTimeout time.Duration
	// ScreenshotDir is where page-check screenshots are written. "" →
	// <root>/.sprout/verify/screenshots (the .sprout/ directory is
	// gitignored project state).
	ScreenshotDir string
	// MaxExcerptBytes bounds Check.Excerpt (see boundedExcerpt).
	// Default: DefaultMaxExcerptBytes.
	MaxExcerptBytes int
	// RequireTest is the mechanical input to the require-a-test-for-new-
	// behavior check: when Enabled, a turn that added new behavior must
	// either carry an active-plan test acceptance item or add/change a test
	// file, and the run reports a failing check otherwise. The default (zero
	// value) is disabled — the run's checks are then unchanged.
	RequireTest RequireTestInput
}

Runner runs the build, test, page, and interaction checks of a verification run.

Every field is injectable so the run is testable without a shell; New() wires the production defaults. Fields that are nil are treated as "absent" (no manifest source, no plan source, no explicit config, no default executor) rather than panicking — but a Runner without an executor cannot run anything.

func New

func New() *Runner

New returns a Runner with the production defaults: the starter manifest and the plan from the project's .sprout/ directory, the shell executor, the webcontent-backed page browser and step browser, and the default per-check, dev-server, and excerpt bounds. The caller wires the explicit project configuration source (ConfigurationCommands from a merged configuration.Config) when one exists.

func (*Runner) Run

func (r *Runner) Run(ctx context.Context, root string) (*Result, error)

Run executes the verification checks for the project rooted at root and returns the structured result. It is implemented as a snapshot-then-run: it captures a fresh Snapshot of the manifest and plan and executes RunSnapshot against it, so a single Run call reads each file exactly once.

  • With an active plan, the runner runs one check per acceptance kind it implements (build and test, page, and interaction), one check per kind for build/test/page (each covering every acceptance item of that kind) and one check per interaction item. A manual check is listed but never executed (it is pre-filled skipped).
  • Without a plan, the build and test commands run as a baseline. A baseline run never produces a page, interaction, or manual check, even when the manifest declares a dev command, port, and routes.

Command resolution is per check kind: the starter manifest's command wins where it is set, the explicit project configuration fills the gap, and a check with no trusted command is skipped (never guessed). The plan's acceptance Check fields are never read for a command, a route, or a step: a page check's routes and an interaction check's dev command come only from the manifest, and an interaction check's steps come only from the plan's frozen interaction items.

Run only returns a non-nil error for setup problems (no executor, empty root, nil runner). Run-level findings (a corrupt manifest or plan, an executor failure) are recorded on the result, never swallowed.

func (*Runner) RunSnapshot

func (r *Runner) RunSnapshot(ctx context.Context, root string, snap *Snapshot) (*Result, error)

RunSnapshot executes the verification checks against the frozen inputs a turn captured with Snapshot: the manifest's commands and the plan's acceptance come from the snapshot, never from a re-read of the files on disk. It is the implementation Run delegates to after taking a fresh snapshot; the two behave identically for a snapshot captured immediately before the run.

Like Run, it returns a non-nil error only for setup problems (no executor, empty root, nil runner, or a nil snapshot). Run-level findings (a corrupt manifest or plan, an executor failure) are recorded on the result, never swallowed; the snapshot's stored load findings are recorded exactly as Run would have recorded them at load time.

func (*Runner) Snapshot

func (r *Runner) Snapshot(root string) *Snapshot

Snapshot captures the manifest and plan a turn's verification runs will use (see the Snapshot type for what is frozen and why). It takes a snapshot-then-run contract: call it once at the turn's start and pass the result to RunSnapshot for every verification run of the turn.

type ShellExecutor

type ShellExecutor struct {
	// Shell is the shell the command runs through (default "sh").
	Shell string
}

ShellExecutor is the default Executor: it runs the command through the system shell ("sh -c") in dir and captures the combined output. When the context expires (the Runner's per-check timeout) it reports a failed outcome with a reason rather than an error, so the result carries the partial output up to the stop.

It is deliberately the only executor in this package: verification commands are trusted by design, and running them under a shell is the mechanism the project's own build and test commands assume.

func (*ShellExecutor) Run

func (e *ShellExecutor) Run(ctx context.Context, dir, command string) (Outcome, error)

Run implements Executor.

type Snapshot

type Snapshot struct {
	// Manifest is the starter manifest captured at snapshot time, or nil
	// when the project has no usable one.
	Manifest *startermanifest.StarterManifest
	// ManifestError is the run-level finding a corrupt manifest produces
	// ("" when the manifest is present or absent).
	ManifestError string
	// Plan is the active plan captured at snapshot time, or nil when the
	// project has no usable plan (absent → baseline; corrupt → baseline
	// plus PlanError).
	Plan *plancontract.Plan
	// PlanError is the run-level finding a corrupt plan produces ("" when
	// the plan is present or absent).
	PlanError string
}

Snapshot is the frozen input to a turn's verification runs: the starter manifest and the active plan captured once, so that every verification run of the turn (every repair round) executes against the same trusted inputs rather than re-reading the files. A model that edits .sprout/starter.json or .sprout/plan.json mid-turn cannot change what "passing" means: the commands, the acceptance set, and the plan's presence are exactly what the turn started with.

Snapshot loads the manifest and the plan once and records their load findings the way Run does today (a corrupt manifest or plan is a run-level finding, never swallowed). It freezes:

  • the manifest's commands (build, test, dev, port, routes);
  • the plan's acceptance (ids, kinds, and the interaction steps); and
  • the plan's presence. A plan present at snapshot time gates its checks for the whole turn even if the plan file is later deleted or corrupted; a project with no plan at snapshot time runs the baseline for the whole turn.

Snapshot reads nothing else and runs nothing; it is safe to call at the turn's start, before any verification run.

type StepBrowser

type StepBrowser interface {
	// RunSteps runs the steps in order against baseURL (the dev server root)
	// and reports the executed actions (evidence). A nil error means every
	// step, including assert-style expected-outcome steps, succeeded.
	// errors.Is(err, webcontent.ErrBrowserUnavailable) marks a browser that
	// cannot render here (skipped, not a failure).
	RunSteps(ctx context.Context, baseURL string, steps []plancontract.BrowseStep) (*StepObservation, error)
}

StepBrowser is the verify package's narrow browser seam for interaction checks: it runs the plan's scripted browser steps in order against a base URL and reports what it observed. The production implementation adapts pkg/webcontent (NewWebcontentStepBrowser); tests inject a fake so a run is deterministic and needs no Chromium.

The steps come only from the active plan's interaction acceptance items — the frozen plan written before the turn, not model output produced mid-turn — so running them is a trusted verification act, not a model-sourced command.

func NewWebcontentStepBrowser

func NewWebcontentStepBrowser() StepBrowser

NewWebcontentStepBrowser returns the production StepBrowser backed by webcontent.GetGlobalBrowser(). The global browser is lazy (Chromium launches on first use), so this adapter is cheap to construct and WASM-safe: it never launches a browser at construction time.

type StepObservation

type StepObservation struct {
	// FinalURL is the page URL after the last step navigated ("" when
	// unknown).
	FinalURL string
	// Title is the page title after the last step ("" when unknown).
	Title string
	// Actions are the executed step descriptions in order — the evidence an
	// interaction check's excerpt carries (the confirmed outcome).
	Actions []string
}

StepObservation is what a StepBrowser reports for one scripted run.

Jump to

Keyboard shortcuts

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