Documentation
¶
Overview ¶
Package protocol defines the public convergence types for the Pasture multi-agent orchestration protocol.
These types are consumed by all other packages (formatters, handlers, workflows, audit, ACP adapter). They are designed to be serialization-safe for the durable runtime's serializer and standard encoding/json.
JSON tags use camelCase to match Python aura-protocol output format.
Index ¶
- Variables
- func DedupKey(epochID, phase, kind, stepSeq string) string
- func MustHaveImpl()
- func RegisterOpenTaskTracker(impl func(dbPath string) (TaskTracker, error))
- func ReviewWorkflowID(epochId, phaseId string, round int) string
- type AuditEvent
- type AutomatonRole
- type Context
- type ContextKind
- type EpochState
- type EpochStateMachine
- func (sm *EpochStateMachine) Advance(toPhase PhaseId, triggeredBy string, conditionMet string, timestamp time.Time) (*TransitionRecord, error)
- func (sm *EpochStateMachine) AvailableTransitions() []PhaseId
- func (sm *EpochStateMachine) HasConsensus() bool
- func (sm *EpochStateMachine) RecordBlocker(resolved bool)
- func (sm *EpochStateMachine) RecordFailedTransition(fromPhase, toPhase PhaseId, timestamp time.Time, triggeredBy string, err error)
- func (sm *EpochStateMachine) RecordVote(axis ReviewAxis, vote VoteType) error
- func (sm *EpochStateMachine) State() *EpochState
- func (sm *EpochStateMachine) ValidateAdvance(toPhase PhaseId) []string
- type EventType
- type PastureRole
- type PhaseAdvanceSignal
- type PhaseId
- type PhaseSpec
- type Pipeline
- type QueryName
- type QueryStateResult
- type RegisterSessionSignal
- type ReviewAxis
- type ReviewCycleRecord
- type ReviewVoteSignal
- type RoleId
- type SessionEntry
- type SeverityLevel
- type SignalTopic
- type SliceCompleteSignal
- type SliceExecutionMode
- type SliceProgressSignal
- type SliceStartSignal
- type TaskAssignmentState
- type TaskAssignmentTransferError
- type TaskAssignmentTransferErrorKind
- type TaskTracker
- type TransferTaskAssignmentRequest
- type TransferTaskAssignmentResult
- type TransitionError
- type TransitionRecord
- type VoteType
Constants ¶
This section is empty.
Variables ¶
var AllAutomatonRoles = []AutomatonRole{ AutomatonRoleNone, AutomatonRoleConstraintChecker, AutomatonRoleTransitionGate, AutomatonRoleHookHandler, AutomatonRoleConsensusReached, AutomatonRoleCreateFollowup, }
AllAutomatonRoles is the ordered slice of all valid AutomatonRole values. Useful for iteration, completeness checks, and parameterised tests.
var AllContextKinds = []ContextKind{ ContextNone, ContextEpoch, ContextSlice, ContextReview, ContextFollowup, ContextGit, ContextSkill, ContextSession, }
AllContextKinds is the ordered slice of all valid ContextKind values.
Useful for parameterised tests and Scenario 10's enum-membership assertion (the test confirms that ResearcherNoteContext is NOT present).
var AllEventTypes = []EventType{ EventPhaseTransition, EventPhaseAdvance, EventVoteRecorded, EventConstraintChecked, EventSliceStarted, EventSliceCompleted, EventSessionRegistered, EventReviewCycleStarted, EventEpochCancelled, }
AllEventTypes is the ordered slice of all valid EventType values.
var AllPastureRoles = []PastureRole{ PastureRoleNone, PastureRoleArchitect, PastureRoleSupervisor, PastureRoleWorker, PastureRoleReviewer, }
AllPastureRoles is the ordered slice of all valid PastureRole values.
var AllPhaseIds = []PhaseId{ PhaseRequest, PhaseElicit, PhasePropose, PhaseReview, PhasePlanReview, PhaseRatify, PhaseHandoff, PhaseImplPlan, PhaseWorkerSlices, PhaseCodeReview, PhaseImplUAT, PhaseLanding, PhaseComplete, }
AllPhaseIds is the ordered slice of all valid PhaseId values (pipeline + terminal). Useful for iteration, completeness checks, and building lookup tables.
var AllQueryNames = []QueryName{ QueryCurrentState, QueryAvailableTransitions, QueryFullState, QuerySliceProgressState, QueryActiveSessions, }
AllQueryNames is the ordered set of every valid QueryName.
var AllReviewAxes = []ReviewAxis{AxisCorrectness, AxisTestQuality, AxisElegance}
AllReviewAxes is the ordered slice of all valid ReviewAxis values.
var AllRoleIds = []RoleId{RoleEpoch, RoleArchitect, RoleReviewer, RoleSupervisor, RoleWorker}
AllRoleIds is the ordered slice of all valid RoleId values.
var AllSeverityLevels = []SeverityLevel{SeverityBlocker, SeverityImportant, SeverityMinor}
AllSeverityLevels is the ordered slice of all valid SeverityLevel values.
var AllSignalTopics = []SignalTopic{ SignalAdvancePhase, SignalSubmitVote, SignalSliceProgress, SignalRegisterSession, SignalStartSlice, SignalCompleteSlice, }
AllSignalTopics is the ordered set of every valid SignalTopic.
var AllSliceExecutionModes = []SliceExecutionMode{SliceMock, SliceTmux, SliceSubprocess}
AllSliceExecutionModes is the ordered set of every valid SliceExecutionMode.
var AllVoteTypes = []VoteType{VoteAccept, VoteRevise}
AllVoteTypes is the ordered slice of all valid VoteType values.
var DefaultPipeline = Pipeline{ PhaseRequest, PhaseElicit, PhasePropose, PhaseReview, PhasePlanReview, PhaseRatify, PhaseHandoff, PhaseImplPlan, PhaseWorkerSlices, PhaseCodeReview, PhaseImplUAT, PhaseLanding, }
DefaultPipeline is the standard 12-phase pasture protocol pipeline. The index in this slice determines the pX number (0-based index + 1).
var PhaseSpecs = map[PhaseId]PhaseSpec{ PhaseRequest: {Transitions: []PhaseId{PhaseElicit}}, PhaseElicit: {Transitions: []PhaseId{PhasePropose}}, PhasePropose: {Transitions: []PhaseId{PhaseReview}}, PhaseReview: {Transitions: []PhaseId{PhasePlanReview, PhasePropose}}, PhasePlanReview: {Transitions: []PhaseId{PhaseRatify}}, PhaseRatify: {Transitions: []PhaseId{PhaseHandoff}}, PhaseHandoff: {Transitions: []PhaseId{PhaseImplPlan}}, PhaseImplPlan: {Transitions: []PhaseId{PhaseWorkerSlices}}, PhaseWorkerSlices: {Transitions: []PhaseId{PhaseCodeReview}}, PhaseCodeReview: {Transitions: []PhaseId{PhaseImplUAT, PhaseWorkerSlices}}, PhaseImplUAT: {Transitions: []PhaseId{PhaseLanding}}, PhaseLanding: {Transitions: []PhaseId{PhaseComplete}}, }
PhaseSpecs is the canonical transition table for the 12-phase epoch lifecycle.
Gate rules (enforced by EpochStateMachine, not by this table):
- PhaseReview→PhasePlanReview and PhaseCodeReview→PhaseImplUAT require all 3 review axes to ACCEPT (consensus gate).
- PhaseCodeReview→PhaseImplUAT additionally requires blocker_count == 0 (BLOCKER gate).
- At PhaseReview/PhaseCodeReview with any REVISE vote, only the backward transition is available.
Functions ¶
func DedupKey ¶ added in v0.0.5
DedupKey derives the deterministic deduplication key for a single forensic emission. It is a name-based UUID (version 5) over the name
"<epochID>/<phase>/<kind>/<stepSeq>"
with field order and the "/" separator fixed. The same inputs always yield the same key, and distinct epochs always yield distinct keys (the epoch is hashed into the name), so a crash-replay collapses onto the same row while two different epochs at the same (phase, kind, stepSeq) stay distinct.
Both forensic tiers call this one function: the audit tier passes its event_type as kind and stores the result in the audit_events dedup_key column; the activities tier passes its activity_kind as kind and uses the result as the activity's primary-key id. They differ only in storage, never in derivation.
Invariant: at most one forensic emission of a given kind per step. Two emissions of the same kind within one durable step would derive the same key and the second would be dropped by the ON CONFLICT clause; callers that need multiple same-kind emissions in one step must disambiguate the stepSeq.
func MustHaveImpl ¶
func MustHaveImpl()
MustHaveImpl panics if RegisterOpenTaskTracker has not yet been called. Call this at program startup (e.g. in a TestMain or an init() of a top-level package) to fail fast with a clear error rather than discovering the missing wiring the first time OpenTaskTracker is called.
Example — add this to your main package or TestMain:
func init() { protocol.MustHaveImpl() }
In-tree callers (cmd/pasture, cmd/pastured, internal/handlers) already import internal/tasks directly and therefore never need this guard. The guard exists for completeness and for any future in-module integration tests that construct main-like binaries without going through the handler layer.
func RegisterOpenTaskTracker ¶
func RegisterOpenTaskTracker(impl func(dbPath string) (TaskTracker, error))
RegisterOpenTaskTracker is called by internal/tasks's init() to wire the constructor implementation. It is exported only so the internal package can assign through it; external packages MUST NOT call it directly (doing so is a programming error and will overwrite the implementation).
This indirection keeps (TaskTracker, OpenTaskTracker) co-located in pkg/protocol (PROPOSAL-2 §7.4) while the body lives in internal/tasks (UAT-1 placement binding). Without it, OpenTaskTracker's body would import internal/tasks — forbidden because internal/tasks already imports pkg/protocol for the TaskTracker type, which would create an import cycle.
See MustHaveImpl for a startup-time guard that panics if this function has not been called.
func ReviewWorkflowID ¶ added in v0.0.5
ReviewWorkflowID derives the deterministic DBOS workflow id for a review sub-workflow from the epoch id, phase id, and round number.
The round component is required because DBOS workflow ids are idempotent: a second call with the same id returns the already-completed first workflow and its memoized result. Without the round, a REVISE outcome on round 1 would prevent round 2 from running — EnqueueReview would silently return the stale round-1 REVISE result forever, breaking the iterate-until-ACCEPT loop.
The round value MUST come from a deterministic, replay-stable counter tracked in workflow state (the FSM-tracked review-cycle counter), NOT from wall-clock time or a random value — replay-stability is required because DBOS re-executes the workflow body on crash recovery, and a non-deterministic id would produce a different workflow address on each replay.
Both the enqueue side (engine.EnqueueReview) and the send side (any caller that submits a vote via submit_vote) MUST use this function to compute the workflow id.
Types ¶
type AuditEvent ¶
type AuditEvent struct {
EpochId string `json:"epochId"`
Phase PhaseId `json:"phase"`
Role string `json:"role"`
EventType EventType `json:"eventType"`
Payload map[string]any `json:"payload"`
Timestamp time.Time `json:"timestamp"`
DedupKey string `json:"dedupKey,omitempty"`
}
AuditEvent is a generic audit trail event emitted by epoch workflows and activities. JSON tags use camelCase to match Python aura-protocol output.
DedupKey is the OPTIONAL deterministic deduplication key (see DedupKey). When set, the SQLite trail writes it into the dedup_key column with an ON CONFLICT … DO NOTHING upsert, so a crash-replay of the emitting durable step records the row exactly once. Ordinary (non-engine) callers leave it empty; the column is then NULL and the partial unique index ignores the row, preserving the legacy insert-always behaviour.
type AutomatonRole ¶
type AutomatonRole string
AutomatonRole is the strongly-typed pasture-side category for SoftwareAgent instances that represent rules-based automata (PROPOSAL-2 §7.6, URD R8).
Wire values are stable strings stored in pasture_agent_categories.automaton_role. The enum has exactly 6 values (None + 5 concrete categories); UAT-1 dropped the earlier generic "Derivation" catch-all in favor of the two first-class values ConsensusReached and CreateFollowup.
const ( // AutomatonRoleNone marks a SoftwareAgent that has no pasture-side // automaton role (e.g. the pastured daemon process itself). AutomatonRoleNone AutomatonRole = "None" // AutomatonRoleConstraintChecker is the canonical name for the // constraint-checker automaton (pasture/automaton/check-constraints). AutomatonRoleConstraintChecker AutomatonRole = "ConstraintChecker" // AutomatonRoleTransitionGate covers the 3 transition gate kinds // (consensus, vote-threshold, exit-condition). AutomatonRoleTransitionGate AutomatonRole = "TransitionGate" // AutomatonRoleHookHandler covers all Claude-Code-hook-event handlers // (per Pasture URD D7's hook list). AutomatonRoleHookHandler AutomatonRole = "HookHandler" // AutomatonRoleConsensusReached is the synthesized event emitted when // all reviewers have voted ACCEPT during a phase transition. UAT-1 // promoted this from a Derivation child to a first-class category. AutomatonRoleConsensusReached AutomatonRole = "ConsensusReached" // AutomatonRoleCreateFollowup synthesizes follow-up epics from // PROPOSAL findings during ratification. UAT-1 promoted this to a // first-class category alongside ConsensusReached. AutomatonRoleCreateFollowup AutomatonRole = "CreateFollowup" )
func (AutomatonRole) IsValid ¶
func (r AutomatonRole) IsValid() bool
IsValid reports whether r is a known AutomatonRole value.
Membership is tested via switch (not slice scan) so the compiler can flag missing cases when the enum grows.
func (AutomatonRole) String ¶
func (r AutomatonRole) String() string
String returns the wire-format string value of r.
type Context ¶
type Context struct {
Kind ContextKind `json:"kind"`
ContextId string `json:"contextId"`
}
Context carries a typed (Kind, ContextId) pair as returned by TaskTracker.EventContexts. PROPOSAL-2 §7.5.
ContextId's shape varies per Kind:
- ContextEpoch / ContextSlice / ContextReview / ContextFollowup: a Provenance TaskID string ("namespace--uuid").
- ContextGit: a git commit SHA (or remote ref).
- ContextSkill: a skill run ID.
- ContextSession: a Claude Code session ID.
- ContextNone: unused.
type ContextKind ¶
type ContextKind string
ContextKind enumerates the kinds of context an audit event may attach to.
Wire values are stable strings stored in context_edges.context_kind. The enum has exactly 8 values (None + 7 concrete kinds) — note that PROPOSAL-2 Scenario 10 asserts ResearcherNoteContext is NOT a member.
const ( // ContextNone is the zero value indicating "no context" — used by // callers building empty Context structs and by IsValid() to allow the // zero value through. ContextNone ContextKind = "None" // ContextEpoch attaches an event to an epoch (Provenance TaskID for the // originating REQUEST). Wire format of context_id: "namespace--uuid". ContextEpoch ContextKind = "EpochContext" // ContextSlice attaches an event to an implementation slice (Beads // SLICE-N task ID). ContextSlice ContextKind = "SliceContext" // ContextReview attaches an event to a review cycle. ContextReview ContextKind = "ReviewContext" // ContextFollowup attaches an event to a FOLLOWUP_SLICE-N task. ContextFollowup ContextKind = "FollowupContext" // ContextGit attaches a free-floating git event (commit, push, rebase) // using the commit SHA (or remote ref) as context_id. ContextGit ContextKind = "GitContext" // ContextSkill attaches a /pasture:* skill invocation; context_id is the // skill run ID. ContextSkill ContextKind = "SkillContext" // ContextSession attaches a Claude Code session event; context_id is // the session ID. ContextSession ContextKind = "SessionContext" )
func (ContextKind) IsValid ¶
func (k ContextKind) IsValid() bool
IsValid reports whether k is a known ContextKind value.
Used by Scenario 10 to assert IsValid("ResearcherNoteContext") == false.
func (ContextKind) String ¶
func (k ContextKind) String() string
String returns the wire-format string value of k.
type EpochState ¶ added in v0.0.5
type EpochState struct {
EpochId string `json:"epochId"`
CurrentPhase PhaseId `json:"currentPhase"`
CurrentRole RoleId `json:"currentRole"`
CompletedPhases []PhaseId `json:"completedPhases"`
ReviewVotes map[ReviewAxis]VoteType `json:"reviewVotes"`
BlockerCount int `json:"blockerCount"`
TransitionHistory []TransitionRecord `json:"transitionHistory"`
// ReviewCycles tracks per-slice review-fix cycle history.
// Key: slice task ID. Value: ordered list of review rounds for that slice.
ReviewCycles map[string][]ReviewCycleRecord `json:"reviewCycles,omitempty"`
LastError *string `json:"lastError,omitempty"`
ActiveSessionCount int `json:"activeSessionCount"`
// ActiveSessions holds the sessions registered with this epoch, in
// registration order, de-duplicated by SessionId. ActiveSessionCount stays
// equal to len(ActiveSessions); both are maintained together. omitempty so
// epochs driven without the signal-driven control loop serialize unchanged.
ActiveSessions []RegisterSessionSignal `json:"activeSessions,omitempty"`
// SliceProgress holds the slice-progress events reported to this epoch, in
// arrival order. omitempty for backward-compatible serialization.
SliceProgress []SliceProgressSignal `json:"sliceProgress,omitempty"`
}
EpochState holds the runtime state of a single epoch workflow.
Tracks the current phase, completed phases, review votes, blocker count, current role, and full transition history. Mutable — updated by the durable engine on each transition; serialized into the projection that queries read.
type EpochStateMachine ¶ added in v0.0.5
type EpochStateMachine struct {
// contains filtered or unexported fields
}
EpochStateMachine manages the 12-phase epoch lifecycle with phase transition validation and vote/blocker gate checks. Pure Go — no substrate dependency.
Usage:
sm := NewEpochStateMachine("epoch-123", nil)
record, err := sm.Advance(PhaseElicit, "architect", "classification confirmed", time.Now())
sm.RecordVote(AxisCorrectness, VoteAccept)
sm.RecordVote(AxisTestQuality, VoteAccept)
sm.RecordVote(AxisElegance, VoteAccept)
record, err = sm.Advance(PhasePlanReview, "reviewer", "all 3 vote ACCEPT", time.Now())
func NewEpochStateMachine ¶ added in v0.0.5
func NewEpochStateMachine(epochId string, specs map[PhaseId]PhaseSpec) *EpochStateMachine
NewEpochStateMachine creates a new EpochStateMachine initialized to PhaseRequest. Accepts an optional specs map for dependency injection in tests; pass nil to use the canonical PhaseSpecs.
func NewEpochStateMachineFromState ¶ added in v0.0.5
func NewEpochStateMachineFromState(state *EpochState, specs map[PhaseId]PhaseSpec) *EpochStateMachine
NewEpochStateMachineFromState rebuilds an EpochStateMachine around an existing EpochState snapshot — for validation or recompute paths that already hold a state (e.g. constraint checks, query recompute) and must not start from PhaseRequest. The state pointer is adopted, not copied; pass a snapshot you own. specs is the transition table; pass nil for the canonical PhaseSpecs.
func (*EpochStateMachine) Advance ¶ added in v0.0.5
func (sm *EpochStateMachine) Advance( toPhase PhaseId, triggeredBy string, conditionMet string, timestamp time.Time, ) (*TransitionRecord, error)
Advance transitions the epoch to toPhase.
Validates first; returns TransitionError if invalid. On success:
- Appends the current phase to CompletedPhases.
- Sets CurrentPhase = toPhase.
- Appends a TransitionRecord to TransitionHistory.
- Clears ReviewVotes (votes are phase-scoped).
- Clears LastError.
timestamp is used for the record; pass time.Now() for production or a fixed time for determinism in tests. In a durable step, pass the deterministic step time so replays record an identical timestamp.
func (*EpochStateMachine) AvailableTransitions ¶ added in v0.0.5
func (sm *EpochStateMachine) AvailableTransitions() []PhaseId
AvailableTransitions returns the transitions currently available from the current phase, filtered by vote/blocker/consensus state.
Gate rule priority (highest first):
- REVISE gate: If at p4/p10 with any REVISE vote, only backward transition.
- Consensus gate: p4→p5 / p10→p11 excluded until all 3 axes ACCEPT.
- BLOCKER gate: p10→p11 excluded while blocker_count > 0.
Returns empty slice when current phase is Complete or has no spec.
func (*EpochStateMachine) HasConsensus ¶ added in v0.0.5
func (sm *EpochStateMachine) HasConsensus() bool
HasConsensus returns true if all 3 review axes have ACCEPT votes.
func (*EpochStateMachine) RecordBlocker ¶ added in v0.0.5
func (sm *EpochStateMachine) RecordBlocker(resolved bool)
RecordBlocker updates the blocker count. resolved=false: increment (new blocker); resolved=true: decrement (blocker resolved). Clamped to 0; cannot go negative.
func (*EpochStateMachine) RecordFailedTransition ¶ added in v0.0.5
func (sm *EpochStateMachine) RecordFailedTransition( fromPhase, toPhase PhaseId, timestamp time.Time, triggeredBy string, err error, )
RecordFailedTransition appends a failed TransitionRecord to the transition history and records the error message in LastError.
This is the correct mutation path for failed advances — callers must not mutate State() directly (see State() doc). fromPhase and toPhase describe the attempted transition; err is the failure reason.
func (*EpochStateMachine) RecordVote ¶ added in v0.0.5
func (sm *EpochStateMachine) RecordVote(axis ReviewAxis, vote VoteType) error
RecordVote records a reviewer vote for the given axis. Overwrites any previous vote for the same axis.
Returns an error if axis is not a valid ReviewAxis value.
func (*EpochStateMachine) State ¶ added in v0.0.5
func (sm *EpochStateMachine) State() *EpochState
State returns the current epoch state. Callers must not modify the returned pointer directly; use RecordVote, RecordBlocker, and Advance instead.
func (*EpochStateMachine) ValidateAdvance ¶ added in v0.0.5
func (sm *EpochStateMachine) ValidateAdvance(toPhase PhaseId) []string
ValidateAdvance returns a list of violation strings for a proposed transition. An empty list means the transition is valid and Advance would succeed.
Checks (in order):
- Current phase is not COMPLETE.
- to_phase is in the transition table for the current phase.
- Consensus gate: p4→p5 / p10→p11 require HasConsensus().
- BLOCKER gate: p10→p11 requires BlockerCount == 0.
type EventType ¶
type EventType string
EventType classifies an audit event in the dual-write trail.
const ( EventPhaseTransition EventType = "PhaseTransition" EventPhaseAdvance EventType = "PhaseAdvance" EventVoteRecorded EventType = "VoteRecorded" EventConstraintChecked EventType = "ConstraintChecked" EventSliceStarted EventType = "SliceStarted" EventSliceCompleted EventType = "SliceCompleted" EventSessionRegistered EventType = "SessionRegistered" EventReviewCycleStarted EventType = "ReviewCycleStarted" // EventEpochCancelled records that an operator explicitly cancelled a // running epoch via the CLI. The event payload carries the operator's // reason (key "reason", empty string when no reason was given). Unlike // engine-emitted transition events, this event is a one-shot CLI action // and is written via the non-dedup RecordEvent path (NULL dedup_key). EventEpochCancelled EventType = "EpochCancelled" )
type PastureRole ¶
type PastureRole string
PastureRole mirrors the PROV-O Role concept for non-automaton agents (humans and MLAgents acting in epoch roles). PROPOSAL-2 §7.6 / URD R8.
Wire values are stable strings stored in pasture_agent_categories.pasture_role.
const ( // PastureRoleNone marks an agent without a pasture-side role // (e.g. SoftwareAgents whose categorisation is fully captured by // AutomatonRole). PastureRoleNone PastureRole = "None" // PastureRoleArchitect — owns proposal authoring (Phases 3-7). PastureRoleArchitect PastureRole = "Architect" // PastureRoleSupervisor — owns IMPL_PLAN, slice decomposition, // worker dispatch, and code-review coordination (Phases 8-10). PastureRoleSupervisor PastureRole = "Supervisor" // PastureRoleWorker — implements vertical slices in Phase 9. PastureRoleWorker PastureRole = "Worker" // PastureRoleReviewer — performs code/plan reviews (Phases 4, 5, 10). PastureRoleReviewer PastureRole = "Reviewer" )
func (PastureRole) IsValid ¶
func (r PastureRole) IsValid() bool
IsValid reports whether r is a known PastureRole value.
func (PastureRole) String ¶
func (r PastureRole) String() string
String returns the wire-format string value of r.
type PhaseAdvanceSignal ¶ added in v0.0.5
type PhaseAdvanceSignal struct {
ToPhase PhaseId `json:"toPhase"`
TriggeredBy string `json:"triggeredBy"`
ConditionMet string `json:"conditionMet"`
}
PhaseAdvanceSignal is the payload for the advance_phase signal.
Sent by any authorized caller to transition the epoch to a new phase. TriggeredBy identifies who or what sent the signal (e.g. a role name or external trigger). ConditionMet describes the transition condition from the protocol table that was satisfied.
type PhaseId ¶
type PhaseId string
PhaseId identifies a phase in the 12-phase epoch lifecycle by its name. The position (pX number) is determined by the phase's index in a Pipeline, not by the value of the PhaseId itself.
const ( PhaseRequest PhaseId = "request" PhaseElicit PhaseId = "elicit" PhasePropose PhaseId = "propose" PhaseReview PhaseId = "review" PhasePlanReview PhaseId = "plan-review" PhaseRatify PhaseId = "ratify" PhaseHandoff PhaseId = "handoff" PhaseImplPlan PhaseId = "impl-plan" PhaseWorkerSlices PhaseId = "worker-slices" PhaseCodeReview PhaseId = "code-review" PhaseImplUAT PhaseId = "impl-uat" PhaseLanding PhaseId = "landing" PhaseComplete PhaseId = "complete" )
func ParsePhaseId ¶
ParsePhaseId parses a flexible phase input string into a PhaseId.
Supported formats (case-insensitive):
- Name only: "request", "elicit", "propose", "review", "plan-review", "ratify", "handoff", "impl-plan", "worker-slices", "code-review", "impl-uat", "landing", "complete"
- pX format: "p1", "p2", ..., "p12" (resolved via DefaultPipeline)
- pX-name: "p1-request", "p2-elicit", ... (legacy; name portion used)
- Number only: "1", "2", ..., "12"
Returns an error if the input does not match any known format.
type PhaseSpec ¶ added in v0.0.5
type PhaseSpec struct {
// Transitions lists all target PhaseIds reachable from this phase.
Transitions []PhaseId
}
PhaseSpec describes the allowed forward/backward transitions from one phase.
type Pipeline ¶
type Pipeline []PhaseId
Pipeline is an ordered sequence of phases. The 0-based index of a PhaseId in the slice determines its 1-based phase number (pX). PhaseComplete is a terminal state and is NOT included in the pipeline itself.
func (Pipeline) Next ¶
Next returns the next phase after id in the pipeline. Returns PhaseComplete if id is the last phase or not found.
func (Pipeline) PhaseAt ¶
PhaseAt returns the PhaseId at the given 1-based phase number. Returns ("", false) if number is out of range.
func (Pipeline) PhaseNumber ¶
PhaseNumber returns the 1-based phase number for id, or -1 if not found.
type QueryName ¶ added in v0.0.5
type QueryName string
QueryName identifies a read-only epoch state query. Queries are answered from the persisted EpochState projection (a SQL read), never a workflow round-trip.
const ( QueryCurrentState QueryName = "current_state" QueryAvailableTransitions QueryName = "available_transitions" QueryFullState QueryName = "full_state" QuerySliceProgressState QueryName = "slice_progress_state" QueryActiveSessions QueryName = "active_sessions" )
Query names for epoch-level state inspection.
func ParseQueryName ¶ added in v0.0.5
ParseQueryName converts a raw string to a QueryName, reporting whether it is a recognized query. Used at the CLI boundary where the query is user-supplied.
type QueryStateResult ¶ added in v0.0.5
type QueryStateResult struct {
CurrentPhase PhaseId `json:"currentPhase"`
CurrentRole RoleId `json:"currentRole"`
TransitionHistory []TransitionRecord `json:"transitionHistory"`
Votes map[ReviewAxis]VoteType `json:"votes"`
LastError *string `json:"lastError,omitempty"`
AvailableTransitions []PhaseId `json:"availableTransitions"`
ActiveSessionCount int `json:"activeSessionCount"`
}
QueryStateResult is a serialization-safe snapshot of epoch state returned by the full-state query. Designed for CLI consumers.
AvailableTransitions lists the target PhaseIds reachable from the current phase given the current vote/blocker state.
type RegisterSessionSignal ¶ added in v0.0.5
type RegisterSessionSignal struct {
EpochId string `json:"epochId"`
SessionId string `json:"sessionId"`
Role string `json:"role"`
ModelHarness string `json:"modelHarness"`
Model string `json:"model"`
}
RegisterSessionSignal is the payload for the register_session signal.
Registers a Claude Code session with the active epoch for observability and permission tracking. Duplicate session_id registrations are silently ignored (idempotent). ModelHarness identifies the runtime harness (e.g. "claude-code").
type ReviewAxis ¶ added in v0.0.5
type ReviewAxis string
ReviewAxis identifies a semantic dimension of a code review vote. Values are lowercase wire-format strings used in JSON serialization.
const ( AxisCorrectness ReviewAxis = "correctness" AxisTestQuality ReviewAxis = "test_quality" AxisElegance ReviewAxis = "elegance" )
func (ReviewAxis) IsValid ¶ added in v0.0.5
func (a ReviewAxis) IsValid() bool
IsValid reports whether a is a known ReviewAxis value.
type ReviewCycleRecord ¶ added in v0.0.5
type ReviewCycleRecord struct {
// SliceId is the task ID of the slice being reviewed.
SliceId string `json:"sliceId"`
// Round is the 1-based review cycle number for this slice (max 3).
Round int `json:"round"`
// Votes maps each reviewer axis to its vote for this round.
Votes map[ReviewAxis]VoteType `json:"votes"`
// FindingCounts maps severity level to the number of findings.
FindingCounts map[SeverityLevel]int `json:"findingCounts"`
// Timestamp records when the review round completed.
Timestamp time.Time `json:"timestamp"`
}
ReviewCycleRecord tracks the state of a single review-fix cycle for one slice.
The supervisor creates one record per (slice, round) pair. It captures which reviewers participated, their votes, and the count of findings by severity. This enables the supervisor to enforce the max-3-cycles constraint and determine whether a clean exit was reached via IsCleanExit().
func (ReviewCycleRecord) IsCleanExit ¶ added in v0.0.5
func (r ReviewCycleRecord) IsCleanExit() bool
IsCleanExit returns true if the review cycle is clean: all 3 axes voted ACCEPT AND there are 0 BLOCKERs and 0 IMPORTANTs. This is the single authoritative check for the review-wave workflow.
type ReviewVoteSignal ¶ added in v0.0.5
type ReviewVoteSignal struct {
Axis ReviewAxis `json:"axis"`
Vote VoteType `json:"vote"`
ReviewerId string `json:"reviewerId"`
}
ReviewVoteSignal is the payload for the submit_vote signal.
ReviewerId must be the unique identifier for the reviewer agent submitting the vote. Axis and Vote use their wire-format string values for JSON round-trip safety.
type RoleId ¶ added in v0.0.5
type RoleId string
RoleId identifies an agent role within the protocol. Values match schema.xml <role id="..."> elements.
type SessionEntry ¶
type SessionEntry struct {
SessionId string `json:"sessionId"`
EntryIndex int `json:"entryIndex"`
Provider string `json:"provider"`
EntryType string `json:"entryType"`
Role string `json:"role"`
TimestampMs *int64 `json:"timestampMs,omitempty"`
ContentPreview *string `json:"contentPreview,omitempty"` // max 500 chars
TokensIn *int `json:"tokensIn,omitempty"`
TokensOut *int `json:"tokensOut,omitempty"`
HasToolUse bool `json:"hasToolUse"`
ToolKind *string `json:"toolKind,omitempty"` // ACP-aligned tool classification
ToolNamesCsv *string `json:"toolNamesCsv,omitempty"` // comma-separated tool names
HasThinking bool `json:"hasThinking"`
IsError bool `json:"isError"`
StopReason *string `json:"stopReason,omitempty"` // ACP per-turn stop reason
RawByteLength *int `json:"rawByteLength,omitempty"` // raw JSON byte count
ToolCallId *string `json:"toolCallId,omitempty"` // MCP correlation
EntryId *string `json:"entryId,omitempty"` // provider-native ID
ParentEntryId *string `json:"parentEntryId,omitempty"` // parent entry link
Depth int `json:"depth"` // 0 = message, 1 = content part
ParentIndex *int `json:"parentIndex,omitempty"` // entryIndex of parent (nil for depth=0)
ToolInput *string `json:"toolInput,omitempty"` // tool_use input JSON
ToolOutput *string `json:"toolOutput,omitempty"` // tool_result output JSON
Extra *string `json:"extra,omitempty"` // JSON overflow for provider-specific data
}
SessionEntry represents a single indexed entry within a session transcript.
Aligned with agent-data-leverage pkg/schema.SessionEntry schema. All optional fields use pointer types to distinguish absent from zero-value. JSON tags use camelCase aligned with the ACP content model and Python output.
Maps 1:1 to a row in the session_entries table in the audit SQLite backend.
type SeverityLevel ¶ added in v0.0.5
type SeverityLevel string
SeverityLevel classifies the severity of a code review finding. Used as the key type for ReviewCycleRecord.FindingCounts to prevent stringly-typed map access.
const ( SeverityBlocker SeverityLevel = "blocker" SeverityImportant SeverityLevel = "important" SeverityMinor SeverityLevel = "minor" )
func (SeverityLevel) IsValid ¶ added in v0.0.5
func (s SeverityLevel) IsValid() bool
IsValid reports whether s is a known SeverityLevel value.
type SignalTopic ¶ added in v0.0.5
type SignalTopic string
SignalTopic is the delivery topic of a durable epoch signal. There is exactly one topic per signal name; the workflow's receive loop and every sender reference these constants instead of literals.
const ( SignalAdvancePhase SignalTopic = "advance_phase" SignalSubmitVote SignalTopic = "submit_vote" SignalSliceProgress SignalTopic = "slice_progress" SignalRegisterSession SignalTopic = "register_session" )
Signal topics for epoch-level handlers.
const ( // SignalStartSlice configures the slice execution mode before run. SignalStartSlice SignalTopic = "start_slice" // SignalCompleteSlice provides an external completion override for the slice. SignalCompleteSlice SignalTopic = "complete_slice" )
Signal topics for slice-level handlers — configuring and completing individual implementation slices.
func (SignalTopic) IsValid ¶ added in v0.0.5
func (t SignalTopic) IsValid() bool
IsValid reports whether t is one of the known signal topics.
func (SignalTopic) String ¶ added in v0.0.5
func (t SignalTopic) String() string
String returns the wire-format topic name.
type SliceCompleteSignal ¶ added in v0.0.5
type SliceCompleteSignal struct {
Success bool `json:"success"`
Output string `json:"output,omitempty"`
Error *string `json:"error,omitempty"`
}
SliceCompleteSignal is the payload for the complete_slice signal.
Sent to a slice sub-workflow to override its outcome with an externally reported result. Success is true for a successful completion; Output carries a success message; Error carries the failure reason when Success is false.
type SliceExecutionMode ¶ added in v0.0.5
type SliceExecutionMode string
SliceExecutionMode is the execution strategy a slice sub-workflow uses.
const ( // SliceMock runs the slice as a no-op stub (for testing and dry-runs). SliceMock SliceExecutionMode = "mock" // SliceTmux launches the slice command inside a tmux session. SliceTmux SliceExecutionMode = "tmux" // SliceSubprocess runs the slice command as a direct child process. SliceSubprocess SliceExecutionMode = "subprocess" )
func (SliceExecutionMode) IsValid ¶ added in v0.0.5
func (m SliceExecutionMode) IsValid() bool
IsValid reports whether m is one of the recognised execution modes.
type SliceProgressSignal ¶ added in v0.0.5
type SliceProgressSignal struct {
SliceId string `json:"sliceId"`
LeafTaskId string `json:"leafTaskId"`
StageName string `json:"stageName"`
Completed bool `json:"completed"`
}
SliceProgressSignal is the payload for the slice_progress signal.
Sent by a slice sub-workflow to its parent epoch to report per-leaf-task progress. Completed is true when the leaf task finishes, false for in-progress heartbeat events.
type SliceStartSignal ¶ added in v0.0.5
type SliceStartSignal struct {
Mode SliceExecutionMode `json:"mode"`
Command string `json:"command,omitempty"`
TimeoutSeconds int `json:"timeoutSeconds,omitempty"`
}
SliceStartSignal is the payload for the start_slice signal.
Sent to a slice sub-workflow to configure how it executes before it runs. Mode selects the execution strategy (SliceMock, SliceTmux, or SliceSubprocess); Command is the shell command for the tmux/subprocess strategies; TimeoutSeconds overrides the default start-to-close timeout when non-zero.
type TaskAssignmentState ¶ added in v0.0.5
type TaskAssignmentState struct {
TaskID provenance.TaskID
Slot provenance.AssignmentSlotID
AssignmentID provenance.AssignmentID
Occupant provenance.ActorID
}
TaskAssignmentState is the user-meaningful state of one task assignment.
type TaskAssignmentTransferError ¶ added in v0.0.5
type TaskAssignmentTransferError struct {
Kind TaskAssignmentTransferErrorKind
// contains filtered or unexported fields
}
TaskAssignmentTransferError is an actionable, typed transfer failure. Cause remains available through errors.Is and errors.As without becoming part of the semantic TaskTracker API.
func NewTaskAssignmentTransferError ¶ added in v0.0.5
func NewTaskAssignmentTransferError(kind TaskAssignmentTransferErrorKind, cause error) *TaskAssignmentTransferError
NewTaskAssignmentTransferError constructs a typed semantic transfer failure. It is exported for TaskTracker implementations outside this package.
func (*TaskAssignmentTransferError) Error ¶ added in v0.0.5
func (e *TaskAssignmentTransferError) Error() string
Error explains what failed, why no assignment changed, and how to recover.
func (*TaskAssignmentTransferError) Unwrap ¶ added in v0.0.5
func (e *TaskAssignmentTransferError) Unwrap() error
Unwrap preserves the underlying error classification for errors.Is and errors.As.
type TaskAssignmentTransferErrorKind ¶ added in v0.0.5
type TaskAssignmentTransferErrorKind uint8
TaskAssignmentTransferErrorKind classifies semantic transfer failures without exposing persistence or execution details.
const ( TaskAssignmentTransferInvalidRequest TaskAssignmentTransferErrorKind = iota + 1 TaskAssignmentTransferUnsupportedSlot TaskAssignmentTransferMissingAssignment TaskAssignmentTransferAmbiguousAssignment TaskAssignmentTransferMismatchedAssignment TaskAssignmentTransferStaleAssignment TaskAssignmentTransferReplayConflict )
func (TaskAssignmentTransferErrorKind) String ¶ added in v0.0.5
func (k TaskAssignmentTransferErrorKind) String() string
String returns the stable diagnostic name for a transfer error kind.
type TaskTracker ¶
type TaskTracker interface {
// ─── Embedded: Provenance task reads, edges/labels/comments reads,
// agents (Human/ML/Software), activities, plus the journaled mutation
// binder As(actor, authority) and the global journal surface Journal().
// See provenance.Tracker. ───
provenance.Tracker
// Create mints a new task and journals its birth (status open).
Create(namespace, title, description string, taskType provenance.TaskType, priority provenance.Priority, phase provenance.Phase) (provenance.Task, error)
// Update applies partial metadata (Title/Description/Priority/Phase/Notes)
// to a task as one journaled operation. Status is NOT a metadata field:
// use the lifecycle verbs Start/Stop/Reopen/CloseTask to change status.
Update(id provenance.TaskID, fields provenance.UpdateFields) (provenance.Task, error)
// CloseTask transitions {open,in_progress} → closed, materialising reason.
CloseTask(id provenance.TaskID, reason string) (provenance.Task, error)
// Start transitions open → in_progress through the journaled lifecycle FSM.
Start(id provenance.TaskID) (provenance.Task, error)
// Stop transitions in_progress → open through the journaled lifecycle FSM.
Stop(id provenance.TaskID) (provenance.Task, error)
// Reopen transitions closed → open through the journaled lifecycle FSM.
Reopen(id provenance.TaskID) (provenance.Task, error)
// AddEdge journals a typed edge from sourceId to targetId.
AddEdge(sourceId provenance.TaskID, targetId string, kind provenance.EdgeKind) error
// RemoveEdge journals the removal of the edge from sourceId to targetId.
RemoveEdge(sourceId provenance.TaskID, targetId string, kind provenance.EdgeKind) error
// AddLabel journals attaching a label to a task.
AddLabel(id provenance.TaskID, label string) error
// RemoveLabel journals detaching a label from a task.
RemoveLabel(id provenance.TaskID, label string) error
// AddComment journals a comment on a task authored by authorId.
AddComment(id provenance.TaskID, authorId provenance.AgentID, body string) (provenance.Comment, error)
// TransferTaskAssignment atomically transfers the active assignment in one
// supported task slot to its requested successor. The caller supplies only
// semantic task and actor values; Pasture resolves the current assignment
// internally and returns semantic prior and successor states.
TransferTaskAssignment(ctx context.Context, request TransferTaskAssignmentRequest) (TransferTaskAssignmentResult, error)
// RecordEvent persists a single audit event. Returns an error if the
// underlying store is unavailable or the write fails. The caller
// (typically a durable workflow step) is responsible for retry policy.
//
// PROPOSAL-2 §7.11: workflows call this then immediately call
// AttachContext with ContextEpoch. Free-floating events use other
// ContextKind values (Git/Skill/Session).
//
// Note: callers that need the inserted event_id (so they can attach
// context_edges rows in the same logical step) should prefer
// RecordEventReturningId — it bundles the write + id-recovery in a
// single call, removing the post-write SELECT MAX(id) round-trip the
// S9 free-floating helpers had to do as a workaround.
RecordEvent(ctx context.Context, event AuditEvent) error
// RecordEventReturningId persists a single audit event and returns the
// audit_events.id of the just-inserted row. The implementation reads the
// id from sql.Result.LastInsertId on the SAME INSERT statement that wrote
// the row, so the returned id is race-safe under any level of write
// contention — independent of the D11 "low write contention" deployment
// binding. Returns the new id and a nil error on success; on failure
// returns 0 and an actionable *pasterrors.StructuredError.
//
// This is the canonical RecordEvent entry point for workflow activities
// (PROPOSAL-2 §7.11): RecordTransition and RecordAuditEvent call this
// then immediately call AttachContext(eventId, ContextEpoch, epochId)
// to record the event-to-epoch correlation. Free-floating helpers
// (RecordGitEvent / RecordSkillEvent / RecordSessionEvent) also use it
// in place of the older SELECT MAX(id) workaround that this method
// supersedes (Phase 11 R1-B per finding aura-plugins-d1h6y).
//
// Behaviour for non-SQLite trail backends (e.g. *audit.InMemoryAuditTrail
// used in tests): the returned id is a synthetic per-trail monotonic
// counter — it is NOT a real audit_events row id and MUST NOT be
// persisted across processes. The counter is incremented atomically per
// call so concurrent test goroutines always observe distinct ids,
// matching the SQLite trail's per-statement-LastInsertId guarantee.
// AttachContext on an in-memory trail is a no-op anyway (no
// context_edges table backing it), so the synthetic id is only
// meaningful for AttachContext-relative assertions in unit tests that
// exercise the workflow integration path without paying for a real
// SQLite file.
RecordEventReturningId(ctx context.Context, event AuditEvent) (int64, error)
// QueryEvents returns audit events filtered by epoch and (optionally)
// phase / role. Results are returned in chronological order. epochId
// is required and is always part of the WHERE clause.
//
// Note: this is the legacy v1 query path; new callers should prefer
// Timeline(ctx, ContextEpoch, epochId) which uses the context_edges
// JOIN and works for all ContextKind values, not just epoch.
QueryEvents(ctx context.Context, epochId string, phase *PhaseId, role *string) ([]AuditEvent, error)
// RecordSessionEntries persists a batch of SessionEntry records
// atomically (single transaction). Nil or empty slices are no-ops.
RecordSessionEntries(ctx context.Context, entries []SessionEntry) error
// QuerySessionEntries returns all session entries for sessionId in
// insertion order. Returns an empty (non-nil) slice when no entries
// exist for sessionId.
QuerySessionEntries(ctx context.Context, sessionId string) ([]SessionEntry, error)
// SetAgentCategories upserts the (automaton, pasture-role) pair for
// the given agent into pasture_agent_categories. Idempotent: a second
// call with the same id replaces the row. Both AutomatonRole and
// PastureRole MUST be valid enum values (see IsValid); a nil/zero
// value is permitted and stored as the literal "None".
//
// Returns *pasterrors.StructuredError{Category: CategoryStorage} on
// write failure, or {Category: CategoryValidation} if either enum
// value is unknown.
SetAgentCategories(id provenance.AgentID, automaton AutomatonRole, pastureRole PastureRole) error
// AgentCategories returns the (automaton, pasture-role) pair stored
// for id. Returns ("None", "None", nil) if no row exists for id.
AgentCategories(id provenance.AgentID) (AutomatonRole, PastureRole, error)
// AttachContext adds a row to context_edges binding eventId to the
// (kind, contextId) pair. The (event_id, context_kind, context_id)
// triple is the BCNF composite primary key — duplicate inserts are
// idempotent (returns nil; the existing row is preserved).
//
// kind MUST be a valid ContextKind (kind.IsValid()); contextId MUST
// be non-empty. Validation failures return CategoryValidation.
AttachContext(ctx context.Context, eventId int64, kind ContextKind, contextId string) error
// EventContexts returns the typed contexts attached to eventId, in
// insertion order. Returns an empty (non-nil) slice when no edges
// exist for eventId.
EventContexts(ctx context.Context, eventId int64) ([]Context, error)
// Timeline returns all events whose context_edges row matches the
// (kind, contextId) pair, in chronological order. The intended usage:
//
// events := tracker.Timeline(ctx, ContextEpoch, epochId)
// events := tracker.Timeline(ctx, ContextGit, "<sha>")
//
// A nil/empty contextId returns an empty slice (no error).
Timeline(ctx context.Context, kind ContextKind, contextId string) ([]AuditEvent, error)
// Close releases all resources held by the tracker. It is safe to call
// Close multiple times; the second and subsequent calls return nil.
//
// Note: provenance.Tracker also declares Close(), and the embedded
// method satisfies this interface requirement; implementations MUST
// however ensure both subsystems (the provenance.Tracker AND the
// underlying audit.Trail's *sql.DB) are closed exactly once.
Close() error
}
TaskTracker is the unified Pasture workflow-record façade. Implementations wrap a provenance.Tracker (task CRUD, edges, labels, comments, agents, activities) and an audit.Trail (event recording, query, session entries), both opened against the same SQLite file at ~/.local/share/pasture/pasture.db.
The interface adds 7 pasture-only methods on top of the 28 + 4 inherited:
- Agent categorisation (R8): SetAgentCategories, AgentCategories
- Context attachment (R9): AttachContext, EventContexts, Timeline
- Task assignment: TransferTaskAssignment
- Lifecycle: Close (closes both wrapped subsystems exactly once)
The constructor OpenTaskTracker is the supported way to obtain an instance; see its doc comment for error semantics. Callers MUST call Close on the returned tracker.
All methods are safe for concurrent use; the SQLite file is opened in WAL mode with a busy_timeout taken from the shared timeout profile. The cross-subsystem race test (BLOCKER B3) in internal/tasks proves this.
func OpenTaskTracker ¶
func OpenTaskTracker(dbPath string) (TaskTracker, error)
OpenTaskTracker opens the unified SQLite database at dbPath and returns a wrapped TaskTracker. See the OpenTaskTracker var doc comment above for the full contract (errors, side effects, lifecycle, wiring requirement).
If the implementation has not been registered (internal/tasks not imported), this function returns a descriptive error. Call MustHaveImpl() at startup to catch this condition at init time rather than at the first call site.
type TransferTaskAssignmentRequest ¶ added in v0.0.5
type TransferTaskAssignmentRequest struct {
TaskID provenance.TaskID
Slot provenance.AssignmentSlotID
NextAssignmentID provenance.AssignmentID
ActorID provenance.ActorID
NextOccupant provenance.ActorID
}
TransferTaskAssignmentRequest describes a semantic task-assignment transfer. It intentionally contains no persistence identity or execution mechanism.
type TransferTaskAssignmentResult ¶ added in v0.0.5
type TransferTaskAssignmentResult struct {
Previous TaskAssignmentState
Next TaskAssignmentState
Replayed bool
}
TransferTaskAssignmentResult reports the assignment state before and after a transfer. Replayed is true when the same semantic request was already applied.
type TransitionError ¶ added in v0.0.5
type TransitionError struct {
Violations []string
}
TransitionError is returned by Advance when a proposed transition is invalid. Violations is always non-empty when returned.
func (*TransitionError) Error ¶ added in v0.0.5
func (e *TransitionError) Error() string
type TransitionRecord ¶ added in v0.0.5
type TransitionRecord struct {
FromPhase PhaseId `json:"fromPhase"`
ToPhase PhaseId `json:"toPhase"`
Timestamp time.Time `json:"timestamp"`
TriggeredBy string `json:"triggeredBy"`
ConditionMet string `json:"conditionMet"`
Success bool `json:"success"`
}
TransitionRecord is an immutable audit entry for a single phase transition.
Success is true for a completed phase advance, false for a failed attempt (e.g. constraint violation). All programmatic success/failure checks MUST use this boolean field, not any string prefix in ConditionMet.