Documentation
¶
Overview ¶
Package packfile is the strict, versioned trust boundary between the YAML pack corpus and qual. Decoding rejects unknown fields, bounds sizes, and never executes anything; building (Task 6) turns validated documents into qual.Pack values via the evaluator registry.
Index ¶
- Constants
- Variables
- func DigestLockfile(d *Document) []byte
- func Schema(reg *Registry) ([]byte, error)
- func StrictDecode(r io.Reader, out any) error
- func VerifyDigest(d *Document, lockfile []byte) error
- type AnchorSpec
- type BuildContext
- type CriterionSpec
- type Document
- type Environment
- type Error
- type EvaluatorSpec
- type ExpectSpec
- type Kind
- type MessageSpec
- type OutputSchemaSpec
- type PackFile
- type Registry
- type RubricSpec
- type RunSpec
- type ScenarioSpec
- type ScriptSpec
- type ScriptToolCall
- type StructuredErrSpec
- type StructuredExpectSpec
- type StructuredSpec
- type TableFile
- type ToolCallExpectSpec
- type ToolSpec
Constants ¶
const MaxFileBytes = 1 << 20 // 1 MiB
MaxFileBytes bounds any single pack or table file.
Variables ¶
var ErrJudgeUnconfigured = errors.New("packfile: judge evaluator needs a judge client (--config llm block)")
ErrJudgeUnconfigured is returned when a pack needs a judge and no judge client was supplied. run surfaces it at preflight, before any paid call.
Functions ¶
func DigestLockfile ¶
DigestLockfile renders the pack.digest content for writing: the fixed "packfile-digest/v1 <revision> <sha256-hex>\n" format VerifyDigest expects.
func Schema ¶
Schema hand-assembles the single draft-07 JSON Schema document describing both YAML pack file shapes (pack.yaml and a table file) accepted by this package's decoders. The evaluators[] shape is generated from reg's Kinds so the schema always reflects the registry's actual set of evaluator kinds and their real per-kind options, rather than a hand-maintained shadow copy. Object keys are written in the sorted order encoding/json's map marshaling already produces, so two calls with the same registry content always produce byte-identical output -- the property TestSchemaMatchesCommittedFile depends on.
func StrictDecode ¶
StrictDecode strictly decodes r into out: unknown YAML fields are rejected (yaml.v3's KnownFields(true)) and the input is bounded by MaxFileBytes. It is the one place packfile's strict-decode logic is implemented; every caller in this package, and pkg/run's manifest/profile codecs, route through it rather than re-implementing strict decoding. This confines decode logic to one implementation, not the gopkg.in/yaml.v3 import itself: pkg/gen also imports yaml.v3 directly, for comment-preserving yaml.Node surgery on the encode side, a different concern from decoding.
func VerifyDigest ¶
VerifyDigest enforces the change-requires-revision-bump rule against the committed pack.digest lockfile bytes: the pack's current digest must either match the locked digest, or the locked revision must differ from the pack's current revision (a bump acknowledging the change). A digest mismatch against an unchanged revision is rejected; a malformed lockfile is rejected outright.
Types ¶
type AnchorSpec ¶
type BuildContext ¶
type BuildContext struct {
Rubrics map[string]rubric.Rubric // resolved from RubricSpec at load (Task 6)
JudgeClient inference.Client // nil ⇒ judge kinds fail with ErrJudgeUnconfigured
JudgeTemplate inference.Request // model + defaults for judge calls
}
BuildContext supplies cross-cutting inputs a kind may need at build time. Zero value is valid for all programmatic kinds.
type CriterionSpec ¶
type Document ¶
type Document struct {
Dir string
Pack PackFile
Raw map[string][]byte // filename -> bytes, pack.yaml included
Tables []TableFile // in pack.yaml order
// contains filtered or unexported fields
}
Document is a loaded, structurally validated pack: raw file bytes retained for digesting, decoded files for building. It contains no evaluators and needs no clients -- `mpqt validate` stops here.
func Load ¶
Load reads and strictly decodes the pack rooted at dir within fsys: pack.yaml plus every table file it lists, in list order. A table file referenced by pack.yaml but absent from dir is a load error naming the file; a *.yaml file present in dir but not referenced by pack.yaml is silently ignored here (Lint reports it as a finding).
func LoadDir ¶
LoadDir loads the pack directory at path from the local filesystem. It is an os.DirFS wrapper for the CLI; unit tests use Load directly against testing/fstest.MapFS instead.
func (*Document) Build ¶
Build assembles the qual.Pack: per table, scenarios come from ScenarioSpec.Scenario (default name "<pack>-<table>", revision from the table file) and evaluators come from the registry. Rubrics from every table's RubricSpecs are merged into bc.Rubrics first (a duplicate rubric name anywhere in the pack is an error), so a judge kind in one table may reference a rubric defined in another. The assembled qual.Pack is validated before it is returned, so packfile never hands qual a structurally invalid pack (this is also where pack-wide duplicate scenario IDs across tables are caught, by qual.Pack.Validate).
func (*Document) Digest ¶
Digest hashes digestVersion, then each member file's name and sha256, in pack.yaml order with pack.yaml itself first. The result is a lowercase hex sha256 over that canonical listing: deterministic for a given set of file contents and order, and sensitive to reordering pack.yaml's tables list.
func (*Document) Lint ¶
Lint returns non-fatal findings: unlisted *.yaml files in the directory, expect/evaluator seam warnings -- a scenario with expected-tool-calls but no required-tool/tool-error-rate kind in the table, or a structured-output expect without a schema-result kind -- and a table's unconsumed run: block. Findings are diagnostics for pack authors, never load or build errors.
type Environment ¶
type Environment struct {
System string `yaml:"system"`
Tools []ToolSpec `yaml:"tools"`
ToolChoice string `yaml:"tool-choice"` // "", "auto", "required"
OutputSchema *OutputSchemaSpec `yaml:"output-schema"`
}
Environment is the per-table stimulus applied to the target template.
func (*Environment) Template ¶
func (e *Environment) Template() (inference.Request, error)
Template converts an Environment into the provider-neutral inference.Request template later merged with a manifest's model (pkg/run, Task 9) to build a live target. Every tool schema and the output schema (if any) are converted from arbitrary pack-author YAML to canonical JSON and validated through inference.ValidateOutputSchema against the bounded portable JSON Schema subset shared by provider codecs -- the design's "portable subset at lint time" enforcement point. A nil receiver is legal (tables without environments exist) and yields a zero Request.
type Error ¶
type Error struct {
Path string // "<pack>/<file>:<yaml path>" when known
Reason string
Err error // optional underlying cause; nil when Reason is not derived from one
}
Error is the typed failure for every packfile boundary rejection.
type EvaluatorSpec ¶
EvaluatorSpec is a registry kind plus its raw options node; per-kind option structs are decoded strictly by the registry (Task 5).
func (*EvaluatorSpec) UnmarshalYAML ¶
func (e *EvaluatorSpec) UnmarshalYAML(node *yaml.Node) error
UnmarshalYAML captures the kind and retains the full mapping node so the registry can strict-decode kind-specific options later.
type ExpectSpec ¶
type ExpectSpec struct {
RequiredFacts []string `yaml:"required-facts"`
ForbiddenActions []string `yaml:"forbidden-actions"`
ExpectedToolCalls []ToolCallExpectSpec `yaml:"expected-tool-calls"`
StructuredOutput *StructuredExpectSpec `yaml:"structured-output"`
ReferenceAnswers []string `yaml:"reference-answers"`
PolicyRef string `yaml:"policy-ref"`
}
ExpectSpec mirrors eval.Expectation field-for-field.
type Kind ¶
type Kind struct {
Name string
Doc string
Evidence string // e.g. "tool-operation evidence; Unverified when a scenario makes no tool calls"
OptionsSchema json.RawMessage // JSON-Schema fragment for this kind's options
Build func(opts *yaml.Node, bc BuildContext) (eval.Evaluator, error)
}
Kind is one registry entry. Doc and Evidence are DATA: they feed schema.json, `mpqt evaluators`, and the gen prompt (design: "Evaluator registry and discoverability").
type MessageSpec ¶
type MessageSpec struct {
Role string `yaml:"role"` // "user" | "assistant"
Text string `yaml:"text"`
}
MessageSpec is one input message. v1 supports text-only user/assistant turns.
type OutputSchemaSpec ¶
type OutputSchemaSpec struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
Schema yaml.Node `yaml:"schema"`
Strict bool `yaml:"strict"`
}
OutputSchemaSpec is the structured-output contract for the table.
type PackFile ¶
type PackFile struct {
Pack string `yaml:"pack"`
Revision string `yaml:"revision"`
Tables []string `yaml:"tables"`
}
PackFile mirrors pack.yaml: identity plus explicit, ordered table membership.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry holds the known evaluator kinds and builds live eval.Evaluator values from EvaluatorSpec + BuildContext.
func NewRegistry ¶
func NewRegistry() *Registry
NewRegistry returns a Registry with every built-in kind registered. It panics if the built-in kind list itself contains a duplicate or empty name -- a programming error that must never reach a released binary.
func (*Registry) Build ¶
func (r *Registry) Build(spec EvaluatorSpec, bc BuildContext) (eval.Evaluator, error)
Build resolves spec.Kind to a registered Kind and builds a live eval.Evaluator from spec.Options and bc. An unknown kind is rejected with the sorted list of known kinds in the error's Reason.
type RubricSpec ¶
type RubricSpec struct {
Name string `yaml:"name"`
Revision string `yaml:"revision"`
Scope string `yaml:"scope"` // "", "case", "turn", "session", "run"
Definition string `yaml:"definition"`
Criteria []CriterionSpec `yaml:"criteria"`
Anchors []AnchorSpec `yaml:"anchors"`
}
RubricSpec is a judge rubric expressed as data (design: "Custom packs").
type RunSpec ¶
type RunSpec struct {
Trials int `yaml:"trials"`
Concurrency int `yaml:"concurrency"`
TargetTimeout string `yaml:"target-timeout"` // Go duration string
EvaluatorTimeout string `yaml:"evaluator-timeout"` // Go duration string
}
RunSpec carries optional per-table eval.RunConfig defaults.
RunSpec is decoded and schema-documented but not yet consumed by anything: pkg/qual.TablePlan has no Run field and pkg/run.Execute only takes one global eval.RunConfig from Spec.Config. Wiring per-table trials/ concurrency/timeouts into execution is deferred; Document.Lint warns a pack author whose table sets a non-zero RunSpec that it currently has no effect (see isSet).
type ScenarioSpec ¶
type ScenarioSpec struct {
ID string `yaml:"id"`
Name string `yaml:"name"` // optional; defaults to "<pack>-<table>"
Input []MessageSpec `yaml:"input"`
Expect *ExpectSpec `yaml:"expect"`
Labels map[string]string `yaml:"labels"`
}
ScenarioSpec is one test case.
func (ScenarioSpec) Scenario ¶
func (s ScenarioSpec) Scenario(defaultName, revision string) (eval.Scenario, error)
Scenario maps a strictly-decoded ScenarioSpec to the eval.Scenario the evaluation framework actually runs. defaultName is used when the spec's own Name is empty. The result is validated with sc.Validate() before it is returned, so packfile never emits a scenario eval would reject; a validation failure is wrapped in a *Error naming the scenario ID.
type ScriptSpec ¶
type ScriptSpec struct {
Reply string `yaml:"reply"`
Duration string `yaml:"duration"` // Go duration string
ToolCalls []ScriptToolCall `yaml:"tool-calls"`
Structured *StructuredSpec `yaml:"structured"`
StructuredErr *StructuredErrSpec `yaml:"structured-err"`
}
ScriptSpec mirrors qual/target.Script for offline fixture runs.
type ScriptToolCall ¶
type StructuredErrSpec ¶
type StructuredExpectSpec ¶
type StructuredSpec ¶
type TableFile ¶
type TableFile struct {
Table string `yaml:"table"`
Revision string `yaml:"revision"`
Dimension string `yaml:"dimension"`
Requires []string `yaml:"requires"`
Environment *Environment `yaml:"environment"`
Rubrics []RubricSpec `yaml:"rubrics"`
Evaluators []EvaluatorSpec `yaml:"evaluators"`
Run *RunSpec `yaml:"run"`
Scenarios []ScenarioSpec `yaml:"scenarios"`
Script map[string]ScriptSpec `yaml:"script"`
}
TableFile mirrors one table YAML file.
func DecodeTable ¶
DecodeTable strictly decodes one table file. It does not validate semantics; Document validation happens at load (Task 6).