jstestprovider

package
v0.8.1 Latest Latest
Warning

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

Go to latest
Published: Sep 24, 2026 License: AGPL-3.0, AGPL-3.0-or-later Imports: 27 Imported by: 0

Documentation

Overview

Package jstestprovider is an experimental IPR-08 provider slice: one JS/TS unit adapter (Vitest) and one E2E adapter (Playwright) that each produce a receipt binding test/config/app-build/environment identity to per-test execution outcomes, then feed those facts through internal/testvalidity.Project (see ../testvalidity/projection.go). This package carries no qualification authority: a receipt states what was observed, never a "valid" verdict.

Index

Constants

View Source
const (
	AttestedExternalProfile               = "corvint-playwright-external/1"
	ApplicationAttestationProfile         = "corvint-application-attestation/0"
	ApplicationAttestationProviderProfile = "corvint-application-attestation-command/0"
)
View Source
const (
	SensitiveExternalProfile              = "corvint-playwright-external/2"
	SensitiveInputRedactionMarker         = "[REDACTED]"
	SensitiveInputUnredacted              = "sensitive-input-unredacted"
	SensitiveInputDocumentInvalid         = "sensitive-input-document-invalid"
	SensitiveInputActionSyntaxUnsupported = "sensitive-input-action-syntax-unsupported"
	SensitiveInputDepthExceeded           = "sensitive-input-depth-exceeded"
	SensitiveInputStepBoundExceeded       = "sensitive-input-step-bound-exceeded"
	SensitiveInputStringBoundExceeded     = "sensitive-input-string-bound-exceeded"
	SensitiveInputFindingBoundExceeded    = "sensitive-input-finding-bound-exceeded"
)
View Source
const ExternalProfile = "corvint-playwright-external/0"

Variables

This section is empty.

Functions

func EncodeQualified

func EncodeQualified(r Receipt) ([]byte, error)

EncodeQualified emits the frozen canonical envelope used by retention and the strict consumer. The projections are derived, never caller-supplied.

func ParsePlaywrightJSON

func ParsePlaywrightJSON(data []byte) ([]TestOutcome, *InfrastructureFailure, error)

ParsePlaywrightJSON parses one Playwright `--reporter=json` output into TestOutcomes. A run that produced no suites at all (e.g. "No tests found", an unrecognized --project) is reported as a run-level InfrastructureFailure via the returned bool/failure, never as zero silently-passing tests.

func ParseVitestJSON

func ParseVitestJSON(data []byte) ([]TestOutcome, *InfrastructureFailure, error)

ParseVitestJSON parses one Vitest `--reporter=json` output file into TestOutcomes. A file-level entry with status "failed" and no failed assertion (a collection error that prevented any test from running, or a file-level hook such as afterAll that threw after its tests passed) surfaces as one StateInfrastructure outcome rather than being silently dropped, per AGENTS.md invariant 2 (missing evidence must never collapse to certainty). A report with no outcome at all (Vitest found no test files) returns the run-level "no-suites-collected" failure, as ParsePlaywrightJSON does.

func QualifiedApplicationRevision

func QualifiedApplicationRevision(r Receipt, t TestOutcome) (string, bool)

QualifiedApplicationRevision returns the application identity only after the complete external receipt and the selected outcome have been qualified.

func QualifiedReceiptBindingReady

func QualifiedReceiptBindingReady(r Receipt, t TestOutcome) bool

QualifiedReceiptBindingReady verifies the qualified external lifecycle, identity and attempt structure independently of an observed infrastructure outcome. Classification remains the consumer's separate responsibility.

func ReceiptRunProjection

func ReceiptRunProjection(r Receipt) testvalidity.Projection

ReceiptRunProjection projects the receipt's own run-level facts: a run-level InfrastructureFailure (bad command, no tests found, missing browser at collection time) and stale-app-build freshness. It is separate from per-test projections because a run-level infrastructure failure may exist with zero test outcomes, and staleness is a fact about the whole run's served build, not any one test.

func ReceiptTestProjection

func ReceiptTestProjection(r Receipt, t TestOutcome) testvalidity.Projection

ReceiptTestProjection carries lifecycle and identity uncertainty into every qualified row, including retained/MCP readers that recompute projections.

func ToTestProjection

func ToTestProjection(outcome TestOutcome) testvalidity.Projection

ToTestProjection projects one TestOutcome through the shared testvalidity.Project (internal/testvalidity/projection.go). Ordinary per-test states (passed/failed/skipped/flaky) carry ClaimFacts, since they are TCQ-shaped per-test report rows with a known association anchor. Harness-level incomplete states (timedOut/interrupted/infrastructure) that mean the test itself never produced a real report instead carry ExecutionFacts with an INCOMPLETE outcome, matching the LPCV/GLTP cause vocabulary Project already understands. No branch invents a "valid" verdict; every branch states exactly the fact this provider observed.

Types

type Anchor

type Anchor struct {
	File string `json:"file"`
	Line int    `json:"line"`
}

Anchor is one file:line source anchor taken from a reporter's own location field (Playwright) or parsed from its failure stack trace (Vitest, which does not emit a structured location field as of 5.0.0 - see evidence/ipr-08-runner-selection.md section 4/5 and vitest_test.go).

type AppBuildIdentity

type AppBuildIdentity struct {
	Digest  string `json:"digest"`
	Unknown bool   `json:"unknown"`
	Reason  string `json:"reason,omitempty"`
}

AppBuildIdentity is the served app's content identity: a digest of the configured build output directory, or "unknown" when no build directory was configured (Vitest unit runs, or an E2E run with no app dir bound).

func DigestAppBuildDir

func DigestAppBuildDir(dir string) (AppBuildIdentity, error)

DigestAppBuildDir hashes every regular file under dir (relative path plus content), sorted for determinism, into one app-build identity digest. A missing or empty directory returns an explicit AppBuildIdentity rather than a digest of nothing, so "not built yet" is never mistaken for "built and empty." An entry that does not resolve to a regular file (a symlink to a directory, a dangling symlink) makes the identity unknown as well.

type ApplicationArtifactIdentity

type ApplicationArtifactIdentity struct {
	Kind   string `json:"kind"`
	Digest string `json:"digest"`
}

type ApplicationAttestation

type ApplicationAttestation struct {
	Profile       string                        `json:"profile"`
	Repository    ApplicationRepositoryIdentity `json:"repository"`
	Build         ApplicationArtifactIdentity   `json:"build"`
	Configuration ApplicationArtifactIdentity   `json:"configuration"`
	Instance      ApplicationInstanceIdentity   `json:"instance"`
	Health        ApplicationHealth             `json:"health"`
}

type ApplicationAttestationExpectation

type ApplicationAttestationExpectation struct {
	Repository    ApplicationRepositoryExpectation `json:"repository"`
	Build         ApplicationArtifactIdentity      `json:"build"`
	Configuration ApplicationArtifactIdentity      `json:"configuration"`
	InstanceKind  string                           `json:"instanceKind"`
}

type ApplicationAttestationObservation

type ApplicationAttestationObservation struct {
	OutputDigest string                 `json:"outputDigest"`
	Attestation  ApplicationAttestation `json:"attestation"`
}

type ApplicationAttestationProvider

type ApplicationAttestationProvider struct {
	Argv       []string
	ConfigFile string
	Timeout    time.Duration
}

ApplicationAttestationProvider is the typed, bounded local command used to observe an externally owned application. Corvint supplies the canonical configuration bytes on stdin and never supplies lifecycle verbs.

type ApplicationAttestationProviderIdentity

type ApplicationAttestationProviderIdentity struct {
	Profile          string            `json:"profile"`
	Argv             []string          `json:"argv"`
	ExecutablePath   string            `json:"executablePath"`
	ExecutableDigest string            `json:"executableDigest"`
	ConfigPath       string            `json:"configPath"`
	ConfigDigest     string            `json:"configDigest"`
	Environment      map[string]string `json:"environment"`
}

type ApplicationAttestationReceipt

type ApplicationAttestationReceipt struct {
	Provider    ApplicationAttestationProviderIdentity `json:"provider"`
	Expectation ApplicationAttestationExpectation      `json:"expectation"`
	Before      *ApplicationAttestationObservation     `json:"before,omitempty"`
	After       *ApplicationAttestationObservation     `json:"after,omitempty"`
	Failures    []string                               `json:"failures"`
}

type ApplicationHealth

type ApplicationHealth struct {
	State  string `json:"state"`
	Detail string `json:"detail,omitempty"`
}

type ApplicationInstanceIdentity

type ApplicationInstanceIdentity struct {
	Kind            string `json:"kind"`
	ID              string `json:"id"`
	StartGeneration string `json:"startGeneration"`
}

type ApplicationRepositoryExpectation

type ApplicationRepositoryExpectation struct {
	RootCommit  string `json:"rootCommit"`
	Revision    string `json:"revision"`
	Tree        string `json:"tree"`
	DirtyPolicy string `json:"dirtyPolicy"`
	DirtyDigest string `json:"dirtyDigest,omitempty"`
}

type ApplicationRepositoryIdentity

type ApplicationRepositoryIdentity struct {
	RootCommit  string `json:"rootCommit"`
	Revision    string `json:"revision"`
	Tree        string `json:"tree"`
	DirtyState  string `json:"dirtyState"`
	DirtyDigest string `json:"dirtyDigest,omitempty"`
}

type Attempt

type Attempt struct {
	State       ExecutionState `json:"state"`
	Retry       int            `json:"retry"`
	FailureKind string         `json:"failureKind"`
	Steps       []BrowserStep  `json:"steps,omitempty"`
}

type BrowserStep

type BrowserStep struct {
	Title       string            `json:"title"`
	Category    string            `json:"category,omitempty"`
	Metadata    map[string]string `json:"metadata,omitempty"`
	Error       string            `json:"error,omitempty"`
	Attachments []FailureArtifact `json:"attachments,omitempty"`
	Steps       []BrowserStep     `json:"steps,omitempty"`
	Redacted    bool              `json:"redacted,omitempty"`
}

BrowserStep is the bounded action trace retained by the redaction-capable external profile. Values entered by input actions never cross this boundary.

type Config

type Config struct {
	Dir             string
	TestFiles       []string
	ConfigFile      string
	PackageJSON     string
	Lockfile        string
	RunnerName      string
	RunnerVersion   string
	DeclaredEnvKeys []string
	Timeout         time.Duration
	OutputLimit     int
}

Config is the shared subset of binding inputs both adapters need: the files whose identity must be pinned before evidence emits (AGENTS.md invariant 1), the tool's own argv, and the env keys a caller has declared worth observing (never the full process environment).

type E2EConfig

type E2EConfig struct {
	Config
	ObserveDescendants     bool
	ExternalServer         bool
	AppIdentity            string
	ServerArgv             []string
	ServerReadyURL         string
	ServerReadyLimit       time.Duration
	AppBuildDir            string // "" => unknown app build identity.
	TestArgv               []string
	ApplicationAttestation *ApplicationAttestationProvider
	SensitiveInputPolicy   *SensitiveInputPolicy
}

E2EConfig configures one Playwright E2E run. Corvint starts and owns the app server itself under procgroup rather than relying on Playwright's `webServer` teardown, per the memo's named lifecycle risk ( evidence/ipr-08-runner-selection.md section 6, item 1) and the roadmap acceptance line requiring proven descendant cleanup.

type ExecutionSchedule

type ExecutionSchedule struct {
	Workers int              `json:"workers"`
	Starts  []ExecutionStart `json:"starts"`
}

ExecutionSchedule records reporter-observed starts, not the requested order.

type ExecutionStart

type ExecutionStart struct {
	FullName      string `json:"fullName"`
	File          string `json:"file"`
	Line          int    `json:"line"`
	Project       string `json:"project"`
	Retry         int    `json:"retry"`
	Retries       int    `json:"retries"`
	Worker        int    `json:"worker"`
	FullyParallel bool   `json:"fullyParallel"`
}

type ExecutionState

type ExecutionState string

ExecutionState is the per-test outcome vocabulary this provider maps every reporter's own status field onto. It intentionally does not reuse testvalidity.Execution* verbatim: those are the shared LPCV/GLTP axis states, while this is the finer-grained JS/TS runner vocabulary the roadmap acceptance criteria name explicitly (flaky/retried, timedOut, interrupted, infrastructure) that a caller then folds into the shared axis via ToExecutionFacts.

const (
	StatePassed         ExecutionState = "passed"
	StateFailed         ExecutionState = "failed"
	StateSkipped        ExecutionState = "skipped"
	StateFlaky          ExecutionState = "flaky"
	StateTimedOut       ExecutionState = "timedOut"
	StateInterrupted    ExecutionState = "interrupted"
	StateInfrastructure ExecutionState = "infrastructure"
)

type ExternalLifecycle

type ExternalLifecycle struct {
	ReadyURL              string `json:"readyUrl"`
	DeclaredAppIdentity   string `json:"declaredAppIdentity"`
	Ownership             string `json:"ownership"`
	CleanupResponsibility string `json:"cleanupResponsibility"`
	ServerDescendants     string `json:"serverDescendants"`
	ReadyAtStart          bool   `json:"readyAtStart"`
	ReadyAtPublish        bool   `json:"readyAtPublish"`
	RunnerDescendantsGone bool   `json:"runnerDescendantsGone"`
	InputsUnchanged       bool   `json:"inputsUnchanged"`
	ConfigOverride        string `json:"configOverride"`
}

type FailureArtifact

type FailureArtifact struct {
	Name string `json:"name"`
	Path string `json:"path"`
}

FailureArtifact is a bounded pointer to a failure artifact (trace, screenshot, error-context) - the path the reporter recorded, never the artifact's own content, which stays wherever the runner wrote it.

type Identity

type Identity struct {
	ConfigInputDigests map[string]string `json:"configInputDigests,omitempty"`
	TestFileDigests    map[string]string `json:"testFileDigests"`
	ConfigFile         string            `json:"configFile"`
	ConfigDigest       string            `json:"configDigest"`
	PackageDigest      string            `json:"packageDigest"`
	NodeVersion        string            `json:"nodeVersion"`
	RunnerName         string            `json:"runnerName"`
	RunnerVersion      string            `json:"runnerVersion"`
	// Environment holds only the declared keys a caller asked to bind, never
	// the full process environment (AGENTS.md invariant 4 spirit: bounded,
	// explicit inputs, not incidental host state).
	Environment map[string]string `json:"environment"`
	Argv        []string          `json:"argv"`
}

Identity is every binding fact a receipt commits to before evidence emits, per AGENTS.md invariant 1 and the roadmap acceptance line "bind test/configuration/application build and environment identities."

type InfrastructureFailure

type InfrastructureFailure struct {
	Reason string `json:"reason"`
	Detail string `json:"detail"`
}

InfrastructureFailure records a run-level failure that never produced a per-test result: a bad command/config, a missing browser, a run the reporter itself reports as having found no tests. It is distinct from a per-test "failed" outcome (a real assertion or workflow failure) and from StateInfrastructure applied to an individual outcome whose message pattern still matches a known infrastructure cause (e.g. a missing browser executable surfacing inside an otherwise well-formed per-test result).

type ProjectIdentity

type ProjectIdentity struct {
	Name         string          `json:"name"`
	Browser      string          `json:"browser"`
	Device       string          `json:"device"`
	Use          json.RawMessage `json:"use"`
	ConfigDigest string          `json:"configDigest"`
}

ProjectIdentity binds the resolved runtime configuration, not a device label inferred from the project name. Empty device labels remain explicit unknowns.

type Receipt

type Receipt struct {
	DescendantObservation   *procgroup.DescendantObservation `json:"descendantObservation,omitempty"`
	RunnerResources         *procgroup.ResourceUsage         `json:"runnerResources,omitempty"`
	Schedule                *ExecutionSchedule               `json:"schedule,omitempty"`
	Profile                 string                           `json:"profile,omitempty"`
	SensitiveInputPolicy    *SensitiveInputPolicy            `json:"sensitiveInputPolicy,omitempty"`
	External                *ExternalLifecycle               `json:"external,omitempty"`
	ApplicationAttestation  *ApplicationAttestationReceipt   `json:"applicationAttestation,omitempty"`
	TestRepositoryAtStart   *ApplicationRepositoryIdentity   `json:"testRepositoryAtStart,omitempty"`
	TestRepositoryAtPublish *ApplicationRepositoryIdentity   `json:"testRepositoryAtPublish,omitempty"`
	Kind                    string                           `json:"kind"` // "unit" | "e2e"
	Identity                Identity                         `json:"identity"`
	AppBuildAtStart         AppBuildIdentity                 `json:"appBuildAtStart"`
	AppBuildAtPublish       AppBuildIdentity                 `json:"appBuildAtPublish"`
	StaleAppBuild           bool                             `json:"staleAppBuild"`
	Tests                   []TestOutcome                    `json:"tests"`
	Infrastructure          *InfrastructureFailure           `json:"infrastructure,omitempty"`
	Cancelled               bool                             `json:"cancelled"`
	ServerDescendantsGone   *bool                            `json:"serverDescendantsGone,omitempty"`
}

Receipt is the one JS/TS provider output shape: identity, app-build freshness, per-test outcomes, and an optional run-level infrastructure failure. It carries no boolean "valid" summary; ToInput below projects it through the shared testvalidity axes instead.

func RedactSensitiveInputEvidence

func RedactSensitiveInputEvidence(receipt Receipt) (Receipt, error)

func RunE2E

func RunE2E(ctx context.Context, cfg E2EConfig) (Receipt, error)

RunE2E starts the app server, waits for it to answer ServerReadyURL, runs the Playwright test command, then cancels the server's context and confirms procgroup's owned-process-group cleanup fired before returning. The app build identity is captured once before the readiness check passes and again after the test command completes; a mismatch is reported as StaleAppBuild rather than silently trusted.

func RunUnit

func RunUnit(ctx context.Context, cfg UnitConfig) (Receipt, error)

RunUnit runs `vitest run --reporter=json --outputFile=<path>` under procgroup containment and returns the bound receipt. A nonzero vitest exit status is expected on real test failures and is not itself an infrastructure error when the report already names the failure (a failed assertion, or the file-level fallback in ParseVitestJSON). But an error raised outside any test's own execution window (e.g. thrown from a timer callback after its test already passed) never touches Vitest's own numFailedTests/numFailedSuites counters, so it leaves every reported outcome "passed" while the process still exits nonzero - the report carries no signal at all. unexplainedNonzeroExit below catches exactly that gap so such a run abstains instead of publishing an all-green receipt (AGENTS.md invariant 2); see docs/decisions/0182 and js-live-test-provider-v0.md §2.1.

type SensitiveInputFinding

type SensitiveInputFinding struct {
	Code string `json:"code"`
	Path string `json:"path"`
}

SensitiveInputFinding is a typed, value-free validation result. Path names the structural location only; it never includes the rejected value.

func ValidateSensitiveInputEvidence

func ValidateSensitiveInputEvidence(receipt Receipt) []SensitiveInputFinding

type SensitiveInputPolicy

type SensitiveInputPolicy struct {
	AdditionalActionPatterns  []string `json:"additionalActionPatterns,omitempty"`
	AdditionalSensitiveFields []string `json:"additionalSensitiveFields,omitempty"`
}

SensitiveInputPolicy carries provider additions. Defaults are always applied and cannot be disabled or replaced by these declarations.

type SensitiveInputValidationError

type SensitiveInputValidationError struct {
	Findings []SensitiveInputFinding
}

func (*SensitiveInputValidationError) Error

type TestOutcome

type TestOutcome struct {
	ID             string            `json:"id,omitempty"`
	Project        *ProjectIdentity  `json:"project,omitempty"`
	Attempts       []Attempt         `json:"attempts,omitempty"`
	Name           string            `json:"name"`
	FullName       string            `json:"fullName"`
	State          ExecutionState    `json:"state"`
	Retries        int               `json:"retries"`
	DurationMS     float64           `json:"durationMs"`
	Anchor         *Anchor           `json:"anchor,omitempty"`
	FailureMessage string            `json:"failureMessage,omitempty"`
	Artifacts      []FailureArtifact `json:"artifacts,omitempty"`
}

TestOutcome is one reporter test/assertion result, normalized to this package's ExecutionState vocabulary.

type UnitConfig

type UnitConfig struct {
	Config
	OutputFile string // where --outputFile writes, removed before the run; a temp file is used if empty.
}

UnitConfig configures one Vitest unit run.

Jump to

Keyboard shortcuts

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