scenario

package
v0.6.1 Latest Latest
Warning

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

Go to latest
Published: Jul 27, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

Documentation

Overview

Package scenario is the parser, validator and run.Run mapper for Chatwright's self-contained scenario document format (https://chatwright.dev/formats/scenario-document/v1) — the format spec/features/chatwright/scenario-authoring/portable-scenario-documents/ self-contained-scenario-documents/README.md in the standard repository (chatwright/chatwright) defines. A document declares a bot endpoint, an AI goal with its tasks and budgets, the cast, the declared fidelity and an independent journal verification — everything arena.GreetbotScenario() carries compiled into Go, expressed instead as one committed, language-neutral JSON file that both chatwright runtimes execute to the same verdict with no Go or TypeScript written.

Three stages, kept deliberately separate:

  1. Parse — pure, in-memory, no I/O of any kind. It never starts a bot, reads an environment variable, resolves a credential, opens a file beyond the document bytes it was given, or makes a network call (the spec's "parsing is inert, and refusal is explicit"). Parse returns a Report naming every structural problem by JSON pointer and rule id, and rejects (returns a non-nil error) the moment any Report issue is an error-severity one — never echoing the offending value.
  2. Resolve — a ScenarioProvider turns a reference (a file path today; a store id or a Cloud reference tomorrow) into document bytes, then Parse validates them. The runner (Build, run.Run) never learns where a Document came from.
  3. Build — the separate, explicit step that actually starts anything: it resolves declared secrets (env/credential lookup), boots an example bot or wires an HTTP bot transport, and produces a ready-to-execute run.Run plus the roster and doc-local chat-id mapping a caller needs to assemble a run bundle afterwards. A document that failed validation never reaches Build.

Index

Constants

View Source
const (
	EndpointProfilePlatformEmulated = "platform-emulated"
	EndpointProfileHeadlessEngine   = "headless-engine"
)

Endpoint profiles — see Fidelity.EndpointProfile.

View Source
const (
	EnvironmentDev        = "dev"
	EnvironmentTest       = "test"
	EnvironmentProduction = "production"
	EnvironmentUnknown    = "unknown"
)

Environments — see Fidelity.Environment.

View Source
const (
	DataSensitivitySynthetic   = "synthetic"
	DataSensitivityRealSubject = "real-subject"
)

Data sensitivities — see Fidelity.DataSensitivity.

View Source
const (
	TransportHTTP   = "http"
	TransportIframe = "iframe"
)

Bot transports — see Bot.Transport.

View Source
const (
	DeliveryWebhook = "webhook"
	DeliveryPolling = "polling"
)

Bot deliveries — see Bot.Delivery.

View Source
const (
	CastTypeAIAgent = "ai-agent"
	CastTypeHuman   = "human"
)

Cast types — see Cast.Type. Reused verbatim from the sdk wire vocabulary.

View Source
const (
	ProviderKindModel    = "model"
	ProviderKindCassette = "cassette"
)

Provider kinds — see Provider.Kind.

View Source
const (
	CassetteModeReplay = "replay"
	CassetteModeRecord = "record"
)

Cassette provider modes — see Provider.Mode.

View Source
const (
	PartKindAIGoal        = "ai-goal"
	PartKindDeterministic = "deterministic"
)

Part kinds — see Part.Kind.

View Source
const (
	FailurePolicyAbort       = "abort"
	FailurePolicyCoverageGap = "coverage-gap"
)

Failure policies — see Part.FailurePolicy. Reused verbatim from run.FailurePolicy's own wire vocabulary.

View Source
const (
	FieldKind      = "kind"
	FieldDirection = "direction"
	FieldText      = "text"
	FieldEdited    = "edited"
)

Condition fields — see Condition.Field.

View Source
const (
	OpExact    = "exact"
	OpContains = "contains"
	OpRegex    = "regex"
)

Condition operators — see Condition.Op.

View Source
const FormatV1 = "https://chatwright.dev/formats/scenario-document/v1"

FormatV1 is the scenario-document format identifier this package parses. Parse rejects any other value for Document.Format — see the format's own "unsupported format ... is named" validation rule.

View Source
const SchemaVersion1 = 1

SchemaVersion1 is the only schemaVersion this package accepts. A document declaring any other value is rejected naming it explicitly, never silently downgraded or upgraded.

View Source
const UnmetPrefix = "journal evidence incomplete: "

UnmetPrefix is the fixed runtime prefix an unmet VerifyResult's Detail always starts with — the README's "the verdict maps to VerifyResult ... the fixed runtime prefix journal evidence incomplete: " rule. It is pinned as an exported constant, not reconstructed at each call site, so this package and its tests can never drift from arena's own verifyGreetbotJournal, which produces the identical string for the identical reason.

Variables

View Source
var DefaultSupportedExampleBots = map[string]bool{
	"greetbot": true,
}

DefaultSupportedExampleBots is the exampleBot ids this runtime (runtime-go) ships and Validate accepts a `requires: ["exampleBot:<id>"]` declaration for — see ExampleBotRegistry, which must register exactly this set (an id present here with no matching registry entry, or vice versa, is a packaging bug, not a document problem).

View Source
var ErrCredentialStoreUnavailable = fmt.Errorf("scenario: no credential store is configured in this runtime")

ErrCredentialStoreUnavailable is returned by EnvOnlyResolver.ResolveCredential for every credential name: this runtime has no credential store yet.

Functions

func Parse

func Parse(data []byte) (*Document, Report, error)

Parse validates data as a scenario-document/v1 document. It is pure: no file is opened beyond decoding data itself, no environment variable is read, no credential store is consulted, no network call is made, and no bot — example or otherwise — is started. See the package doc comment's "Parse" stage.

The returned Report carries every Issue found, error and warning alike. The returned error is non-nil exactly when the Report has at least one SeverityError Issue, in which case the returned *Document is nil: no part of a rejected document is resolved, and the error never echoes an offending value (see Issue's own doc comment). A non-nil Document may still carry Report warnings (e.g. noRunCeiling) worth surfacing to a caller even though the document is perfectly valid.

Types

type Bot

type Bot struct {
	ID   string `json:"id"`
	Name string `json:"name"`

	// Transport is "http" or "iframe" — required with URL, forbidden with
	// ExampleBot (the runtime wires its own example however it likes, and
	// declares no transport in the document).
	Transport string `json:"transport,omitempty"`
	// Delivery is "webhook" or "polling" — http transport only.
	Delivery string `json:"delivery,omitempty"`
	// URL is the bot's webhook URL (http+webhook) or iframe src. Required
	// for iframe and for http+webhook; forbidden for http+polling.
	URL string `json:"url,omitempty"`
	// Headers optionally carries request headers for webhook delivery; each
	// value MUST be a secret reference — see SecretRef.
	Headers map[string]SecretRef `json:"headers,omitempty"`
	// ExampleBot names a bot the runtime itself ships (e.g. "greetbot").
	// Not an extension point — see ExampleBotRegistry.
	ExampleBot string `json:"exampleBot,omitempty"`
}

Bot declares how the runtime reaches the bot under test and, at the same time, its roster identity — see the README's "The bot endpoint" section. Exactly one of URL or ExampleBot is set (see Validate).

type Budgets

type Budgets struct {
	MaxSteps           int      `json:"maxSteps,omitempty"`
	MaxDurationSeconds int      `json:"maxDurationSeconds,omitempty"`
	MaxCost            *float64 `json:"maxCost,omitempty"`
}

Budgets is goal.Budgets, authored with integer seconds instead of a nanosecond duration — see the README's "Durations are integer seconds" rule. At least one of MaxSteps, MaxDurationSeconds or MaxCost must be a positive value (see Validate's ai-goal-part-without-budgets rule); goal.Budgets' own "zero means unlimited" convention is deliberately not inherited here.

type BuildOptions

type BuildOptions struct {
	// Now supplies the built run's clock — see run.Environment.Now. Nil
	// uses time.Now.
	Now func() time.Time
	// Secrets resolves a declared secret's value — see SecretResolver. Nil
	// uses EnvOnlyResolver{}; a document declaring no secrets never
	// consults it either way.
	Secrets SecretResolver
	// ExampleBots is the registry Build boots a Bot.ExampleBot from. Nil
	// uses DefaultExampleBots().
	ExampleBots ExampleBotRegistry
	// BindAddr, when set, binds the emulator to this exact address
	// (telegram.NewEmulatorAt) instead of a random local port. Needed for
	// "http"+"polling" delivery: an already-running external bot must be
	// pre-configured with the emulator's address before Build even runs —
	// see the README's "the operator points the bot's Bot API base URL at
	// the emulator". Ignored for an exampleBot document (the README: "there
	// is no authorable URL").
	BindAddr string
	// HTTPClient is the client the emulator uses to push webhook updates,
	// for "http"+"webhook" delivery. Nil uses http.DefaultClient. Ignored
	// otherwise.
	HTTPClient *http.Client
}

BuildOptions configures Build — every seam it needs beyond doc itself. The zero value is a usable default for an exampleBot document that declares no secrets (exactly the worked-example greetbot document).

type Built

type Built struct {
	Run        run.Run
	Actors     []sdk.Actor
	ChatIDs    map[string]int64 // doc-local chat id -> platform chat id
	VerifySpec *VerifySpec
	Fidelity   ResolvedFidelity
	Close      func()
}

Built is everything Build produced from a validated Document: a ready-to-execute run.Run, the roster a caller needs to assemble a run bundle afterwards, the doc-local-to-platform chat-id mapping, this document's compiled VerifySpec (nil when it declares none), its resolved fidelity, and a Close tearing down whatever Build itself started (an example bot's own server; never anything an operator started out of band, e.g. a real bot process polling for "http"+"polling" delivery).

func Build

func Build(_ context.Context, doc *Document, opts BuildOptions) (built *Built, err error)

Build is the package's separate, explicit resolution step (see the package doc comment): it resolves every declared secret doc's bot endpoint and cast providers need, boots an example bot or wires an HTTP bot transport, loads any referenced cassette file, and assembles a run.Run ready for Run.Execute. doc must already have passed Parse's validation — Build does not re-validate structure, only resolves it.

Build is where every side effect this format's parsing stage explicitly forbids is, deliberately, allowed to happen: reading an env var or consulting a credential store (via opts.Secrets), starting an example bot, reading a cassette file from disk. A caller that only wants to validate a document, never run it, should call Parse (or a ScenarioProvider's Load) and stop there.

type CaseDecl

type CaseDecl struct {
	Name   string                     `json:"name"`
	Inputs map[string]json.RawMessage `json:"inputs,omitempty"`
}

CaseDecl is a document-declared named input binding — see Document.Cases' own doc comment on why this shape is provisional.

type Cast

type Cast struct {
	ID               string           `json:"id"`
	Type             string           `json:"type"`
	Name             string           `json:"name"`
	PlatformIdentity PlatformIdentity `json:"platformIdentity"`
	// Provider is set for an "ai-agent" cast member; nil for a "human" or
	// "scripted"/"replay" one (v1's document format has no way to author a
	// scripted actor — see the README's "scripted is deliberately not
	// expressible").
	Provider *Provider `json:"provider,omitempty"`
}

Cast is one non-bot participant — see the README's "The cast and its providers" section.

type Ceiling

type Ceiling struct {
	MaxSteps           int      `json:"maxSteps,omitempty"`
	MaxCost            *float64 `json:"maxCost,omitempty"`
	MaxDurationSeconds int      `json:"maxDurationSeconds,omitempty"`
}

Ceiling is run.RunCeiling, authored with integer seconds — see Budgets' own doc comment on why. Absence is reported by Validate as noRunCeiling, never silently accepted as "no limit intended".

type Chat

type Chat struct {
	ID             string `json:"id"`
	PlatformChatID int64  `json:"platformChatId"`
}

Chat is one declared chat — exactly one in v1 (see Validate). ID is document-local: a Part's Chat member, and Verify.Chat, reference it, so no part hard-codes a platform chat number.

type Condition

type Condition struct {
	Field  string          `json:"field"`
	Op     string          `json:"op"`
	Value  json.RawMessage `json:"value"`
	Negate bool            `json:"negate,omitempty"`
}

Condition is a {field, op, value} triple with an optional negate — the same shape the exploration-to-regression idea fixed for action matchers.

type Document

type Document struct {
	Format        string `json:"format"`
	SchemaVersion int    `json:"schemaVersion"`

	ID      string `json:"id"`
	Version string `json:"version"`
	Title   string `json:"title"`

	Description string   `json:"description,omitempty"`
	Requires    []string `json:"requires,omitempty"`

	Fidelity Fidelity `json:"fidelity"`
	Platform string   `json:"platform"`
	Chats    []Chat   `json:"chats"`
	Bot      Bot      `json:"bot"`
	Cast     []Cast   `json:"cast"`

	Secrets []Secret `json:"secrets,omitempty"`

	// Inputs and Cases are declared, but their shape beyond "a named input"
	// and "a named binding of inputs" is not pinned down by the format's
	// worked example or any acceptance criterion — see InputDecl and
	// CaseDecl's own doc comments. Neither is consulted by Build: a
	// document executed directly (no manifest) uses no case and no input
	// binding.
	Inputs []InputDecl `json:"inputs,omitempty"`
	Cases  []CaseDecl  `json:"cases,omitempty"`

	Parts []Part `json:"parts"`

	Ceiling *Ceiling `json:"ceiling,omitempty"`
	Verify  *Verify  `json:"verify,omitempty"`

	Verifies []string `json:"verifies,omitempty"`

	// SourceURL is this Document's canonical source identity — e.g.
	// "file:///abs/path/to/doc.json" for a FileScenarioProvider. Empty for
	// a Document built directly from Parse without going through a
	// ScenarioProvider (e.g. in a unit test).
	SourceURL string `json:"-"`
	// BaseDir is the directory document-relative references (a cassette
	// path today) resolve against — the same directory SourceURL names,
	// for a file-backed Document.
	BaseDir string `json:"-"`
}

Document is the parsed, validated shape of one scenario-document/v1 file — the Go embodiment of the format the standard repository's self-contained-scenario-documents README defines. Field order mirrors that README's "Shape" table.

SourceURL and BaseDir are not part of the wire shape (no json tag): they are provenance a ScenarioProvider stamps on the Document after Parse succeeds, so Build can resolve document-relative references (e.g. a cassette path) without Parse itself ever touching the filesystem.

type EnvOnlyResolver

type EnvOnlyResolver struct {
	// Lookup overrides how an env var is read — defaults to os.LookupEnv.
	// Tests substitute a fake here so a resolver test never actually reads
	// the process environment.
	Lookup func(varName string) (string, bool)
}

EnvOnlyResolver is the default SecretResolver: {"env": "VAR"} secrets resolve via os.Getenv (missing/empty is an error, never a silent empty string standing in for a credential), and {"credential": "name"} secrets always fail with ErrCredentialStoreUnavailable — no credential store exists yet in this runtime.

func (EnvOnlyResolver) ResolveCredential

func (EnvOnlyResolver) ResolveCredential(credentialName string) (string, error)

ResolveCredential implements SecretResolver.

func (EnvOnlyResolver) ResolveEnv

func (r EnvOnlyResolver) ResolveEnv(varName string) (string, error)

ResolveEnv implements SecretResolver.

type ExampleBotFactory

type ExampleBotFactory func() (*ExampleBotSession, error)

ExampleBotFactory boots one fresh exampleBot session. Every call gets fresh state — no two Build calls, and no Build call and a warm-up, ever observe another's conversation history — matching arena's own Scenario.Setup contract.

type ExampleBotRegistry

type ExampleBotRegistry map[string]ExampleBotFactory

ExampleBotRegistry maps an exampleBot id (Bot.ExampleBot) to the factory that boots it. Not an extension point (see Bot.ExampleBot's own doc comment in document.go): this registry's key set is exactly DefaultSupportedExampleBots, the set validate checks a document's `requires: ["exampleBot:<id>"]` declaration against.

func DefaultExampleBots

func DefaultExampleBots() ExampleBotRegistry

DefaultExampleBots returns the registry this runtime (runtime-go) ships: "greetbot", booting chatwright.dev/runtime/examples/greetbot over a fresh telegram.Emulator — exactly arena.GreetbotScenario()'s own setupGreetbot.

func (ExampleBotRegistry) Boot

Boot resolves id against r, returning a clear error naming id when unregistered. validate's requires check should already have refused an undeclared exampleBot id before Build ever runs — reaching an unknown id here means the running binary's registry disagrees with DefaultSupportedExampleBots, a packaging bug rather than a document problem.

type ExampleBotSession

type ExampleBotSession struct {
	Emulator *telegram.Emulator
	Close    func()
}

ExampleBotSession is what booting one exampleBot produced: the emulator it is wired to, and a Close tearing both down. Mirrors arena.ScenarioSession's Setup/Close shape.

type Fidelity

type Fidelity struct {
	EndpointProfile string `json:"endpointProfile"`
	Environment     string `json:"environment,omitempty"`
	DataSensitivity string `json:"dataSensitivity,omitempty"`
	RedactionPolicy string `json:"redactionPolicy,omitempty"`
}

Fidelity declares a document's endpoint profile, environment and data sensitivity — see the README's "Declared fidelity and environment" section. RedactionPolicy is this package's minimal, provisional answer to that section's open question ("where does a document's redaction policy live"): a non-empty policy name, required exactly when DataSensitivity is "real-subject" (see Validate) and otherwise unused. It is not a redaction policy body — the sensitive-data-redaction idea owns that; this field only records that a document declares one applies.

type FileScenarioProvider

type FileScenarioProvider struct {
	// Root is prepended to a relative ref. Empty resolves a relative ref
	// against the process's current working directory (os.ReadFile's own
	// behaviour).
	Root string
}

FileScenarioProvider is the seam's only implementation today: ref is a local filesystem path.

func (FileScenarioProvider) Load

Load implements ScenarioProvider.

type Goal

type Goal struct {
	ID          string   `json:"id"`
	Title       string   `json:"title"`
	Description string   `json:"description,omitempty"`
	Tasks       []Task   `json:"tasks"`
	Constraints []string `json:"constraints,omitempty"`
	Budgets     Budgets  `json:"budgets"`
}

Goal is goal.Goal, authored — see the README's "Parts, budgets and ceilings" section.

type InputDecl

type InputDecl struct {
	Name        string          `json:"name"`
	Description string          `json:"description,omitempty"`
	Default     json.RawMessage `json:"default,omitempty"`
}

InputDecl is a document-declared input — see Document.Inputs' own doc comment on why this shape is provisional.

type Issue

type Issue struct {
	// Code is a short, stable, machine-readable rule id, e.g.
	// "inline-secret", "unsupported-capability", "ai-goal-budgets-required".
	Code string `json:"code"`
	// Pointer is an RFC 6901 JSON pointer into the document (e.g.
	// "/cast/0/provider/apiKey"), or "" for a document-level issue with no
	// single location.
	Pointer  string   `json:"pointer"`
	Message  string   `json:"message"`
	Severity Severity `json:"severity"`
}

Issue is one machine-readable validation finding: a rule id, the JSON pointer into the document it applies to, a human-readable message that never echoes the offending value, and a Severity. See the format's own "a rejection names the JSON pointer and the rule, and never echoes the value" requirement — Message is written by this package's own code at each call site, never built by interpolating a document-supplied value, so that requirement holds by construction, not by scrubbing after the fact.

type JournalExpectation

type JournalExpectation struct {
	ID          string      `json:"id"`
	UnmetDetail string      `json:"unmetDetail"`
	All         []Condition `json:"all"`
}

JournalExpectation is one ordered journal expectation — see Verify.

type LoopConfig

type LoopConfig struct {
	HistoryWindow         int   `json:"historyWindow,omitempty"`
	NonProgressLimit      int   `json:"nonProgressLimit,omitempty"`
	ActWaitTimeoutSeconds int   `json:"actWaitTimeoutSeconds,omitempty"`
	RetainObservations    *bool `json:"retainObservations,omitempty"`
	OvershootProbe        *bool `json:"overshootProbe,omitempty"`
}

LoopConfig is actor.Config's authorable tunables — see the README's "loop" paragraph for each field's default, identical to actor.Config's own zero-value defaults. RetainObservations and OvershootProbe are pointers so "omitted" (nil, defaults to true) is distinguishable from an explicit false — actor.Config itself inverts both into DisableObservationRetention/DisableOvershootProbe (see toActorConfig in build.go).

type Part

type Part struct {
	ID            string `json:"id"`
	Kind          string `json:"kind"`
	Title         string `json:"title,omitempty"`
	Chat          string `json:"chat"`
	ActorID       string `json:"actorId"`
	FailurePolicy string `json:"failurePolicy,omitempty"`

	// Goal is set for kind:"ai-goal".
	Goal *Goal `json:"goal,omitempty"`
	// Loop optionally overrides the actor.Config tunables — see LoopConfig.
	Loop *LoopConfig `json:"loop,omitempty"`

	// Steps is reserved for kind:"deterministic" (action matchers — not
	// built; see the README's "Reserved for action matchers" section). Its
	// mere presence is never inspected beyond "this document declares a
	// deterministic part", which Validate always rejects in v1.
	Steps json.RawMessage `json:"steps,omitempty"`
}

Part is one ordered passage of the run — see the README's "Parts, budgets and ceilings" section. Maps onto run.Part/run.AIGoalPartInput member for member.

type PlatformIdentity

type PlatformIdentity struct {
	UserID    int64  `json:"userId"`
	FirstName string `json:"firstName,omitempty"`
}

PlatformIdentity is one cast member's platform-native identity, serving both the roster entry and the user the loop acts as.

type Provider

type Provider struct {
	Kind string `json:"kind"`

	// Model-kind fields.
	ProviderID string     `json:"providerId,omitempty"`
	Model      string     `json:"model,omitempty"`
	BaseURL    string     `json:"baseUrl,omitempty"`
	APIKey     *SecretRef `json:"apiKey,omitempty"`

	// Cassette-kind fields.
	Mode     string    `json:"mode,omitempty"`
	Cassette string    `json:"cassette,omitempty"`
	Wraps    *Provider `json:"wraps,omitempty"` // mode:"record" only, a model provider
}

Provider is an ai-agent cast member's provider declaration — a discriminated union on Kind, encoded as one flat JSON object because Go's encoding/json has no native sum-type support. Exactly the fields belonging to Kind are meaningful; see Validate for which combinations are legal.

type RejectionError

type RejectionError struct {
	Report Report
}

RejectionError is the error Parse returns for a Report with at least one SeverityError Issue: every error-severity Issue's pointer and code, joined — never the Report's Warnings, and never any document-supplied value (see Issue's own doc comment on why that holds by construction).

func (*RejectionError) Error

func (e *RejectionError) Error() string

Error renders every error-severity Issue as "<pointer>: <code>: <message>", one per line.

type Report

type Report struct {
	Issues []Issue `json:"issues"`
}

Report is every Issue Parse (and the validation it runs) found, in the order they were discovered.

func (Report) Errors

func (r Report) Errors() []Issue

Errors returns every SeverityError Issue in r, in order.

func (Report) HasErrors

func (r Report) HasErrors() bool

HasErrors reports whether r carries at least one SeverityError Issue — exactly the condition under which Parse rejects the whole document.

func (Report) Warnings

func (r Report) Warnings() []Issue

Warnings returns every SeverityWarning Issue in r, in order.

type ResolvedFidelity

type ResolvedFidelity struct {
	EndpointProfile string
	// Environment is doc.Fidelity.Environment when declared; otherwise the
	// configured host map (localHostEnvironments, the only one this
	// runtime ships) when the bot's own host is unambiguous; otherwise
	// EnvironmentUnknown — never guessed beyond that.
	Environment string
	// DataSensitivity is doc.Fidelity.DataSensitivity when declared;
	// otherwise DataSensitivityRealSubject when Environment is
	// EnvironmentProduction; otherwise DataSensitivitySynthetic.
	DataSensitivity string
}

ResolvedFidelity is doc.Fidelity with every default the README's "Declared fidelity and environment" section describes actually applied — what a caller (Build, a report) should show as "what this run's fidelity actually is", as opposed to Document.Fidelity, which shows only what the author explicitly wrote.

func ResolveFidelity

func ResolveFidelity(doc *Document) ResolvedFidelity

ResolveFidelity applies the README's declared-then-configured-then- heuristic resolution order for environment, and the environment-defaults- sensitivity rule, to doc — a pure function (net/url parsing only; no network access) kept separate from Validate because it needs doc.Bot.URL, which is meaningless to resolve for an exampleBot document (there is no host to inspect) and because "what was declared" and "what is effective" are different questions a caller may want to show both answers to.

type ScenarioProvider

type ScenarioProvider interface {
	// Load resolves ref, then parses and validates the result exactly as
	// Parse does — see Parse's own doc comment for what "resolves" does
	// and does not do (reading ref's bytes is the only I/O Load performs;
	// nothing about the document's own content — a secret, an example bot,
	// a cassette file — is touched). A Report with at least one
	// SeverityError Issue means Load returns a non-nil error and a nil
	// Document.
	Load(ctx context.Context, ref string) (*Document, Report, error)
}

ScenarioProvider resolves a reference — a file path today; a store id or a Cloud reference tomorrow — into a loaded, validated Document. It is the only seam through which a Document ever enters this package's runner side (Build): the runner never learns where a Document came from, so a future store-backed or Cloud-backed provider is a drop-in replacement for FileScenarioProvider with no change to Build, run.Run or the CLI.

type Secret

type Secret struct {
	Name string       `json:"name"`
	From SecretSource `json:"from"`
}

Secret declares a name and where the runner resolves it from — never a value. See SecretSource.

type SecretRef

type SecretRef struct {
	Name string `json:"secretRef"`
}

SecretRef is the only legal shape for a secret-bearing field: a JSON object carrying exactly one member, "secretRef", naming a Secret declared in Document.Secrets. Its UnmarshalJSON (see secrets.go) is deliberately strict — a JSON string, or an object with any sibling member, fails to decode — so a literal secret in one of these fields is a parse-time rejection, not something Validate discovers later.

type SecretResolver

type SecretResolver interface {
	ResolveEnv(varName string) (string, error)
	ResolveCredential(credentialName string) (string, error)
}

SecretResolver resolves one declared Secret's actual value at Build time — the separate, explicit step Parse never reaches (see the package doc comment). Two methods, not one, so a caller can support one source without pretending to support the other: ResolveCredential returning ErrCredentialStoreUnavailable is a normal, expected outcome for a runner with no configured credential store, distinct from "this credential name does not exist in a store that does exist".

type SecretSource

type SecretSource struct {
	Env        string `json:"env,omitempty"`
	Credential string `json:"credential,omitempty"`
}

SecretSource names exactly one of an environment variable or a named credential-store entry a Secret resolves from.

type Severity

type Severity string

Severity classifies one Issue — see Issue.

const (
	// SeverityError means the document is rejected: Parse returns a nil
	// Document and a non-nil error the moment any Issue in the Report is
	// SeverityError.
	SeverityError Severity = "error"
	// SeverityWarning means the document is accepted but the condition is
	// reported rather than silently accepted — e.g. noRunCeiling,
	// noIndependentVerification, a declared-but-unused secret.
	SeverityWarning Severity = "warning"
)

Severities. See Severity.

type Task

type Task struct {
	ID              string   `json:"id"`
	Title           string   `json:"title,omitempty"`
	DependsOn       []string `json:"dependsOn,omitempty"`
	SuccessCriteria string   `json:"successCriteria"`
	Milestones      []string `json:"milestones,omitempty"`
}

Task is goal.Task, authored.

type Verify

type Verify struct {
	Chat      string               `json:"chat"`
	MetDetail string               `json:"metDetail"`
	Journal   []JournalExpectation `json:"journal"`
}

Verify is the declarative form of arena.Scenario.Verify — see the README's "Independent verification of what happened" section, and verify.go for its compiled evaluation.

type VerifyResult

type VerifyResult struct {
	Verified bool
	Detail   string
}

VerifyResult is one Evaluate call's deterministic verdict — the wire equivalent of arena.VerifyResult, kept as this package's own type so scenario never imports arena (a lower-level package importing a higher-level one).

type VerifySpec

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

VerifySpec is a Document's Verify block, compiled once (regex patterns parsed, condition values decoded) so Evaluate can run against a journal with no further parsing.

func CompileVerify

func CompileVerify(doc *Document) (*VerifySpec, error)

CompileVerify compiles doc.Verify into a VerifySpec ready to Evaluate, or (nil, nil) when doc declares no Verify block at all — see the README's "A document with no verify block is not reported as verified" rule; the caller (Build) is what turns a nil VerifySpec into the judged-not- verified outcome, this function only reports absence.

CompileVerify assumes doc has already passed validate (see Parse): Condition.Field/Op vocabulary, the regex subset restriction and Value's shape are not re-checked here — a malformed Verify block reaching this function unvalidated is a caller bug, reported as a plain Go error rather than a Report Issue.

func (*VerifySpec) ChatDocID

func (s *VerifySpec) ChatDocID() string

ChatDocID is the doc-local chat id (Document.Chats[*].ID) this VerifySpec evaluates against — the caller resolves it to a platform chat id via the same Document.Chats mapping Build uses, and reads that chat's journal.

func (*VerifySpec) Evaluate

func (s *VerifySpec) Evaluate(entries []platform.JournalEntry) VerifyResult

Evaluate re-derives a Verify block's verdict from entries — one chat's complete platform.JournalEntry history — independent of any actor's own task-done claim (principle 3, "evidence over claims"). Expectations are matched in declared order: expectation N matches the earliest entry strictly after the entry expectation N-1 matched (or, if N-1 never matched, after whatever the last successfully matched expectation did) — see the README's "Expectations are ordered" rule. All expectations matched yields Verified:true with Detail set to the spec's own MetDetail; otherwise Verified:false with Detail set to UnmetPrefix followed by every unmatched expectation's UnmetDetail, in declared order, joined with "; ".

Jump to

Keyboard shortcuts

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