Documentation
¶
Overview ¶
Package verdict is the engine's structured outcome contract: every migrate invocation ends in exactly one verdict — executed natively, refused with a typed reason and, where one exists, a safer native idiom, or failed during execution with the executor's stable outcome code and a disclosure of what committed before the failure. Refusals use a distinct exit code from operational errors. This type is the seam a future orchestrator adapter maps onto SchemaBot's ExecutionModeBlocked.
Index ¶
- Constants
- Variables
- func AcceptedBlockingDecision(r Refusal) (eligible, decided bool)
- func AcceptedBlockingEligible(r Refusal) bool
- type Cause
- type Class
- type Outcome
- type Owner
- type Reason
- type Refusal
- func ByDesign(reason Reason) Refusal
- func CapabilityBoundary(reason Reason) Refusal
- func Environmental(reason Reason) Refusal
- func InvariantViolation(reason Reason) Refusal
- func NewRefusal(class Class, reason Reason, owner Owner) (Refusal, error)
- func NoOnlineSafetyProblem(reason Reason, owner Owner) Refusal
- type RefusalSite
- type Verdict
Constants ¶
const ( // ExitCodeRefused is the process exit code for refusal. Exit 0 remains // exclusive to online-safe success and exit 1 to operational failure. ExitCodeRefused = 2 // ExitCodeAcceptedBlocking marks committed execution without an online- // safety guarantee; it cannot be confused with online-safe exit 0. ExitCodeAcceptedBlocking = 3 )
Variables ¶
var ErrAcceptedBlocking = errors.New("executed without online safety")
ErrAcceptedBlocking is returned after an accepted-blocking verdict is printed so the entry point can map it to ExitCodeAcceptedBlocking.
var ErrInvalidAcceptedBlocking = errors.New("invalid accepted-blocking verdict")
ErrInvalidAcceptedBlocking reports an incomplete accepted-blocking verdict.
var ErrRefused = errors.New("refused")
ErrRefused is the sentinel the CLI returns after printing a refusal verdict, so the entry point can map it to ExitCodeRefused.
Functions ¶
func AcceptedBlockingDecision ¶ added in v0.3.3
AcceptedBlockingDecision reports eligibility and whether the refusal key has an explicit decision. Completeness tests use decided to reject additions to the refusal vocabulary that have not been classified here.
func AcceptedBlockingEligible ¶ added in v0.3.3
AcceptedBlockingEligible reports whether a typed refusal is in the closed accepted-blocking registry. Unknown combinations fail closed.
Types ¶
type Cause ¶
type Cause string
Cause narrows a refusal reason to the typed discriminator automation branches on without parsing prose: which budget fired under ReasonBudgetExceeded, or which parent shape was refused under ReasonUnsupportedPartitionedParent. The accepted-blocking registry keys on it, so a cause is part of the refusal identity, not a rendering detail.
const ( // CauseNone is the zero cause for verdicts whose reason has no narrower // discriminator. CauseNone Cause = "" // CauseLockBudget: the lock was not granted within lock_timeout; nothing // was executed. CauseLockBudget Cause = "lock-budget" // CauseStatementBudget: the statement ran past statement_timeout and was // cancelled; the change needs a rewrite. CauseStatementBudget Cause = "statement-budget" // CauseParentBlockingIndexBuild identifies a blocking index build on a // partitioned parent. CauseParentBlockingIndexBuild Cause = "parent-blocking-index-build" // CauseParentConcurrentIndexBuild identifies a concurrent index build on // a partitioned parent. CauseParentConcurrentIndexBuild Cause = "parent-concurrent-index-build" // CauseParentIndexAdoption identifies index adoption on a partitioned parent. CauseParentIndexAdoption Cause = "parent-index-adoption" // CauseParentNotValidForeignKey identifies a NOT VALID foreign key on a // partitioned parent. CauseParentNotValidForeignKey Cause = "parent-not-valid-foreign-key" )
The causes a refusal can carry, grouped by the reason they narrow.
type Class ¶ added in v0.3.3
type Class string
Class identifies the routing category of a refusal.
const ( // ClassCapabilityBoundary means the engine has no implemented safe route. ClassCapabilityBoundary Class = "capability-boundary" // ClassNoOnlineSafetyProblem means another owner should run the work. ClassNoOnlineSafetyProblem Class = "no-online-safety-problem" // ClassByDesign means pg-sprite permanently refuses the form. ClassByDesign Class = "by-design" // ClassEnvironmental means the current run environment blocked the work. ClassEnvironmental Class = "environmental" // ClassInvariantViolation reports incoherent engine state. ClassInvariantViolation Class = "invariant-violation" )
func Classes ¶ added in v0.3.3
func Classes() []Class
Classes returns the closed set of refusal classes.
func ParseClass ¶ added in v0.3.3
ParseClass validates a refusal class token.
type Outcome ¶
type Outcome string
Outcome is what happened to the submitted change.
const ( // OutcomeExecuted means the change ran and committed natively within // its budgets. OutcomeExecuted Outcome = "executed-natively" // OutcomeExecutedWithoutOnlineSafety means the operator accepted a typed // blocking refusal and the statement committed under explicit budgets. OutcomeExecutedWithoutOnlineSafety Outcome = "executed-without-online-safety" // OutcomeRefused means the change was not executed; Reason says why. OutcomeRefused Outcome = "refused" // OutcomeFailed means execution was attempted and failed: an // operational error, not a refusal — the process still exits 1. Code // carries the executor's stable outcome code, and for a mid-sequence // failure FailedStep and ExecutedSQL disclose the failed step and the // committed prefix whose state remains, so automation can distinguish // "nothing happened" from "partial state left behind". OutcomeFailed Outcome = "failed" )
The outcomes a migrate run can end in.
type Owner ¶ added in v0.3.3
type Owner string
Owner identifies who owns work that has no online-safety problem.
const ( // OwnerDataChangeRunner owns DML and backfills. OwnerDataChangeRunner Owner = "data-change-runner" // OwnerDeclarativeFrontDoor owns desired catalog convergence. OwnerDeclarativeFrontDoor Owner = "declarative-front-door" // OwnerDirectOperator means the operator runs the work directly. OwnerDirectOperator Owner = "direct-operator" // OwnerProvisioning owns access-control and replication provisioning. OwnerProvisioning Owner = "provisioning" )
func Owners ¶ added in v0.3.3
func Owners() []Owner
Owners returns the closed set of refusal owners.
func ParseOwner ¶ added in v0.3.3
ParseOwner validates a refusal owner token.
type Reason ¶
type Reason string
Reason is the typed cause of a refusal. Reasons are flat kebab-case tokens — they are what automation switches on; prose belongs in Detail.
const ( // ReasonNone is the zero reason carried by an executed verdict. ReasonNone Reason = "" // ReasonUnsupportedStatement: only ALTER TABLE is supported. ReasonUnsupportedStatement Reason = "unsupported-statement" // ReasonIndexStatement: index maintenance has a native safe idiom // (CONCURRENTLY) and is never attempted here. ReasonIndexStatement Reason = "index-statement" // ReasonTableTooLarge: the size guard skipped the optimistic attempt. ReasonTableTooLarge Reason = "not-native-safe-table-too-large" // ReasonInsufficientPrivileges: the connected role lacks the access // the change needs; Detail names the exact missing GRANT (see // docs/engine-role.md). ReasonInsufficientPrivileges Reason = "insufficient-privileges" // ReasonUnsupportedPartitionedParent: the routed plan builds an index // on a partitioned parent, for which the required partition-aware // online sequence is not implemented. ReasonUnsupportedPartitionedParent Reason = "unsupported-partitioned-parent" // ReasonBudgetExceeded: the optimistic attempt exceeded its lock or // statement budget and was cancelled. ReasonBudgetExceeded Reason = "not-native-safe-budget-exceeded" // ReasonRewriteRequired: the submitted form blocks and must run as a // safer native sequence, but the planner could not construct one (a // multi-operation statement, or a pattern it cannot build). Running // the submitted form would falsify the plan's own reason, so the // engine refuses instead. ReasonRewriteRequired Reason = "not-native-safe-rewrite-required" // this build does not implement (copy-and-swap). ReasonBackendUnavailable Reason = "backend-unavailable" // ReasonDestructiveChange: the desired-state plan discards live // structure — a dropped column, constraint, index, or NOT NULL — and // desired-state execution runs no destructive statement without an // explicit path for it. The imperative front door remains the way to // run a reviewed destructive statement deliberately. ReasonDestructiveChange Reason = "destructive-change" // ReasonPlanFingerprintMismatch: the plan recomputed at execution time // does not carry the fingerprint the caller pinned, so the plan a // reviewer approved is not the plan that would execute; nothing runs. ReasonPlanFingerprintMismatch Reason = "plan-fingerprint-mismatch" // ReasonCreateCollision: the create plan's target name is already // occupied — a relation or standalone type took it after the plan was // derived — so the greenfield create cannot run; the caller re-derives // the plan against the live catalog rather than assuming the // occupant's shape. ReasonCreateCollision Reason = "create-collision" )
The refusal reasons Phase 1 can emit.
func Reasons ¶ added in v0.2.0
func Reasons() []Reason
Reasons returns the closed set of non-zero Reason values. It is part of the verdict contract: the tokens are what automation switches on, so the set changes only deliberately, every token is pinned by test, and every token has a row in docs/cli-output-examples.md's refusal-reason table.
type Refusal ¶ added in v0.3.3
type Refusal struct {
// contains filtered or unexported fields
}
Refusal is proof that a class, reason, and owner form a valid refusal: the fields are unexported and the only constructor validates them, so a refusal that reaches a renderer through WithRefusal has been classified. The classification registries in pkg/plan and pkg/migrate mint these; a refusal site never names a class on its own.
func CapabilityBoundary ¶ added in v0.3.3
CapabilityBoundary classifies a refusal of work the engine may learn to do.
func Environmental ¶ added in v0.3.3
Environmental classifies a refusal the run environment caused.
func InvariantViolation ¶ added in v0.3.3
InvariantViolation classifies a refusal of a state this build should not have produced.
func NewRefusal ¶ added in v0.3.3
NewRefusal validates and constructs a refusal proof. It rejects a reason outside Reasons(), a class outside Classes(), an owner outside Owners(), and an owner that is absent when the class is no-online-safety-problem or present when it is not.
func NoOnlineSafetyProblem ¶ added in v0.3.3
NoOnlineSafetyProblem classifies a refusal of work that is safe to run elsewhere and names who runs it.
func (Refusal) Cause ¶ added in v0.3.3
Cause returns the typed cause that narrows the refusal, when one exists.
func (Refusal) IsZero ¶ added in v0.3.3
IsZero reports whether r was never constructed through NewRefusal.
func (Refusal) Owner ¶ added in v0.3.3
Owner returns the refusal's validated owner; empty unless the class is no-online-safety-problem.
func (Refusal) Site ¶ added in v0.3.3
func (r Refusal) Site() RefusalSite
Site returns the typed refusal site that narrows the refusal, when one exists.
func (Refusal) WithSite ¶ added in v0.3.3
func (r Refusal) WithSite(site RefusalSite) Refusal
WithSite returns r narrowed by a typed refusal site.
type RefusalSite ¶ added in v0.3.3
type RefusalSite string
RefusalSite is a typed refusal-site discriminator used when a reason spans statement shapes but has no underlying cause.
const ( // RefusalSiteIndexSingleRelation is the plain one-relation DROP INDEX, // REINDEX INDEX, or REINDEX TABLE gate site. RefusalSiteIndexSingleRelation RefusalSite = "index-statement-single-relation" // RefusalSiteIndexOther is a multi-relation DROP INDEX or a REINDEX scope // that does not identify one relation. RefusalSiteIndexOther RefusalSite = "index-statement-other" )
type Verdict ¶
type Verdict struct {
// Outcome is what happened.
Outcome Outcome `json:"outcome"`
// Reason is the typed refusal cause; empty when executed.
Reason Reason `json:"reason,omitempty"`
// Class identifies how a consumer routes a refusal.
Class Class `json:"class,omitempty"`
// Owner identifies who owns work with no online-safety problem.
Owner Owner `json:"owner,omitempty"`
// Cause narrows the refusal reason to its typed discriminator: the
// budget that fired, or the partitioned-parent shape refused. Empty
// when the reason has none. Preserved on an accepted-blocking verdict.
Cause Cause `json:"cause,omitempty"`
// Code is the executor's stable outcome code (executor.OutcomeCode)
// carried by a failed verdict — flat kebab-case, part of the executor's
// report contract. It stays a plain string here so this contract
// package does not depend on the executor. Empty unless Outcome is
// OutcomeFailed.
Code string `json:"code,omitempty"`
// FailedStep is the 1-based position of the sequence step that failed,
// matching the numbering the planner's partial-failure contracts use;
// zero when the failure was not a mid-sequence one (a single-statement
// attempt rolls back and commits nothing).
FailedStep int `json:"failed_step,omitempty"`
// FailedStepSQL is the failed step's statement — the step the planner's
// partial-failure contract says a retry resumes from.
FailedStepSQL string `json:"failed_step_sql,omitempty"`
// Attempts is how many bounded attempts ran before a lock-budget
// refusal, so automation can tell an exhausted bounded retry from a
// single cancelled attempt; zero for every other verdict.
Attempts int `json:"attempts,omitempty"`
// Statement is the submitted SQL.
Statement string `json:"statement"`
// Table is the target table (schema-qualified when the statement was),
// when the statement has one.
Table string `json:"table,omitempty"`
// Detail is the human explanation: why refused, or what committed.
Detail string `json:"detail,omitempty"`
// SaferIdiom is a native alternative to the refused statement, when one
// exists (e.g. CREATE INDEX CONCURRENTLY, ADD CONSTRAINT ... NOT VALID).
SaferIdiom string `json:"safer_idiom,omitempty"`
// ExecutedSQL is the ordered SQL the engine actually ran and committed.
// On an executed verdict it is the substituted safer native sequence
// (empty when the submitted form ran as-is — a non-empty value is what
// tells automation a substitution happened). On a failed verdict it is
// the committed prefix that remains: empty means nothing committed.
ExecutedSQL []string `json:"executed_sql,omitempty"`
// Forced reports that --force overrode the engine's routing: the
// submitted form ran as-is instead of a safer substitution or a
// strategy refusal. It is the machine-readable audit record of the
// override.
Forced bool `json:"forced,omitempty"`
// BlockingPassthrough marks execution of an operator-accepted refusal.
BlockingPassthrough bool `json:"blocking_passthrough,omitempty"`
// LockTimeout is the explicit lock-acquisition budget.
LockTimeout string `json:"lock_timeout,omitempty"`
// StatementTimeout is the explicit statement execution budget.
StatementTimeout string `json:"statement_timeout,omitempty"`
// contains filtered or unexported fields
}
Verdict is the structured outcome of one migrate invocation.
func (Verdict) Refusal ¶ added in v0.3.3
Refusal reconstructs the classified refusal a refused verdict carries, so a caller that aggregates verdicts into its own result propagates the class and owner through the same one path instead of copying fields. It fails on a verdict that is not refused, or whose reason, class, and owner do not validate together — a verdict this build cannot have produced. An in-process verdict returns its full proof, re-validated the same way and checked against the exported refusal fields, so a proof that never passed the constructors, or fields rewritten after WithRefusal, cannot reach an eligibility decision. A verdict decoded from JSON reconstructs class, reason, owner, and cause, but cannot recover its refusal site; site-keyed eligibility therefore fails closed after JSON decoding, while cause-keyed eligibility is decidable from the JSON fields.
func (Verdict) WithAcceptedBlocking ¶ added in v0.3.3
func (v Verdict) WithAcceptedBlocking(r Refusal, lockTimeout, statementTimeout time.Duration) (Verdict, error)
WithAcceptedBlocking returns an executed verdict that retains the accepted refusal identity and explicit non-zero execution budgets.
func (Verdict) WithRefusal ¶ added in v0.3.3
WithRefusal returns v as a refused verdict carrying r's reason, class, owner, cause, and full in-process proof. It is the one path from a classified refusal onto the verdict contract, so a site that forgets to classify has no Reason to set.