Documentation
¶
Index ¶
- Constants
- Variables
- func AddLabelToIssue(db *sql.DB, issueID int, labelName, color string, author string) error
- func AddLabelsToIssue(db *sql.DB, issueID int, labelNames []string, color string, author string) error
- func AddRunIssue(db *sql.DB, runID, issueID int) error
- func AdvanceSagaTx(tx *sql.Tx, id int, from, to string, nowMS int64) error
- func AllProjectPrefixes(conn *sql.DB) ([]string, error)
- func AttachFiles(db *sql.DB, issueID int, filePaths []string, changedBy string) error
- func AuthorizeLeaseMutation(tx *sql.Tx, id int, token string, nowMS int64) error
- func AuthorizeStepRead(db *sql.DB, id int, token string, nowMS int64) error
- func AuthorizeStepTx(tx *sql.Tx, id int, token string, nowMS int64) (*model.Lease, error)
- func AutoRegisterEnabledTx(tx *sql.Tx, projectID int) (bool, error)
- func BindRunIssueTx(tx *sql.Tx, ri *RunIssue) error
- func BreachRunBudgetTx(tx *sql.Tx, runID int, reason string, nowMS int64) (bool, error)
- func BudgetDefaultTx(tx *sql.Tx, projectID int) (float64, error)
- func BudgetUnitTx(tx *sql.Tx, projectID int) (string, error)
- func CacheRunFloorTx(tx *sql.Tx, runID int, floor float64) error
- func CascadeDeleteIssue(db *sql.DB, id int) error
- func CheckAndBumpVersion(tx *sql.Tx, table string, id int, ifVersion *int) error
- func ClaimIssue(db *sql.DB, id int, owner string, ttlMS int64, nowMS int64) (token string, lease *model.Lease, err error)
- func ClaimStep(db *sql.DB, id int, owner string, ttlMS, nowMS int64) (string, *model.Lease, error)
- func ClaimStepTx(tx *sql.Tx, id int, owner string, ttlMS, nowMS int64) (token string, lease *model.Lease, err error)
- func ClearAllData(db *sql.DB) error
- func ClearAllDataTx(tx *sql.Tx) error
- func ClearProjectDataTx(tx *sql.Tx, projectID int) error
- func ClearRunBreachTx(tx *sql.Tx, runID int, newReason string, nowMS int64) error
- func ClearStaleBudgetReasonTx(tx *sql.Tx, runID int, newReason string, nowMS int64) error
- func ClearStepStartTx(tx *sql.Tx, id int) error
- func CloseDispatchTx(tx *sql.Tx, id int, status, reason string, nowMS int64) (bool, error)
- func CloseOpenProposalsTx(tx *sql.Tx, ids []int, reason string) (int, error)
- func CloseProposal(db *sql.DB, id int, reason string) error
- func CommitProposal(db *sql.DB, id int, outcome string, escalationReason string) error
- func CountActivity(db *sql.DB, issueID int) (int, error)
- func CountByPriority(db *sql.DB, projectID int) (map[string]int, error)
- func CountByStatus(db *sql.DB, projectID int) (map[string]int, error)
- func CountIssues(db *sql.DB, projectID int) (int, error)
- func CountRootIssues(db *sql.DB, projectID int) (int, error)
- func CountSteps(db *sql.DB, runID int) (int, error)
- func CreateComment(db *sql.DB, comment *model.Comment) (int, error)
- func CreateCommentIdempotent(db *sql.DB, comment *model.Comment, idempotencyKey string) (int, error)
- func CreateDoc(db *sql.DB, doc *model.Doc) (int, error)
- func CreateDocComment(db *sql.DB, c *model.DocComment) (int, error)
- func CreateDocCommentIdempotent(db *sql.DB, c *model.DocComment, idempotencyKey string) (int, error)
- func CreateDocIdempotent(db *sql.DB, doc *model.Doc, idempotencyKey string) (int, error)
- func CreateIssue(db *sql.DB, issue *model.Issue, labels []string, files []string) (int, error)
- func CreateIssueIdempotent(db *sql.DB, issue *model.Issue, labels []string, files []string, ...) (int, error)
- func CreateProposal(db *sql.DB, p *model.Proposal) (int, error)
- func CreateProposalIdempotent(db *sql.DB, p *model.Proposal, idempotencyKey string) (int, error)
- func CreateRelation(db *sql.DB, rel *model.Relation) (int, error)
- func DefaultProjectIDOr(id int) int
- func DeleteDoc(db *sql.DB, id int, cascade bool) error
- func DeleteIssue(db *sql.DB, id int) error
- func DeleteLabel(db *sql.DB, labelID int, name, author string) ([]int, error)
- func DeleteProject(conn *sql.DB, id int) error
- func DeleteRelation(db *sql.DB, sourceID, targetID int, relType string) error
- func DeprecateWorkflow(db *sql.DB, projectID int, name string, version int, nowMS int64) (*model.Workflow, error)
- func DerivePrefix(name string) string
- func DetachFiles(db *sql.DB, issueID int, filePaths []string, changedBy string) error
- func DispatchGraceTx(tx *sql.Tx, projectID int) (time.Duration, error)
- func DispatchTTLTx(tx *sql.Tx, projectID int) (time.Duration, error)
- func EnsureProject(conn *sql.DB, identity, name string, nowMS int64) (int, error)
- func EnsureProjectCreated(conn *sql.DB, identity, name string, nowMS int64) (int, bool, error)
- func EventsRetain(db *sql.DB) (time.Duration, error)
- func GetActivity(db *sql.DB, issueID int, limit int) ([]model.Activity, error)
- func GetAllDirectionalRelations(db *sql.DB) ([]model.Relation, error)
- func GetAllRelations(db *sql.DB, projectID int) ([]model.Relation, error)
- func GetBatchSubIssueProgress(conn *sql.DB, parentIDs []int) (map[int][2]int, error)
- func GetComment(db *sql.DB, id int) (*model.Comment, error)
- func GetDoc(db *sql.DB, id int) (*model.Doc, error)
- func GetDocComment(db *sql.DB, id int) (*model.DocComment, error)
- func GetDocIssues(db *sql.DB, docID int) ([]int, error)
- func GetDocProposals(db *sql.DB, docID int) ([]int, error)
- func GetDocRevision(db *sql.DB, docID, rev int) (*model.DocRevision, error)
- func GetIssue(db *sql.DB, id int) (*model.Issue, error)
- func GetIssueDocs(db *sql.DB, issueID int) ([]int, error)
- func GetIssueFiles(db *sql.DB, issueID int) ([]string, error)
- func GetIssueLabelObjects(db *sql.DB, issueID int) ([]*model.Label, error)
- func GetIssueLabels(db *sql.DB, issueID int) ([]string, error)
- func GetIssueLease(db *sql.DB, id int) (*model.Lease, error)
- func GetIssueProposals(db *sql.DB, issueID int) ([]model.Proposal, error)
- func GetIssueRelations(db *sql.DB, issueID int) ([]model.Relation, error)
- func GetIssuesByIDs(db *sql.DB, ids []int) (map[int]*model.Issue, error)
- func GetLabelByName(db *sql.DB, projectID int, name string) (*model.LabelWithCount, error)
- func GetProject(conn *sql.DB, id int) (*model.Project, error)
- func GetProposal(db *sql.DB, id int) (*model.Proposal, error)
- func GetProposalDocs(db *sql.DB, proposalID int) ([]int, error)
- func GetProposalIssues(db *sql.DB, proposalID int) ([]int, error)
- func GetProposalVotes(db *sql.DB, proposalID int) ([]*model.Vote, error)
- func GetRun(db *sql.DB, id int) (*model.Run, error)
- func GetRunTx(tx *sql.Tx, id int) (*model.Run, error)
- func GetSchema(db *sql.DB, projectID int, name string, version int) (*model.Schema, error)
- func GetSchemaTx(tx *sql.Tx, projectID int, name string, version int) (*model.Schema, error)
- func GetStepLease(db *sql.DB, id int) (*model.Lease, error)
- func GetSubIssueProgress(db *sql.DB, parentID int) (int, int, error)
- func GetSubIssueTree(db *sql.DB, parentID int) ([]*model.Issue, error)
- func GetSubIssues(db *sql.DB, parentID int) ([]*model.Issue, error)
- func GetVersion(db *sql.DB, table string, id int) (int, error)
- func GetWorkflow(db *sql.DB, projectID int, name string, version int) (*model.Workflow, error)
- func GrantLoopTx(tx *sql.Tx, runID, issueID int) (int, error)
- func HasGateResult(conn *sql.DB, stepID int, gate string) (bool, error)
- func HeartbeatIssue(db *sql.DB, id int, token string, ttlMS int64, nowMS int64) (*model.Lease, error)
- func HeartbeatStep(db *sql.DB, id int, token string, ttlMS, nowMS int64) (*model.Lease, error)
- func HydrateDocs(db *sql.DB, issues []*model.Issue) error
- func HydrateFiles(db *sql.DB, issues []*model.Issue) error
- func HydrateLabels(db *sql.DB, issues []*model.Issue) error
- func HydrateLinkedIssues(db *sql.DB, docIDs []int) (map[int][]model.IssueRef, error)
- func IdempotencyKeyOf(db *sql.DB, scope string, entityID int) (string, bool, error)
- func IncrementLoopCountTx(tx *sql.Tx, runID, issueID int) (int, error)
- func Initialize(db *sql.DB) error
- func InsertActionResultTx(tx *sql.Tx, r ActionResultRow) error
- func InsertActivityWithID(tx *sql.Tx, a *model.Activity) (bool, error)
- func InsertArtifactTx(tx *sql.Tx, a Artifact, nowMS int64) (int, error)
- func InsertCommentWithID(tx *sql.Tx, comment *model.Comment) (bool, error)
- func InsertDispatchRowTx(tx *sql.Tx, dispatchID int, row DispatchRow) error
- func InsertDispatchTx(tx *sql.Tx, runID int, openedSeq, expiresMS, nowMS int64) (int, error)
- func InsertDocCommentWithID(tx *sql.Tx, c *model.DocComment) (bool, error)
- func InsertDocIssueLink(tx *sql.Tx, docID, issueID int, createdAt string) (bool, error)
- func InsertDocRevisionWithID(tx *sql.Tx, r *model.DocRevision) (bool, error)
- func InsertDocWithID(tx *sql.Tx, doc *model.Doc) (bool, error)
- func InsertEngineComment(tx *sql.Tx, issueID int, body string, nowMS int64) (int, error)
- func InsertFenceTx(tx *sql.Tx, f RunFence) error
- func InsertGapIssueTx(tx *sql.Tx, projectID int, title, description string, relatedIssueID int) (int, error)
- func InsertGateResultTx(tx *sql.Tx, r GateResultRow) error
- func InsertIssueFileMapping(tx *sql.Tx, issueID int, filePath string) (bool, error)
- func InsertIssueLabelMapping(tx *sql.Tx, issueID, labelID int) (bool, error)
- func InsertIssueWithID(tx *sql.Tx, issue *model.Issue) (bool, error)
- func InsertLabelWithID(tx *sql.Tx, label *model.Label) (bool, error)
- func InsertNoteTx(tx *sql.Tx, issueID int, body, author, now string) (int, error)
- func InsertPinTx(tx *sql.Tx, p Pin) error
- func InsertProposalDocLink(tx *sql.Tx, proposalID, docID int, createdAt string) (bool, error)
- func InsertProposalIssueLink(tx *sql.Tx, proposalID, issueID int) (bool, error)
- func InsertProposalWithID(tx *sql.Tx, p *model.Proposal) (bool, error)
- func InsertReapAckTx(tx *sql.Tx, ack ReapAck, nowMS int64) error
- func InsertRelationWithID(tx *sql.Tx, rel *model.Relation) (bool, error)
- func InsertRun(db *sql.DB, projectID int, request string, budget float64, nowMS int64) (*model.Run, error)
- func InsertRunWithContext(db *sql.DB, projectID int, request string, budget float64, nowMS int64, ...) (*model.Run, error)
- func InsertRunWithContextIdempotent(db *sql.DB, projectID int, request string, budget float64, nowMS int64, ...) (*model.Run, error)
- func InsertSchema(db *sql.DB, s *model.Schema, nowMS int64) (stored *model.Schema, created bool, err error)
- func InsertSchemaTx(tx *sql.Tx, s *model.Schema, nowMS int64) (stored *model.Schema, created bool, err error)
- func InsertStepInputTx(tx *sql.Tx, stepID, position, artifactID int) error
- func InsertStepTx(tx *sql.Tx, s StepRow, nowMS int64) error
- func InsertTrustCacheTx(tx *sql.Tx, runID int, kind, gate, argvSHA256, entryName string, ...) error
- func InsertUsageRowTx(tx *sql.Tx, row UsageRow, nowMS int64) error
- func InsertVoteUsageTx(tx *sql.Tx, voteID int64, unit string, quantity float64, source string, ...) error
- func InsertVoteWithID(tx *sql.Tx, v *model.Vote) (bool, error)
- func InsertWorkflow(db *sql.DB, wf *model.Workflow, nowMS int64) (stored *model.Workflow, created bool, err error)
- func InsertWorkflowTx(tx *sql.Tx, wf *model.Workflow, nowMS int64) (stored *model.Workflow, created bool, err error)
- func IsDescendant(db *sql.DB, issueID, potentialDescendantID int) (bool, error)
- func IssueExists(db *sql.DB, issueID int) (bool, error)
- func IssueOwnerPrefix(conn *sql.DB, issueID int) (string, error)
- func IssueProjectID(db *sql.DB, issueID int) (int, error)
- func IssueScopeGlobs(db *sql.DB, issueID int) (string, error)
- func IssueScopeGlobsTx(tx *sql.Tx, issueID int) (string, error)
- func IssueStepRuns(db *sql.DB, issueID int) ([]int, error)
- func KnownConfigKeys() []string
- func LeaseTTL(db *sql.DB, projectID int, class string) (time.Duration, error)
- func LinkDocIssue(db *sql.DB, docID, issueID int) error
- func LinkProposalDoc(db *sql.DB, proposalID, docID int) error
- func LinkProposalIssue(db *sql.DB, proposalID, issueID int) error
- func ListAllActivity(db *sql.DB, projectID int) ([]*model.Activity, error)
- func ListAllComments(db *sql.DB, projectID int) ([]*model.Comment, error)
- func ListAllDocComments(db *sql.DB, projectID int) ([]*model.DocComment, error)
- func ListAllDocIssueLinks(db *sql.DB, projectID int) ([]model.DocIssueLink, error)
- func ListAllDocRevisions(db *sql.DB, projectID int) ([]*model.DocRevision, error)
- func ListAllDocs(db *sql.DB, projectID int) ([]*model.Doc, error)
- func ListAllIssueFileMappings(db *sql.DB, projectID int) ([]model.IssueFileMapping, error)
- func ListAllIssueLabelMappings(db *sql.DB, projectID int) ([]model.IssueLabelMapping, error)
- func ListAllIssues(db *sql.DB, projectID int) ([]*model.Issue, error)
- func ListAllLabels(db *sql.DB, projectID int) ([]*model.LabelWithCount, error)
- func ListAllLabelsRaw(db *sql.DB, projectID int) ([]*model.Label, error)
- func ListAllProposalDocs(db *sql.DB, projectID int) ([]model.ProposalDocLink, error)
- func ListAllProposalIssues(db *sql.DB, projectID int) ([]model.ProposalIssueLink, error)
- func ListAllProposals(db *sql.DB, projectID int) ([]*model.Proposal, error)
- func ListAllVotes(db *sql.DB, projectID int) ([]*model.Vote, error)
- func ListComments(db *sql.DB, issueID int) ([]*model.Comment, error)
- func ListDocComments(db *sql.DB, docID int) ([]*model.DocComment, error)
- func ListDocRevisions(db *sql.DB, docID int) ([]*model.DocRevision, error)
- func ListDocs(db *sql.DB, opts DocListOptions) ([]*model.Doc, int, error)
- func ListIssues(db *sql.DB, opts ListOptions) ([]*model.Issue, int, error)
- func ListProjects(conn *sql.DB) ([]*model.Project, error)
- func ListProposals(db *sql.DB, projectID int, status string, criticality string, domainTag string, ...) ([]*model.Proposal, int, error)
- func ListRuns(db *sql.DB, opts RunListOptions) ([]*model.Run, int, error)
- func ListSchemas(db *sql.DB, opts SchemaListOptions) ([]*model.Schema, int, error)
- func ListWorkflows(db *sql.DB, opts WorkflowListOptions) ([]*model.Workflow, int, error)
- func LookupIdempotencyKey(db *sql.DB, scope, key string) (int, bool, error)
- func LookupIdempotencyKeyTx(tx *sql.Tx, scope, key string) (int, bool, error)
- func LookupIdempotencyKeysTx(tx *sql.Tx, scope, prefix string) (map[string]int, error)
- func LookupProject(conn *sql.DB, identity string) (int, bool, error)
- func LoopGrantsTx(tx *sql.Tx, runID, issueID int) (int, error)
- func MarkExpandedTx(tx *sql.Tx, runID, issueID int, nowMS int64) error
- func MarkStepAttemptFailedTx(tx *sql.Tx, id int, nowMS int64) error
- func MarkStepClaimReapedTx(tx *sql.Tx, id int, nowMS int64) error
- func MarkStepUsageRecordedTx(tx *sql.Tx, stepID int) error
- func Migrate(db *sql.DB) error
- func MoveIssueProject(conn *sql.DB, issueID, targetProjectID int, author string, nowMS int64) ([]int, error)
- func NextActionOrdinalTx(tx *sql.Tx, stepID int, action string) (int, error)
- func NextGateOrdinal(conn *sql.DB, stepID int, gate string) (int, error)
- func Open(dbPath string) (*sql.DB, error)
- func OpenReader(dbPath string) (*sql.DB, error)
- func OrphanSubIssues(db *sql.DB, parentID int, author string) error
- func PrefixHolder(conn *sql.DB, prefix string, exclude int) (int, error)
- func ProjectPrefix(conn *sql.DB, id int) (string, error)
- func ProjectRefCounts(conn *sql.DB, id int) (map[string]int, error)
- func ProposalStatusesTx(tx *sql.Tx, ids []int) (map[int]model.ProposalStatus, error)
- func ReapStepTx(tx *sql.Tx, id int, nowMS int64) error
- func RecordActivity(ex execer, issueID int, field, oldVal, newVal, changedBy string) error
- func RecordIdempotencyKeyTx(tx *sql.Tx, scope, key string, entityID int) error
- func RefreshClaimLeaseTx(tx *sql.Tx, id int, tokenHash string, ttlMS, nowMS int64) (bool, error)
- func RegisteredVoteRules(db *sql.DB, projectID int) ([]string, error)
- func RegisteredVoteRulesTx(tx *sql.Tx, projectID int) ([]string, error)
- func ReleaseIssue(db *sql.DB, id int, token string, nowMS int64) (*model.Lease, error)
- func ReleaseStepLeaseTx(tx *sql.Tx, id int, nowMS int64) error
- func RemoveLabelFromIssue(db *sql.DB, issueID int, labelName string, author string) error
- func RemoveLabelsFromIssue(db *sql.DB, issueID int, labelNames []string, author string) error
- func RemoveRunIssue(db *sql.DB, runID, issueID int) error
- func ReportedUsageTx(tx *sql.Tx, runID int, unit string) (float64, error)
- func ResetStepRetryBudgetTx(tx *sql.Tx, id int, nowMS int64) error
- func RestoreWorkflow(db *sql.DB, projectID int, name string, version int) (*model.Workflow, error)
- func RetireStepTokenTx(tx *sql.Tx, id int) error
- func RunEverDispatchedTx(tx *sql.Tx, runID int) (bool, error)
- func RunPauseOriginTx(tx *sql.Tx, id int) (model.RunPauseOrigin, error)
- func RunProjectID(conn *sql.DB, runID int) (int, error)
- func RunProjectIDTx(tx *sql.Tx, runID int) (int, error)
- func SchemaVersion(db *sql.DB) (int, error)
- func SetConfig(db *sql.DB, projectID int, key, value string) error
- func SetIssueFiles(db *sql.DB, issueID int, filePaths []string, changedBy string) error
- func SetIssueResolutionTx(tx *sql.Tx, issueID int, resolution string) error
- func SetIssueScopeGlobs(db *sql.DB, issueID int, globsJSON string) error
- func SetProjectPrefix(conn *sql.DB, id int, prefix string) error
- func SetRunBudgetTx(tx *sql.Tx, runID int, budget float64, ifVersion *int, nowMS int64) error
- func SetRunPauseOriginTx(tx *sql.Tx, id int, origin model.RunPauseOrigin) error
- func SetRunStatus(db *sql.DB, id int, status model.RunStatus, reason string, nowMS int64) error
- func SetRunStatusTx(tx *sql.Tx, id int, status model.RunStatus, reason string, nowMS int64) error
- func SetStepGateTrailTx(tx *sql.Tx, id int, trail string, nowMS int64) error
- func SetStepMetadataTx(tx *sql.Tx, id int, metadata string, nowMS int64) error
- func SetStepRoutingTx(tx *sql.Tx, id int, routing, status string, nowMS int64) error
- func SetStepStatusTx(tx *sql.Tx, id int, status string, nowMS, activityMS int64) error
- func SortedUnits[V any](byUnit map[string]V) []string
- func SplitNameList(value string) []string
- func StartStepTx(tx *sql.Tx, id int, nowMS int64) error
- func StepAttemptsFor(db *sql.DB, runID int) (map[int]int, error)
- func StepOffScheduler(status string) bool
- func StepStatusCounts(db *sql.DB, runID int) ([]model.StatusCount, error)
- func StepTerminal(status string) bool
- func UnlinkDocIssue(db *sql.DB, docID, issueID int) error
- func UnlinkProposalDoc(db *sql.DB, proposalID, docID int) error
- func UnlinkProposalIssue(db *sql.DB, proposalID, issueID int) error
- func UpdateDoc(db *sql.DB, id int, upd DocUpdate) (int, error)
- func UpdateIssue(db *sql.DB, id int, updates map[string]interface{}, changedBy string) error
- func UpdateIssueCAS(db *sql.DB, id int, updates map[string]interface{}, changedBy string, ...) error
- func UpdateIssueCASLease(db *sql.DB, id int, updates map[string]interface{}, changedBy string, ...) error
- func UpdateIssueCASNote(db *sql.DB, id int, updates map[string]interface{}, changedBy string, ...) error
- func UsageBudgetUnitTx(tx *sql.Tx, projectID int) (string, error)
- func ValidateConfigValue(spec ConfigSpec, value string) error
- func ValidateNameList(noun, value string) error
- func ValidateUnitName(name string) error
- func VoteRuleCriticalityKey(rule string) string
- func VoteRuleExists(db *sql.DB, projectID int, rule string) (bool, error)
- func VoteRuleExistsTx(tx *sql.Tx, projectID int, rule string) (bool, error)
- func VoteRuleSetElsewhere(db *sql.DB, projectID int, rule string) (projects int, value string, err error)
- func VoteRuleSetElsewhereTx(tx *sql.Tx, projectID int, rule string) (projects int, value string, err error)
- func VoteRuleThresholdKey(rule string) string
- type AckReapResult
- type ActionResultRow
- type Artifact
- type CastVoteResult
- type ConfigEntry
- type ConfigSpec
- type ConfigValueKind
- type CycleError
- type Dispatch
- type DispatchRow
- type DocListOptions
- type DocSummary
- type DocUpdate
- type GateResultRow
- type ListOptions
- type MetadataKeyRollup
- type MetadataValueCount
- type Pin
- type ReapAck
- type ResultTrailRow
- type RunBudgetFacts
- type RunContext
- type RunFence
- type RunIssue
- type RunListOptions
- type SchemaListOptions
- type Step
- type StepRow
- type StepUsageRow
- type UnitTotal
- type UsageRow
- type VerdictCount
- type VoteUsageCoverage
- type WorkflowListOptions
Constants ¶
const ( ActionVerdictPass = "pass" ActionVerdictFail = "fail" ActionVerdictUnmatched = "unmatched" )
Action verdicts as stored, mirroring the gate vocabulary exactly. `unmatched` is a FIRST-CLASS outcome: the action name matched no trust entry, so nothing ran — which is neither a pass nor an error (§6.2 A3).
const ( TrustKindGate = "gate" TrustKindAction = "action" )
Trust-cache kinds (§6.3). `trust_cache` gains a `kind` column rather than a second table: the table answers "what did this run consider trusted, and when", and splitting that answer by the SHAPE OF THE CALLER would make the one question two queries.
const ( // DispatchOpen is the state `idx_dispatches_one_open` admits one of per run. DispatchOpen = "open" // DispatchClosed is `dispatch close`: the relay reconciled its batch. DispatchClosed = "closed" // DispatchAbandoned is `dispatch abandon` or the TTL's lazy auto-abandon: // the batch was given up on rather than reconciled. DispatchAbandoned = "abandoned" )
Dispatch statuses. A dispatch is `open` until exactly one of the two closing paths moves it, and the status is what the partial unique index keys on — so these strings are load-bearing rather than decorative.
const ( // CloseReasonTTL is P14's lazy auto-abandon: the manifest outlived // `dispatch.ttl` and `next` retired it. CloseReasonTTL = "ttl" // CloseReasonReconciled is the ordinary `dispatch close` (P18). CloseReasonReconciled = "reconciled" // CloseReasonAcceptedMissingUsage is P19: closed over missing-usage // discrepancies, with the acceptance RECORDED rather than implied. CloseReasonAcceptedMissingUsage = "accepted-missing-usage" // CloseReasonOperator is `dispatch abandon` driven by a person (P21). CloseReasonOperator = "abandoned" )
Close reasons core writes. They are a small closed vocabulary because `run report` and `events list` render them, and a free-text reason from the engine's own paths would make the feed inconsistent with itself. A caller's `--reason` on `abandon` rides in the EVENT's data, not here (P21).
const ( // AckByGuardSpawn is `guard spawn --ack-reap`, group 3's entry point. AckByGuardSpawn = "guard-spawn" // AckByDispatchOpen is `dispatch open --ack-reap`, this group's. AckByDispatchOpen = "dispatch-open" )
Acknowledgers — A8's closed pair. The value records the acknowledging VERB and never a user identity, because core has no identity model.
const ( // KeyLeaseTTLDefault is the fallback lease TTL for any class without its // own entry. KeyLeaseTTLDefault = "lease.ttl.default" // KeyLeaseTTLPrefix is the per-class TTL namespace: lease.ttl.<class>. // The class is an opaque string — core never interprets its value // (engine-spec.md §11.1 [limits]). KeyLeaseTTLPrefix = "lease.ttl." // KeyAttemptMax caps retries per entity (engine-spec.md §11.1 // max_attempts). KeyAttemptMax = "attempt.max" // KeyBudgetDefault is the per-run budget cap; 0 means unlimited. // Enforcement lands with runs; the key exists now so the default is // pinned before the verbs that read it. KeyBudgetDefault = "budget.default" // KeyBudgetUnit names WHICH recorded usage unit the run cap counts // (docs/tdd/runs-dispatch.md §4.5 B16). Empty — the default and the only // value core ships — means `reported` is 0 and the cap rests on the // declared-cost floor alone. // // A config key rather than a workflow field because the cap is a run-level // control (engine-spec §11.3), so the unit it counts is run-level too. // Putting it on a step would let two steps in one run disagree about what // the run's budget means. KeyBudgetUnit = "budget.unit" // KeyUsageBudgetDefault is the default cap over MEASURED usage — the // second budget dimension (DKT-238); 0 means unlimited. // // It is a SEPARATE key from budget.default because the two count different // things and are not commensurable. `budget.default` counts declared // expected costs, which is a discipline over how much WORK a run schedules; // this counts what the ledger recorded, which is a bound on what the work // actually consumed. A run wants both, and one number cannot say both. KeyUsageBudgetDefault = "budget.usage.default" // KeyUsageBudgetUnit names WHICH recorded usage unit the measured cap // counts. Empty — the default — leaves the dimension DORMANT: a cap with // no unit has nothing to count, so it enforces nothing. // // Deliberately distinct from budget.unit. That key names the unit whose // reported total may RAISE the declared-cost spend (B16's max); this one // names the unit the measured cap is taken over. Sharing one key would // make an operator setting a token unit for one dimension silently arm the // other. KeyUsageBudgetUnit = "budget.usage.unit" // KeyDispatchTTL is how long a dispatch manifest stays open before `next` // auto-abandons it (docs/tdd/runs-dispatch.md §4.11, §5.5 P12). // // It is engine-side rather than instance policy for the reason §11.3 gives // for every other enforced number: the expiry is what keeps a crashed relay // from wedging a run, and a bound only a live relay could set would be a // bound the crash case never gets. KeyDispatchTTL = "dispatch.ttl" // KeyDispatchGrace is how long a claimed step may go unrecorded before it // counts as a dispatch discrepancy (§5.8 D1). // // It is DELIBERATELY a different key from `lease.ttl.default` even though // both measure silence. A lease TTL decides when the engine takes work back; // this decides when a relay's batch is judged unreconciled. Sharing one key // would make an operator lengthening leases silently stop `next` refusing. KeyDispatchGrace = "dispatch.grace" // KeyContextWarnBytes and KeyContextErrorBytes are the context-size caps. KeyContextWarnBytes = "context.warn_bytes" KeyContextErrorBytes = "context.error_bytes" // KeyEventsRetain is the RETENTION WINDOW: how long an event must have // existed before `events prune` may delete it // (docs/tdd/events-follow.md §5.3 P12). // // It is engine-spec §3's "artifact-retention boundary", implemented as the // one window a future artifact GC would also read. §3 lists prune and // "artifact GC per run-retention config" as one lifecycle, and no artifact // GC ships at stage 7 — so read literally there would be no boundary to // cross. Implementing it as this window means the GC inherits a boundary // already enforced rather than one retrofitted, and the reading can only // ever REFUSE MORE than the literal one would. // // THE READING IS RECORDED AS AN AMENDMENT rather than made silently, // including the objection a reviewer would raise — that a key spelled // `events.retain` governing only events is not what "artifact-retention" // names. // // The default is "0", which means RETAIN EVERYTHING: prune refuses every // event until an operator states a policy. That is the dormant posture — // Docket deletes nothing an operator did not ask it to delete. KeyEventsRetain = "events.retain" // KeyVoteRulePrefix is the named-threshold-configuration namespace: // vote.rule.<name>.threshold and vote.rule.<name>.criticality // (gates-trust §8.3). // // A workflow's `type="vote"` step names a rule rather than passing flags, // because a step cannot pass flags. The <name> is an OPAQUE string exactly // as lease.ttl.<class>'s class is, and this reuses the config machinery // rather than adding a table: a rule "exists" iff its `.threshold` is set. KeyVoteRulePrefix = "vote.rule." // KeyVoteRuleThresholdSuffix and KeyVoteRuleCriticalitySuffix complete a // rule's two keys. KeyVoteRuleThresholdSuffix = ".threshold" KeyVoteRuleCriticalitySuffix = ".criticality" // KeyVoteHoldRule and KeyVoteHoldVoters configure how a MATERIALIZED HELD // step is decided: by one operator (the default) or by a tally. // // A held step is the one step in a run no author declared — the engine // mints it when a `hold_spread` trips, so its `voters` and `vote_rule` // cannot come from a `[[step]]` table the way a declared vote step's do. // These two keys are where an instance says them instead, and they sit in // the `vote.` family beside `vote.rule.<name>` because that is the same // subject: how a vote is tallied and who casts it. // // BOTH ARE EMPTY BY DEFAULT, and empty means EXACTLY the prior behavior — // held steps are minted `human` and one operator approves or rejects them. // A tally is something an instance opts into, never something core assumes, // for the same reason core ships no default threshold: a roster nobody // chose is not a roster. // // They are ONE PAIR rather than per-workflow settings because a hold is the // engine's own question about its own computation. It is asked identically // whichever pipeline held, so who answers it is a project-level policy. KeyVoteHoldRule = "vote.hold.rule" KeyVoteHoldVoters = "vote.hold.voters" // KeyAutoRegister toggles §9's auto-registration: whether `run activate` // registers a workflow/schema it finds in an instance-config root // (~/.docket/config, <repo>/.docket/config) on its own, or leaves that to // an explicit `workflow register` / `schema register`. // // DEFAULT TRUE — auto-registration is the zero-touch behavior §9 exists // for, and an operator who wants it off states that, rather than every // operator who wants it (the common case) opting in project by project. // // Project-scoped, like any other config key (v12): `--global` sets the // store-wide default every project without its own override falls back // to, and a bare `config set` overrides ONE project — the two knobs the // requirement asks for ("a given project vs all projects") are exactly // GetConfig's existing project-override-then-store-wide resolution, not a // second mechanism. // // It gates ONLY registration, not the pinning half of the same scan // (contracts/, fragments/, policy.toml). Pinning has no version to adopt — // it is "read the current bytes", which is what a repo with this off // still needs to render a step's `packet`. Turning registration off and // pinning off together would leave a project unable to activate at all // without hand-supplying every `--pin` the corpus already offers, which is // not what "I don't want silent version upgrades" is asking for. KeyAutoRegister = "registration.auto" )
Engine configuration keys (engine-spec.md §1: "engine defaults: lease TTLs per class, attempt caps, budget default, context caps"). Values live in the meta table, prefixed so they cannot collide with schema_version or any other internal key.
const ( GateVerdictPass = "pass" GateVerdictFail = "fail" GateVerdictUnmatched = "unmatched" // GateVerdictSkipped: the gate's tree was gone at spawn time, so nothing // ran and nothing was measured (DKT-169). Not a pass for routing. GateVerdictSkipped = "skipped" )
Gate verdicts as stored. `unmatched` is a FIRST-CLASS outcome, not an error and not a pass: the command was not trusted, so it did not run (§6.2 N1-N4).
const ( ScopeIssueCreate = "issue.create" ScopeDocCreate = "doc.create" ScopeVoteCreate = "vote.create" ScopeIssueComment = "issue.comment.add" ScopeDocComment = "doc.comment.add" ScopeRunStart = "run.start" )
Idempotency scopes. One per create verb, so the same key used on two different verbs does not collide.
const ( PinKindWorkflow = "workflow" PinKindFile = "file" // PinKindSchema pins a registered payload schema by its source hash // (docs/tdd/payloads-thresholds.md §4.7 P1). A schema is a registered // object, and §2's pinning clause is "registered objects by version" — // therefore it pins. What a run validates its payloads against is a fact // about the run, not about the table's current contents. PinKindSchema = "schema" )
Pin kinds (TDD §5.1). `workflow` pins a registered `name@version` by its source hash; `file` pins an arbitrary operator-supplied path by its content hash — "how the reference instance pins its contracts, fragments, and policy without core knowing what they are" (engine-spec §2). Core reads bytes, hashes them, stores the path, and never opens the content again.
const ( StepPending = "pending" StepClaimed = "claimed" StepRunning = "running" StepGated = "gated" StepDone = "done" StepWaitingHuman = "waiting-human" StepSkipped = "skipped" StepSuperseded = "superseded" StepFailedRouted = "failed-routed" // StepReady is the COMPUTED status a read verb renders when the §6.3 // predicate holds. It is a rendering value, never a column value, and // TestReadyIsNeverPersisted asserts nothing writes it. StepReady = "ready" // StepStaged is the second COMPUTED status, rendered only on offer rows // (`next`, `dispatch open`): the step is NOT ready — its `after` // predecessors have not all recorded — but every unsatisfied predecessor // is itself in the same offer at a lower stage, so a dispatcher that runs // the offer's stages in order will find it claimable by the time its stage // begins. Like `ready` it is a rendering value, never a column value, and // never an answer `step show` gives (stagedness is a property of one // offer's membership, not of the step). A `staged` row is NOT claimable // yet: `claim` re-checks the predicate and refuses until the predecessors // actually record, which is what keeps a stage-skipping dispatcher safe. StepStaged = "staged" )
The nine PERSISTED step statuses (TDD §6.2). `ready` is the tenth member of the MACHINE and is deliberately absent here: it is computed at read time by the §6.3 predicate and never stored as intent, exactly as v6's effective lease status is. Both numbers are stated in the TDD because the enum's size and the machine's size are different questions.
const ( // SagaRecorded is stage 1's commit: the artifact is in, the token is // retired, status is `gated`. From here the saga is engine-owned and needs // no lease. SagaRecorded = "recorded" // SagaRouting is the last stage's resume point: gates are all recorded and // routing is what remains. SagaRouting = "routing" // SagaHeld is the stage an `aggregate` step enters when `hold_spread` trips // (payloads-thresholds §7.7 H8). Its artifact is recorded and its // `<step>-held` question is open; its routing is DEFERRED until an operator // resolves that question. // // It is a stage rather than a status because "gating the routing step" is a // DEFERRAL OF ROUTING, and the machinery for deferring a saga already // exists. The step's status stays `gated` — non-terminal — so every // downstream successor fails R3 and nothing proceeds: no new status, no // synthetic `after` edge, no second readiness rule. SagaHeld = "held" // SagaGatePrefix prefixes a per-gate resume point, `gate:<name>`. SagaGatePrefix = "gate:" )
Saga stages (TDD §6.8). The value stored in `steps.saga_stage` is the RESUME POINT: the stage that has committed, so the next engine invocation knows which one to run next. NULL means "not in the saga" — either never entered or complete.
const ArtifactMaxBytes = 1 << 20
ArtifactMaxBytes is §3's explicit cap. An artifact over it is a VALIDATION_ERROR naming the size and the cap — a refusal rather than a truncation, because a silently truncated artifact is one a downstream step consumes as if it were whole.
const DefaultProjectID = 1
DefaultProjectID is the row every pre-v12 datum backfilled to, and the row an invocation with no resolvable identity falls back to.
const EngineAuthor = "docket-engine"
EngineAuthor is the fixed Author value for comments the engine writes itself (step claimed, gate/vote opened, step failed, issue completed or abandoned), so they read as machine-authored without a schema column to mark them.
const IssueResolutionAbandoned = model.ResolutionAbandoned
IssueResolutionAbandoned is the resolution the `abandon-issue` routing and `run abandon --issue` write: the machine stopped working this issue and did not finish it. It is model.ResolutionAbandoned rather than a second literal, so the value the engine writes and the value the renderer tests for cannot drift apart.
const NameMaxBytes = 64
NameMaxBytes caps an opaque name stored in config or recorded in a ledger, per §1.3's security note: such a name is attacker-controlled text that lands in a ledger and in a report. ONE number for every opaque-name validator, so two of them cannot drift apart.
const UnitNameMaxBytes = NameMaxBytes
UnitNameMaxBytes is NameMaxBytes under the name `--usage`'s validator has always called it.
const UnregisteredProjectID = -1
UnregisteredProjectID is the id an invocation resolves to when its identity has NO project row and this invocation is not allowed to create one (DKT-58: a read verb, or an identity that is not a repository).
It is negative so it references nothing: every scoped SELECT returns empty and every scoped INSERT fails its foreign key rather than silently landing in someone else's project. That is the honest answer to "show me this directory's issues" when the directory has no project — the pre-DKT-58 code answered it by MINTING one, and a read that creates permanent state is how the store filled with rows nobody asked for.
It is deliberately NOT DefaultProjectID: falling back to project 1 would show a legacy store's issues under an unrelated repository and, worse, let a write from an unregistered directory land in that project's history.
const UsageSourceReported = "reported"
UsageSourceReported is the only source core writes at completion: a claimant said so. A back-fill supplies its own (engine.UsageSourceBackfilled).
const UsageUnitsMax = 32
UsageUnitsMax is B36's first cap: at most 32 units per report.
The caps exist because `--usage` is ATTACKER-CONTROLLED JSON FROM A CLAIMANT (§1.3) that lands in a ledger and in a report — bytes going to a terminal. A claimant that could write ten thousand units would make every subsequent `run report` on that run unreadable, which is a denial of the verb an operator reaches for when something has gone wrong.
const VoteMetadataMaxBytes = 16 << 10
VoteMetadataMaxBytes caps the encoded size of a vote's opaque KV bag, in the shape ArtifactMaxBytes uses: the limit lives beside the column it protects, so every writer of `votes.metadata` crosses it — `vote cast`, and `import` through InsertVoteWithID — rather than only the one command that happens to parse a flag. The value matches the 16 KiB a step's own metadata bag gets; it is duplicated rather than imported because internal/engine already imports this package.
Variables ¶
var ( // ErrLeaseHeld means a live lease is held by someone else — the claim // race's loser. Surfaced as CONFLICT (exit 4). ErrLeaseHeld = errors.New("lease held") // ErrNotHolder means the caller presented no matching capability: either // the entity is unclaimed, or the token is wrong. Surfaced as AUTH_ERROR // (exit 5). // // The two cases are deliberately not distinguished. "Unclaimed" and "wrong // token" are the same answer to the caller — you do not hold this lease — // and separating them would leak whether a lease exists to a caller // holding no capability. ErrNotHolder = errors.New("not the lease holder") // ErrLeaseExpired means the token is RIGHT but the lease lapsed. Surfaced // as STALE_LEASE (exit 6), which is the entire value of a separate code: a // holder seeing it knows to re-claim (its work may be redone), while // AUTH_ERROR means it never held the lease at all. ErrLeaseExpired = errors.New("lease expired") )
Lease refusal sentinels. Each maps to exactly one CLI error code, per the refusal matrix in docs/tdd/claims-leases.md §4 (engine-spec.md §9 item 3).
var ( ErrSelfRelation = errors.New("self-referential relation") ErrDuplicateRelation = errors.New("duplicate relation") ErrCycleDetected = errors.New("cycle detected") )
Sentinel errors for relation operations.
var ( // ErrSchemaConflict means `name@version` is already registered with // DIFFERENT bytes. Surfaced as CONFLICT (exit 4), naming both hashes. // // Why a conflict and not an overwrite: engine-core §4's pinning property — // "editing a pipeline never changes an in-flight run" — is worth nothing if // the pinned bytes can be swapped underneath the run. A schema decides // whether a worker's payload is ACCEPTED, so a mutable findings@1 means a // run's acceptance criteria change mid-flight. Bump the version. ErrSchemaConflict = errors.New("schema already registered with different content") // ErrSchemaNotFound means no row matches the requested name/version. // Surfaced as NOT_FOUND (exit 2). ErrSchemaNotFound = errors.New("schema not found") )
Schema registration sentinels. They mirror the workflow ones exactly, because the two registries have the same immutability contract (TDD §4.4) and a caller mapping errors to exit codes should not have to learn it twice.
var ( // ErrStepNotFound means no `steps` row matches. NOT_FOUND (exit 2). ErrStepNotFound = errors.New("step not found") // ErrSagaStageMoved means a saga stage's CAS guard matched zero rows: // another engine invocation advanced the saga first (TDD §6.8, "resume is // lazy and idempotent"). It is not a failure — the loser re-reads and // either finds the work done or advances the next stage — so no CLI verb // maps it to an exit code; the saga driver handles it internally. ErrSagaStageMoved = errors.New("saga stage already advanced") )
Step sentinels.
var ( // ErrWorkflowConflict means `name@version` is already registered with // DIFFERENT bytes. Surfaced as CONFLICT (exit 4), naming both hashes. // // A registered name@version is frozen: re-registering identical bytes is // an idempotent success, and re-registering different bytes is this. The // version pinning engine-core §4 requires ("editing a pipeline never // changes an in-flight run") is worth nothing if the pinned bytes can be // swapped underneath a run. ErrWorkflowConflict = errors.New("workflow already registered with different content") // ErrWorkflowNotFound means no row matches the requested name/version. // Surfaced as NOT_FOUND (exit 2). ErrWorkflowNotFound = errors.New("workflow not found") )
Workflow registration sentinels.
var ErrConflict = errors.New("conflict")
ErrConflict is returned when an operation violates a uniqueness or state constraint.
var ErrDispatchAlreadyOpen = errors.New("a dispatch is already open for this run")
ErrDispatchAlreadyOpen is C1's loser — the CONFLICT of P6.
var ErrIssueHasParent = errors.New("issue has a parent")
ErrIssueHasParent refuses a project migration of a non-root issue: sub-issues follow their root, so the root is what migrates (or the issue is reparented first).
var ErrIssueInRun = errors.New("issue belongs to a run")
ErrIssueInRun refuses a project migration of an issue any run holds: the run's snapshots, steps, and events are project-scoped bookkeeping, and an issue migrated from under them would strand every record.
var ErrLabelColorConflict = errors.New("label color conflict")
ErrLabelColorConflict is returned when --color specifies a different color than an existing label already has.
var ErrNoOpenDispatch = errors.New("no dispatch is open")
ErrNoOpenDispatch is returned when a verb that needs an open manifest finds none. It is a sentinel so the CLI maps it once rather than matching a message.
var ErrNotAttached = errors.New("label not attached")
ErrNotAttached is returned when a label is not attached to the specified issue.
var ErrNotFound = errors.New("not found")
ErrNotFound is returned when a requested resource does not exist.
var ErrPrefixTaken = errors.New("prefix already held by another project")
ErrPrefixTaken refuses a prefix another project already holds (DKT-60). The prefix is a project's only discriminator in a listing or an event feed, so two projects sharing one makes every id in the store ambiguous about its owner.
var ErrProjectInUse = errors.New("project still has rows")
ErrProjectInUse refuses the deletion of a project anything still references (DKT-59).
var ErrProjectIsDefault = errors.New("the default project cannot be deleted")
ErrProjectIsDefault refuses the deletion of the default row.
var ( // ErrRunNotFound means no `runs` row matches. NOT_FOUND (exit 2). ErrRunNotFound = errors.New("run not found") )
Run sentinels.
var ErrUnknownConfigKey = errors.New("unknown config key")
ErrUnknownConfigKey is returned for a key outside the known set. A typo'd key must not silently store a value nothing reads.
var ErrUsageAlreadyRecorded = errors.New("usage already recorded for this step, attempt, and unit")
ErrUsageAlreadyRecorded is the (step_id, attempt, unit) key firing — this unit was already recorded for this attempt.
It is a SENTINEL rather than a raw constraint error so a caller can phrase the refusal for whoever hit it: the same violation means "the claimant already reported this" at completion and "you are back-filling twice" at back-fill, and the two want different sentences.
var ErrValidation = errors.New("validation")
ErrValidation is returned when an input fails a validation precondition that the DB layer enforces (e.g., negative revision number). CLI surfaces map this to output.ErrValidation. See TDD docket-doc-cli §6.4.
var ErrVersionConflict = errors.New("version conflict")
ErrVersionConflict is returned when a CAS-guarded mutation is attempted against a row whose version differs from the caller's expectation — someone else wrote to it in between. Callers surface this as CONFLICT (exit 4), distinct from ErrNotFound (exit 2).
var ErrWorkflowAlreadyDeprecated = errors.New("workflow version is already deprecated")
ErrWorkflowAlreadyDeprecated means the version is already retired. Surfaced as CONFLICT (exit 4): retiring twice is not a silent success, because the second caller's mental model ("I am the one taking this out of service") is wrong and the timestamp they would expect to see is not the one stored.
Functions ¶
func AddLabelToIssue ¶
AddLabelToIssue attaches a label to an issue within a transaction. The label is created if it does not already exist (with the given color). Activity is recorded and the issue's updated_at timestamp is touched.
func AddLabelsToIssue ¶
func AddLabelsToIssue(db *sql.DB, issueID int, labelNames []string, color string, author string) error
AddLabelsToIssue attaches multiple labels to an issue atomically within a single transaction. Labels are created if they do not already exist (with the given color). Activity is recorded for each newly attached label and the issue's updated_at timestamp is touched once.
func AddRunIssue ¶
AddRunIssue attaches an issue to a run before activation. The binding, snapshots, and expansion timestamp are all NULL until activation fills them — a run in `planning` carries its issue list and nothing else.
func AdvanceSagaTx ¶
AdvanceSagaTx moves a step's saga stage under a CAS guard on the CURRENT stage — §6.8's "each stage's transaction is WHERE saga_stage = <expected> CAS-guarded, so two concurrent engine invocations resuming the same saga produce exactly one advance".
The loser matches zero rows and gets ErrSagaStageMoved, which is not an error condition: it re-reads and either finds the saga finished or advances the stage that is now current. This is the whole of the concurrent-resume story, and it is one WHERE clause because a saga guarded by a read-then-write would have the window this exists to close.
`from` is "" for the NULL stage (entering the saga) and `to` is "" to leave it (the saga completing).
func AllProjectPrefixes ¶
AllProjectPrefixes reads every prefix registered in the store — the root hook's roster for model.SetKnownProjectPrefixes (DKT-110), so id parsing accepts exactly the prefixes some project actually holds and refuses the rest (`ANSI-16` resolving issue 16 was the defect).
func AttachFiles ¶
AttachFiles inserts rows into issue_files for each file path. Duplicate attachments are silently ignored (INSERT OR IGNORE). Activity is recorded for each batch of newly attached files.
func AuthorizeLeaseMutation ¶
AuthorizeLeaseMutation verifies that a caller may mutate an issue whose lease may or may not be live, and ends the lease when the caller is the holder.
This is the guard for terminal verbs (`issue close`), which end a lease as a side effect the way a step's token retires when its artifact records (engine-spec.md §2).
The dormancy rule is here, in the first branch: an issue with no LIVE lease is outside the mechanism entirely. No token is required, nothing is refused, and behavior is exactly what it was at v5. The token check fires only when a live lease exists — which is what makes engine-spec.md §9 item 8 hold for a repo that never claims.
func AuthorizeStepRead ¶
AuthorizeStepRead is AuthorizeStepTx's predicate over a READ, for the one caller that must refuse a non-holder before opening a write transaction: stage 0's payload validation (payloads-thresholds §4.8 C6). It shares the predicate rather than restating it, so the two cannot disagree, and it is ADVISORY — AuthorizeStepTx remains the authority.
func AuthorizeStepTx ¶
AuthorizeStepTx is §6.8 stage 0's holder check and §6.9's R1-R4 in one call: the token must hold a LIVE lease on the step. It is leaseSteps.authorize, exported under a name the saga reads naturally.
func AutoRegisterEnabledTx ¶
AutoRegisterEnabledTx resolves KeyAutoRegister inside a CALLER'S transaction — activation's own, since that is the only place this reads (§9's scan runs inside activateTx, and a pool read there would deadlock against the one-connection pool exactly as VoteRuleExistsTx's doc explains).
The parse cannot fail on a value that reached storage: SetConfig already ran it through ValidateConfigValue's KindBool case, which is the same strconv.ParseBool this calls.
func BindRunIssueTx ¶
BindRunIssueTx records an issue's binding and its activation-time snapshots — stages 1 and 4 of the fat transaction, written together because they are one fact: this issue, bound to this workflow, as it read at this moment.
func BreachRunBudgetTx ¶
BreachRunBudgetTx is B20 and B22: the run flips `active -> waiting-human` with its reason, CAS-guarded on the status it is moving FROM.
The CAS is the whole mechanism (C6). Two invocations that both observe the cap crossed both call this; exactly one matches a row, and the loser writes neither a second reason nor — because the caller keys the event on this return — a second event. The guard is the STATUS ITSELF rather than a flag we maintain, so there is no second piece of state that could disagree with it.
`reason` is written to BOTH `reason` and `breach_reason`. `reason` is the run machine's general "why is it parked" field that `run status` already renders, and `breach_reason` is the budget's own, so a later `pause` for an unrelated cause cannot overwrite the record of the breach.
`pause_origin` is written in the SAME statement (DKT-305): this park is a RUN-LEVEL decision — it parks no step — and the reconciliation rollup reads that column to know it must not auto-resume. Before the column existed the rollup read `breach_reason` for the same purpose, which worked only because a breach is the one run-level park that leaves a second trace; an operator's `run pause` leaves none, and was silently undone.
func BudgetDefaultTx ¶
BudgetDefaultTx is B1's second branch: the config cap, read in the deciding transaction. 0 means unlimited (B2).
func BudgetUnitTx ¶
BudgetUnitTx is B16's resolution: WHICH recorded usage unit the run cap counts.
Empty — the default and the only value core ships — means `reported` is 0 and the enforcement rests entirely on the floor (B17). That is the honest default and it is exactly §9 item 7's configuration: with reporting disabled, the run still pauses at the cap from the floor.
func CacheRunFloorTx ¶
CacheRunFloorTx writes `runs.usage_floor`.
IT IS A CACHE FOR THE REPORT AND NOTHING ELSE (§4.3, §3.2). No enforcement path reads it: the floor that decides is a SUM over claim events, computed inside the deciding transaction, because a stored running total is a read-modify-write and a read-modify-write is the one shape C4 has no defense against. TestFloorIsNeverReadFromCache poisons this column and asserts every decision behaves as though it said the truth.
It does NOT bump `row_version`: the cache is a derived number, not a state change an operator's CAS should collide with. A claim that bumped the run's version for a number nobody asserted against would make `--if-version` on runs unusable during any active run.
func CascadeDeleteIssue ¶
CascadeDeleteIssue deletes an issue and all its descendants recursively in a single transaction. The recursive CTE finds all descendant issues; ON DELETE CASCADE constraints on comments, issue_labels, issue_relations, and activity_log handle cleanup of related rows automatically.
func CheckAndBumpVersion ¶
CheckAndBumpVersion enforces an --if-version precondition inside tx and increments the row's version.
ifVersion nil means "no precondition": the version is still bumped, so every mutation advances it and concurrent CAS writers are detected.
The zero-rows case is deliberately re-probed rather than assumed: a missing row is ErrNotFound (exit 2) while a present row at a different version is ErrVersionConflict (exit 4). Collapsing the two would report a live conflict as a missing entity.
func ClaimIssue ¶
func ClaimIssue(db *sql.DB, id int, owner string, ttlMS int64, nowMS int64) (token string, lease *model.Lease, err error)
ClaimIssue takes a lease on an issue and returns the minted capability token. It is one CAS transaction: exactly one of N concurrent claimants wins, and the losers get ErrLeaseHeld (engine-core.md §5).
Expiry is reaped lazily, here and only here (engine-spec.md §6: "lazy lease reaping confined to next/claim; reads never write"). The `expires_ms <= now` disjunct IS the reaping — there is no reaper, no background pass, and no write from any read path. An expired lease is therefore re-claimable with no operator action beyond the claim itself, which is the liveness mechanism engine-spec.md §9 item 4 requires.
attempt increments on every winning claim, including the one that replaces a holder that died mid-work. That is the complete attempt trail: it counts claims for all time and is never decremented or reset.
func ClaimStep ¶
ClaimStep is the standalone claim, for tests and for any caller that needs only the lease. `step claim` uses ClaimStepTx.
func ClaimStepTx ¶
func ClaimStepTx( tx *sql.Tx, id int, owner string, ttlMS, nowMS int64, ) (token string, lease *model.Lease, err error)
ClaimStepTx takes a lease on a step inside the caller's transaction, through the SAME generalized implementation issues use (TDD §6.6).
It is transaction-scoped rather than standalone because `step claim` must mint the token and assemble the §11.4 context bundle in ONE transaction — "an unclaimed executor has nothing, a claimed one has everything" (engine-core §8) — so the caller owns the transaction and this is one statement inside it.
func ClearAllData ¶
ClearAllData deletes all data from every persistent table within a single transaction. The schema and meta table are preserved.
Tables are deleted in FK-correct order — children before parents. FK CASCADE would handle dependents implicitly, but the explicit ordering keeps behaviour identical to the pre-v4 function and makes the contract auditable from the function body.
Doc tables and the pre-existing proposals/votes/proposal_issues tables are included; prior to v4 the latter three were silently omitted, which broke `--replace` import on any DB containing proposals (TDD §5.4 S4 / R7).
func ClearAllDataTx ¶
func ClearProjectDataTx ¶
ClearProjectDataTx is ClearAllDataTx scoped to ONE project (v12): the same tracker tables, children before parents, but only the rows reachable from this project's roots. Under the shared store, "replace everything" scoped any wider would delete projects the operator was not looking at.
func ClearRunBreachTx ¶
ClearRunBreachTx clears `breach_reason` once a cap change has resolved the breach (DKT-80): a row still asserting "budget: spend N of cap M reached" after the cap moved past N misleads every reader that trusts it. When `newReason` is non-empty the run machine's general `reason` is rewritten too — the caller passes it only when the row's reason IS the breach reason, so an unrelated pause's reason is never overwritten.
`row_version` is not bumped here: every caller runs inside a transaction that already bumped it for the cap write itself.
func ClearStaleBudgetReasonTx ¶
ClearStaleBudgetReasonTx retires a run's `reason` when it still carries a PREVIOUS raise's "cap changed from X to Y" sentence (DKT-80's own text, written by `ClearRunBreachTx`'s `newReason` argument) and no breach is currently standing. `newReason` replaces it; an empty `newReason` blanks the field instead, for a run with no other reason to state.
`ClearRunBreachTx` only rewrites `reason` on the raise that RESOLVES a standing breach — right for `breach_reason` itself, since there is nothing left to clear a second time, but the decorative sentence that raise wrote survives every later, unrelated raise untouched, naming a cap that has since moved again (DKT-47). This is the caller's own leftover text retired, never an operator's pause reason: SetRunBudget only reaches for it once it has confirmed `reason` carries the exact prefix this package writes, and it supplies `newReason` itself only for a run still parked on the sentence (waiting-human) — blanking that one would leave a parked run with no stated reason at all.
func ClearStepStartTx ¶
ClearStepStartTx clears the schedule-to-close clock, so a step returned to the unclaimed pool starts a fresh budget on its next claim. Reaping and explicit failure both go through it.
func CloseDispatchTx ¶
CloseDispatchTx is C2: close and abandon are both CAS on (id, status='open').
It reports whether it MOVED the row. A close racing the TTL abandon matches zero rows and learns so, which is what lets the caller report `CONFLICT` naming what actually happened rather than "not open" (P22) — the loser needs to know WHY, and the row it then reads says.
func CloseOpenProposalsTx ¶
CloseOpenProposalsTx closes every OPEN proposal among `ids`, inside a caller's transaction, and reports how many it closed (DKT-262).
It exists because closing a stranded proposal is not an operator act — it is the tail of a transition that already happened. `run abandon` ends the run that opened them; the ack of a reap answers the question its proposal asked. Those transitions are transactional, so the close has to be able to ride inside them: a close committed separately can be lost while the transition stands, which puts the row back in the state this exists to prevent.
ONLY `open` ROWS MOVE, exactly as CloseProposal insists. Every other status is the record of a decision, and a bulk close that rewrote one would be the overwrite the immutable-record rule forbids — which matters more here than in the single-id case, because a caller passing a set has not looked at each one.
`reason` lands in `final_outcome`, so the row itself says how the question ended. It should name the TRANSITION, not the verdict: these proposals were never decided, and a reason that read like a decision would be a worse lie than the stale `open` was.
func CloseProposal ¶
CloseProposal retires an OPEN proposal without a tally (DKT-114).
The case it exists for: a gate's underlying decision was made another way — an operator authorized the guarded action directly — and the proposal the panel would have decided has no votes and no future. Before this verb such a proposal sat `open` forever, misreporting a settled question as a pending one.
Only `open` closes. Every other status is the record of a decision, and a close that rewrote one would be exactly the overwrite the immutable-record rule forbids. The reason lands in `final_outcome`, so the row itself says how the question ended.
func CommitProposal ¶
CommitProposal transitions an approved proposal to committed status with a final outcome. If escalationReason is non-empty, it is stored on the proposal.
func CountActivity ¶
CountActivity returns the total number of activity log entries for an issue, ignoring any limit. Callers pair it with GetActivity to report an honest pre-limit total and flag truncation.
func CountByPriority ¶
CountByPriority returns a map of priority -> count, scoped to a project when projectID is non-zero.
func CountByStatus ¶
CountByStatus returns a map of status -> count, scoped to a project when projectID is non-zero.
func CountIssues ¶
CountIssues returns the number of issues, scoped to a project when projectID is non-zero.
func CountRootIssues ¶
CountRootIssues returns the number of issues with no parent, scoped to a project when projectID is non-zero.
func CountSteps ¶
CountSteps returns a run's step count, for `run status` without paging the whole table.
func CreateComment ¶
CreateComment inserts a new comment for an issue, records activity, and returns its ID. The insert and activity log are wrapped in a single transaction so they succeed or fail together.
func CreateCommentIdempotent ¶
func CreateCommentIdempotent(db *sql.DB, comment *model.Comment, idempotencyKey string) (int, error)
CreateCommentIdempotent is CreateComment with an optional idempotency key. A repeat call with the same key returns the original comment id and inserts nothing; the key is recorded in the same transaction as the insert.
func CreateDoc ¶
CreateDoc inserts a new doc and appends revision #1 with change_kind="create" in a single transaction. Returns the new doc ID. The supplied doc must have Type, Status, Title, Body, and Author set; CreatedAt/UpdatedAt are stamped by this function.
func CreateDocComment ¶
CreateDocComment inserts a comment on a doc and returns its ID. The doc existence check and insert run in a single transaction. Returns ErrNotFound if the doc does not exist.
func CreateDocCommentIdempotent ¶
func CreateDocCommentIdempotent(db *sql.DB, c *model.DocComment, idempotencyKey string) (int, error)
CreateDocCommentIdempotent is CreateDocComment with an optional idempotency key. A repeat call with the same key returns the original comment id and inserts nothing; the key is recorded in the same transaction as the insert.
func CreateDocIdempotent ¶
CreateDocIdempotent is CreateDoc with an optional idempotency key. A repeat call with the same key returns the original doc id and inserts nothing; the key is recorded in the same transaction as the insert.
func CreateIssue ¶
CreateIssue inserts a new issue and returns its ID. Labels are created (find-or-create) and linked to the issue within the same transaction. Files are attached to the issue if provided.
func CreateIssueIdempotent ¶
func CreateIssueIdempotent(db *sql.DB, issue *model.Issue, labels []string, files []string, idempotencyKey string) (int, error)
CreateIssueIdempotent is CreateIssue with an optional idempotency key.
When idempotencyKey is non-empty and was already used for this scope, the original issue's id is returned and nothing is inserted — a retried create after a dropped response must succeed, not fail. The key record and the insert commit in the SAME transaction, so a crash between them cannot orphan either.
func CreateProposal ¶
CreateProposal inserts a new proposal and returns its ID.
func CreateProposalIdempotent ¶
CreateProposalIdempotent is CreateProposal with an optional idempotency key. A repeat call with the same key returns the original proposal id and inserts nothing. Unlike the plain path this runs in a transaction, so the insert and the key record commit together.
func CreateRelation ¶
CreateRelation inserts a new relation between two issues within a single transaction. It validates that both issues exist, rejects self-referential and duplicate relations, runs cycle detection for blocks/depends_on types, and records activity on both issues.
func DefaultProjectIDOr ¶
DefaultProjectIDOr is projectOrDefault for callers outside this package that render their own SQL and need the same zero-means-default rule.
func DeleteDoc ¶
DeleteDoc removes the doc with the given ID. When cascade is true, FK cascades drop doc_revisions, doc_comments, doc_issue_links, proposal_docs. When cascade is false, the call returns ErrConflict if any links (issue or proposal) exist for the doc — comments and revisions are part of the doc's own history and never block deletion. Returns ErrNotFound if no doc with that ID exists.
func DeleteIssue ¶
DeleteIssue removes an issue by ID. Foreign key cascades handle cleanup of related rows (comments, labels, activity, relations).
func DeleteLabel ¶
DeleteLabel removes a label by ID. CASCADE constraints handle cleanup of issue_labels rows. Activity is recorded for each affected issue using the provided name. Returns the list of issue IDs that were attached to the label.
func DeleteProject ¶
DeleteProject removes an EMPTY project row (DKT-59).
It exists because the auto-registration defect (DKT-58) minted permanent rows nobody asked for, and `docket project` had no verb that could take one back out: the operator's only remedy was a raw sqlite DELETE against a store shared by every repository on the machine.
It REFUSES a project any row references, and refuses the default row outright. That is what makes it safe to expose: the verb can remove junk and cannot remove history, so there is no version of "I meant the other project" that costs anything. Re-homing real rows is `issue move --project`'s job, and a project emptied that way becomes deletable by this verb afterwards.
func DeleteRelation ¶
DeleteRelation removes a relation matching the given source, target, and type. Activity is recorded on both issues within a single transaction.
func DeprecateWorkflow ¶
func DeprecateWorkflow(db *sql.DB, projectID int, name string, version int, nowMS int64) (*model.Workflow, error)
DeprecateWorkflow retires ONE registered version from binding.
It writes a timestamp and NOTHING ELSE. The row, its body, its parsed form, and its hash are untouched, so:
- `workflow show name@n` still renders it, and `--source` still emits the exact registered bytes;
- definitionByID still resolves it, so a run that pinned this version before it was retired continues to completion — retirement is a binding-time filter, not a retraction;
- the lineage stays legible: `workflow list` shows the version with its retirement date rather than a gap where a version used to be.
There is deliberately no delete verb. The operator was offered one and rejected it: old versions stay registered, and that is the point.
func DerivePrefix ¶
DerivePrefix proposes a display prefix from a project's name (DKT-60).
A multi-word name becomes its INITIALS — `agentic-mcp-services` reads as `AMS` — because the alternative, the first three letters, collapses whole families of sibling repositories onto the same three characters. A single-word name takes its first three letters instead, since `D` alone carries nothing.
The result is always a legal prefix per model.ValidateProjectPrefix (letters only, 1-8) or empty when the name yields no letters at all; uniqueness and the reserved names are availablePrefix's job, because both are facts about the store rather than about the name.
func DetachFiles ¶
DetachFiles deletes rows from issue_files matching the issue ID and file paths. Activity is recorded for removed files.
func DispatchGraceTx ¶
DispatchGraceTx is D1's window: how long a claimed step may go unrecorded.
func DispatchTTLTx ¶
DispatchTTLTx and DispatchGraceTx resolve §4.11's two durations inside the caller's transaction, for the reason configValueTx exists: internal/db caps the pool at ONE connection, so a pool read from inside an open transaction deadlocks rather than failing.
func EnsureProject ¶
EnsureProject resolves identity to a project id, creating the row on first contact.
The resolution ladder:
- A row already bound to this identity wins.
- The UNCLAIMED default row — id 1 with an empty identity, seeded by the v12 migration — is claimed in place. A legacy store holds exactly one project's history under project 1, and the first repository to open it is that project; claiming rather than inserting is what keeps that history attached to its repo.
- Otherwise a new row is inserted.
An EMPTY identity never claims and never inserts: it reads as "this invocation could not be resolved to a project" and falls back to the default row, which is the pre-v12 behavior exactly.
func EnsureProjectCreated ¶
EnsureProjectCreated is EnsureProject with the fact the caller needs in order to report a first contact: whether this call is what brought the row into being. The root hook writes a `project-registered` event on true (DKT-61).
func EventsRetain ¶
EventsRetain resolves the retention window, with 0 meaning "retain everything" (docs/tdd/events-follow.md §5.3).
It returns the DURATION rather than a cutoff timestamp, because the caller computes the cutoff inside the prune's own transaction against that transaction's clock — a helper that read the clock here would hand back a boundary that had already moved by the time it was applied. EventsRetain is deliberately STORE-WIDE (projectID 0): the event stream and its prune are one machine-level lifecycle, and a per-project window would let one project's policy delete rows another project's audit still needs.
func GetActivity ¶
GetActivity retrieves activity log entries for an issue, ordered by most recent first.
func GetAllDirectionalRelations ¶
GetAllDirectionalRelations returns all relations where the relation type is "blocks" or "depends_on", in INSERTION order (`id`) — see GetAllRelations.
func GetAllRelations ¶
GetAllRelations returns every relation in the database, in INSERTION order (`id`).
It was `ORDER BY created_at ASC` with no tiebreak, and `created_at` here is RFC3339 at SECOND resolution (see AddRelation). Relations created inside one second therefore had NO defined relative order, so `docket export` run twice against an unchanged database could emit the relations array in two different orders and a manifest diff would carry noise that is not a change (DKT-330).
`id` is `INTEGER PRIMARY KEY AUTOINCREMENT`: strictly ascending, never reused. Ordering by it is not an approximation of creation order, it IS creation order, and it is total. Thirteen of the fifteen export-manifest readers already order by a primary or natural key; this was the one with no total order at all.
Same defect class as DKT-378's on `comments`, reached through another table — and the same remedy, for the same reason.
func GetBatchSubIssueProgress ¶
GetBatchSubIssueProgress returns (done, total) counts for descendants of each given parent ID in a single query, avoiding N+1 overhead.
func GetComment ¶
GetComment retrieves a comment by ID.
func GetDocComment ¶
GetDocComment returns a single doc comment by ID, or ErrNotFound.
func GetDocIssues ¶
GetDocIssues returns issue IDs linked to a doc, ordered by issue_id ASC.
func GetDocProposals ¶
GetDocProposals returns proposal IDs linked to a doc, ordered by proposal_id ASC.
func GetDocRevision ¶
GetDocRevision returns revision rev of the doc with the given ID. Per TDD §3.3 / §6.3 (Q1): rev < 0 → ErrValidation; rev > MAX → ErrNotFound. rev == 0 is treated as "the current revision" (the most-recent one).
func GetIssueDocs ¶
GetIssueDocs returns doc IDs linked to an issue, ordered by doc_id ASC.
func GetIssueFiles ¶
GetIssueFiles returns the file paths attached to an issue, sorted alphabetically.
func GetIssueLabelObjects ¶
GetIssueLabelObjects returns the full Label objects attached to an issue, sorted alphabetically by name.
func GetIssueLabels ¶
GetIssueLabels returns the label names attached to an issue, sorted alphabetically.
func GetIssueLease ¶
GetIssueLease reads an issue's lease without writing anything.
Reads never write (engine-spec.md §6). Liveness is computed from ExpiresMS by the caller via Lease.Live; nothing here reaps.
func GetIssueProposals ¶
GetIssueProposals returns the proposals linked to an issue, ordered by proposal id ascending. It is the reverse edge of GetProposalIssues.
func GetIssueRelations ¶
GetIssueRelations returns all relations where the given issue is either the source or the target, in INSERTION order (`id`) — see GetAllRelations for why that is the creation order rather than an approximation of it.
func GetIssuesByIDs ¶
GetIssuesByIDs retrieves multiple issues by their IDs in a single query. The returned map is keyed by issue ID. IDs that don't exist are silently skipped (no error for missing rows). Labels are hydrated on all returned issues.
func GetLabelByName ¶
GetLabelByName retrieves a label by name WITHIN ONE PROJECT, including the count of issues currently attached to it. Returns ErrNotFound if no label with that name exists in the project.
func GetProject ¶
GetProject reads one project row.
func GetProposal ¶
GetProposal returns a proposal by ID, or ErrNotFound if it does not exist.
func GetProposalDocs ¶
GetProposalDocs returns doc IDs linked to a proposal, ordered by doc_id ASC.
func GetProposalIssues ¶
GetProposalIssues returns the issue IDs linked to a proposal.
func GetProposalVotes ¶
GetProposalVotes returns all votes for a proposal, ordered by creation time.
func GetSchema ¶
GetSchema returns one registered schema visible to a project — its own registration or a builtin. A version of 0 selects the HIGHEST registered version, which is what `schema show NAME` without `@version` means.
func GetSchemaTx ¶
GetSchemaTx is GetSchema at an exact version, inside a transaction. It is what activation's pin stage reads, so the hash it records and the row it checked are the same read (§4.7 P1).
func GetStepLease ¶
GetStepLease reads a step's lease without writing anything (§6.3: reads never write).
func GetSubIssueProgress ¶
GetSubIssueProgress returns (done, total) counts for all descendants of an issue.
func GetSubIssueTree ¶
GetSubIssueTree returns the full recursive tree of all descendants under an issue.
func GetSubIssues ¶
GetSubIssues returns all direct children of an issue.
func GetVersion ¶
GetVersion returns the current CAS version of a row. The table must be one of versionedTables.
func GetWorkflow ¶
GetWorkflow returns one registered workflow WITHIN ONE PROJECT (v12 — a name@version is a per-project registration). A version of 0 selects the HIGHEST registered version, which is what `workflow show NAME` without `@version` means.
func GrantLoopTx ¶
GrantLoopTx authorizes ONE more fix loop for an issue and returns the new total.
It RAISES A GRANT rather than editing `max_fix_loops`, because the two say different things: the workflow's bound is the author's standing policy over every issue it matches, while this is one operator's decision about one issue on one occasion. Editing the bound to unstick a single issue would quietly loosen it for every issue after.
func HasGateResult ¶
HasGateResult reports whether a step+gate has any recorded result.
This is the at-least-once detector (§7.5 A1): a `saga_stage` of `gate:<name>` with no result row is a started-but-unrecorded gate — a crash between the `gate-started` commit and the result commit.
func HeartbeatIssue ¶
func HeartbeatIssue(db *sql.DB, id int, token string, ttlMS int64, nowMS int64) (*model.Lease, error)
HeartbeatIssue extends a live lease held by token, and returns the new expiry. Any tool activity in the holder's session can drive this, so a working holder keeps its lease and a wedged or dead one lets it lapse (engine-core.md §5 "Leases").
attempt and owner are untouched: a heartbeat is not a new claim.
func HeartbeatStep ¶
HeartbeatStep extends a live lease held by token. attempt is untouched — a heartbeat is not a new claim.
func HydrateFiles ¶
HydrateFiles bulk-loads files for a set of issues, populating each issue's Files field. This avoids N+1 queries in list views and the planner.
func HydrateLabels ¶
HydrateLabels bulk-loads labels for a set of issues, populating each issue's Labels field. This avoids N+1 queries when displaying lists.
func HydrateLinkedIssues ¶
func IdempotencyKeyOf ¶
IdempotencyKeyOf is the REVERSE lookup: the key recorded for (scope, entityID), and whether one exists. It exists for the caller that holds an entity and needs the identity its create was keyed under — the engine recovering a vote-step proposal's run from the key OpenVoteProposal recorded — without a second, disagreeable copy of that link on the entity's own row. A create performed without a key (the historical non-idempotent wrappers) simply reports no row.
func IncrementLoopCountTx ¶
IncrementLoopCountTx raises the issue's loop counter by one and returns the NEW value (§11.3 (1)).
The read-back is in the same statement's transaction rather than a separate SELECT so the value returned is the one this UPDATE wrote. Two concurrent routings incrementing the same issue would otherwise both read the same "new" count and both believe they were loop k+1 — and `max_fix_loops` would bound nothing.
func Initialize ¶
Initialize creates all tables if they don't exist and sets the schema version.
func InsertActionResultTx ¶
func InsertActionResultTx(tx *sql.Tx, r ActionResultRow) error
InsertActionResultTx records one action result.
It takes a transaction because the result commits with the routing stage's own writes — the subprocess ran OUTSIDE any transaction (engine-spec §6) and only its recorded fact lands in one.
func InsertActivityWithID ¶
InsertActivityWithID inserts an activity_log row with a caller-supplied ID, skipping if the ID already exists. Must be called within an existing transaction. Returns true if inserted. Mirrors InsertIssueWithID.
func InsertArtifactTx ¶
InsertArtifactTx records one artifact inside the caller's transaction — §6.8 stage 1, alongside the token's retirement, because "the token retires when the artifact records" is one commit or it is not the hinge it is specified to be.
func InsertCommentWithID ¶
InsertCommentWithID inserts a comment with a specific ID (not auto-increment), skipping if the ID already exists. Returns true if the row was inserted. Must be called within an existing transaction.
func InsertDispatchRowTx ¶
func InsertDispatchRowTx(tx *sql.Tx, dispatchID int, row DispatchRow) error
InsertDispatchRowTx stores one manifest row at its position.
func InsertDispatchTx ¶
InsertDispatchTx opens a manifest, and C1 IS THE INSERT.
`idx_dispatches_one_open` is a partial UNIQUE index on (run_id) WHERE status='open', so two relays racing produce one row and one constraint violation — never a check-then-insert's window, in which both SELECT no open dispatch and both then INSERT one. The loser gets ErrDispatchAlreadyOpen and its whole computation is DISCARDED rather than merged (§5.4).
func InsertDocCommentWithID ¶
InsertDocCommentWithID inserts a doc_comments row with a caller-supplied ID, skipping if the ID already exists. Returns true if inserted. Must be called within an existing transaction. Mirrors InsertCommentWithID.
func InsertDocIssueLink ¶
InsertDocIssueLink inserts a doc_issue_links row, skipping on PK conflict. Used by export/import round-trip. Must be called within a transaction. Returns true if inserted.
func InsertDocRevisionWithID ¶
InsertDocRevisionWithID inserts a doc_revisions row with a caller-supplied ID, skipping if the ID already exists. Must be called within a transaction. Returns true if inserted. Mirrors InsertIssueWithID.
func InsertDocWithID ¶
InsertDocWithID inserts a doc row with a caller-supplied ID, skipping if the ID already exists. Mirrors InsertIssueWithID (TDD §5.3 round-trip helpers). Must be called within an existing transaction. Returns true if inserted.
func InsertEngineComment ¶
InsertEngineComment inserts an engine-authored comment against the caller's already-open transaction and returns its auto-minted ID. It never begins or commits a transaction of its own, so engine code already inside a transaction can drop an activity-trail comment without nesting a second top-level transaction. The comment's Author is always EngineAuthor.
The caller supplies the timestamp as epoch milliseconds rather than the writer reading the clock: an engine transaction stamps the issue row, the activity log and this comment from ONE `nowMS`, and a second clock read here would let the narration of a transition carry a different time than the transition itself.
func InsertFenceTx ¶
InsertFenceTx records one harvested command. Harvesting happens at ACTIVATION so "post-activation edits cannot inject" (engine-spec §4) — the hash is of the command as it read when the operator approved the plan.
func InsertGapIssueTx ¶
func InsertGapIssueTx(tx *sql.Tx, projectID int, title, description string, relatedIssueID int) (int, error)
InsertGapIssueTx materializes a backlog issue from a recorded gap artifact (DKT-72), inside the completion saga's transaction, and relates it to the issue whose step recorded the gap.
One transaction with the artifact, because the pair is the whole point: a gap that recorded an artifact but no issue is residue nothing re-reads — the failure mode this replaces — and an issue without its artifact is a claim with no record behind it. `relates_to` rather than a directional relation: a gap is out-of-scope BY DEFINITION, so it must not block the issue that surfaced it.
func InsertGateResultTx ¶
func InsertGateResultTx(tx *sql.Tx, r GateResultRow) error
InsertGateResultTx records one gate result.
It takes a transaction because the result commits with the saga's stage advance — the subprocess ran OUTSIDE any transaction (engine-spec §6) and only its recorded fact lands in one.
func InsertIssueFileMapping ¶
InsertIssueFileMapping inserts a single file mapping using INSERT OR IGNORE. Returns true if inserted, false if already existed. Must be called within an existing transaction.
func InsertIssueLabelMapping ¶
InsertIssueLabelMapping inserts an issue_labels row linking an issue to a label, skipping if the mapping already exists. Returns true if the row was inserted. Must be called within an existing transaction.
func InsertIssueWithID ¶
InsertIssueWithID inserts an issue with a specific ID (not auto-increment), skipping if the ID already exists. Returns true if the row was inserted. Must be called within an existing transaction.
func InsertLabelWithID ¶
InsertLabelWithID inserts a label with a specific ID (not auto-increment), skipping if the ID already exists. Returns true if the row was inserted. Must be called within an existing transaction.
func InsertNoteTx ¶
InsertNoteTx records an author-attributed comment against the caller's already-open transaction: the row `issue comment add` writes, minus the transaction it opens for itself. It is what a verb taking `--note` uses to land the note and the mutation it explains in ONE transaction (DKT-480), so a failed move leaves no orphan comment and a recorded comment never narrates a move that did not happen.
Unlike InsertEngineComment the author is the caller's, not EngineAuthor: a note an operator typed is an operator's comment, indistinguishable from the two-verb form it replaces. It touches the issue's updated_at and records the same `comment_added` activity CreateComment does, for the same reasons.
The updated_at touch runs FIRST so a missing issue is ErrNotFound rather than a foreign-key error from the insert.
The caller supplies the timestamp rather than this reading the clock, so the note carries the same stamp as the mutation it narrates.
func InsertPinTx ¶
InsertPinTx records a pin. `INSERT OR IGNORE` on the UNIQUE(run_id, kind, ref) key makes pinning the same workflow for two issues in one run a single row rather than a conflict — a run pins a workflow once, however many issues bind to it.
func InsertProposalDocLink ¶
InsertProposalDocLink inserts a proposal_docs row, skipping on PK conflict. Must be called within a transaction. Returns true if inserted.
func InsertProposalIssueLink ¶
InsertProposalIssueLink inserts a proposal_issues row, skipping on PK conflict. Must be called within a transaction. Returns true if inserted.
func InsertProposalWithID ¶
InsertProposalWithID inserts a proposal row with a caller-supplied ID, skipping if the ID already exists. Must be called within an existing transaction. Returns true if inserted. Mirrors InsertIssueWithID; domain_tags and files_changed are JSON-encoded identically to CreateProposal.
func InsertReapAckTx ¶
InsertReapAckTx records a reap as unacknowledged, in the reap's own transaction (A16, A19).
The caller is responsible for A16's OTHER half — calling this only for a class whose `[limits] max` is finite — because that decision needs the scheduler's merged limits, which the storage layer does not have and must not guess at.
func InsertRelationWithID ¶
InsertRelationWithID inserts a relation with a specific ID (not auto-increment), skipping if the ID already exists. Returns true if the row was inserted. Must be called within an existing transaction.
func InsertRun ¶
func InsertRun(db *sql.DB, projectID int, request string, budget float64, nowMS int64) (*model.Run, error)
InsertRun creates a run in `planning` — `docket run start` (TDD §5.2).
`budget` is STORED and enforces nothing until S6. Accepting it now means the S6 upgrade adds enforcement rather than a flag: a flag appearing later would break the `run start` invocation an S3-era harness scripted.
func InsertRunWithContext ¶
func InsertRunWithContext(db *sql.DB, projectID int, request string, budget float64, nowMS int64, ctx RunContext) (*model.Run, error)
InsertRunWithContext is InsertRun carrying the invocation's context.
func InsertRunWithContextIdempotent ¶
func InsertRunWithContextIdempotent(db *sql.DB, projectID int, request string, budget float64, nowMS int64, ctx RunContext, idempotencyKey string) (*model.Run, error)
InsertRunWithContextIdempotent is InsertRunWithContext with an optional idempotency key — `docket run start --idempotency-key`.
A repeat call with the same key returns the ORIGINAL run unchanged and creates nothing, the same create-verb replay-protection pattern CreateIssueIdempotent, CreateDocIdempotent, and CreateProposalIdempotent use (internal/db/idempotency.go): a `run start` that commits but dies before its response must be safe to retry, or the key protects nothing. The key record and the insert commit in the SAME transaction, so a crash between them cannot orphan either.
func InsertSchema ¶
func InsertSchema(db *sql.DB, s *model.Schema, nowMS int64) (stored *model.Schema, created bool, err error)
InsertSchema registers a schema document, or returns the existing row when the same bytes are already registered at that `name@version`.
The three outcomes are `InsertWorkflow`'s, verbatim in behavior (§4.4):
- no row at name@version -> insert, created = true
- a row with the SAME source_sha256 -> return it, created = false
- a row with a DIFFERENT source_sha256 -> ErrSchemaConflict
Idempotency is decided on the CONTENT HASH, not on a normalized form: two documents that validate identically but differ in whitespace or key order are different registered bytes, because `source_sha256` is what pins refer to (§4.7) and what a run reproduces against.
func InsertSchemaTx ¶
func InsertSchemaTx( tx *sql.Tx, s *model.Schema, nowMS int64, ) (stored *model.Schema, created bool, err error)
InsertSchemaTx is InsertSchema inside a CALLER'S transaction — the schema half of what S6's auto-registration needs (docs/tdd/runs-dispatch.md §9.2 F8).
It mirrors InsertWorkflowTx exactly, because the two registries have the same immutability contract and a caller should not have to learn it twice. The reason both are needed inside a transaction is F8's: auto-registration runs in activation's fat transaction, so a failure refuses the whole activation and leaves no definitions behind from a run that never started.
func InsertStepInputTx ¶
InsertStepInputTx records that a step consumed an artifact at a declared position — the resolution §6.7 computed, MATERIALIZED.
The record exists because resolution is a function of run state at ASSEMBLY time, and run state moves: a later ordinal's artifact would re-resolve the same input differently. Storing what was actually handed over is what makes the ledger answer "what did this step see" rather than "what would it see now".
func InsertStepTx ¶
InsertStepTx writes one expanded step. The UNIQUE(run_id, issue_id, instance) index is the loop/fanout correctness guard — two rows claiming the same identity is the bug §11.3 exists to prevent — so a duplicate is an error here rather than a silently-ignored insert.
func InsertTrustCacheTx ¶
func InsertTrustCacheTx( tx *sql.Tx, runID int, kind, gate, argvSHA256, entryName string, matched, prefix bool, atMS int64, ) error
InsertTrustCacheTx records what a run considered trusted, and when (§4.5).
IT IS AN AUDIT RECORD, NEVER AN AUTHORIZATION SHORTCUT. Every gate consults the LIVE trust store on every execution (§7.2 M1); nothing is ever executed because a row here says a previous run matched. The opposite implementation is the tempting one and it is a revocation failure: a cache hit that authorized a spawn would make `trust rm` take effect only after the cache cleared.
`kind` is TrustKindGate or TrustKindAction (§6.3). It exists so the one question this table answers stays one query when actions start consulting the same store: a second table keyed by the shape of the caller would split it. Every pre-v9 row reads `gate` from the column default, which is what it was.
func InsertUsageRowTx ¶
InsertUsageRowTx records one unit's quantity for one attempt of one step.
The unique key is (step_id, attempt, unit), so a reaped-and-reclaimed step's SECOND attempt records beside the first rather than overwriting it — which is what makes "retries re-accrue" true on the reported side as well as on the floor side. A second `complete` for the SAME attempt is impossible (the saga's stage-0 CAS), so the key is a belt-and-braces assertion rather than an upsert: if it ever fires, something upstream broke a guarantee and silently merging the rows would hide it.
func InsertVoteUsageTx ¶
func InsertVoteUsageTx( tx *sql.Tx, voteID int64, unit string, quantity float64, source string, nowMS int64, ) error
InsertVoteUsageTx records one unit's quantity against one SEAT's cast in the vote_usage ledger, source explicit (v17, DKT-115): the cast-time writer passes UsageSourceReported, the vote-scoped back-fill its own source. The (vote_id, unit) key firing maps to ErrUsageAlreadyRecorded so each caller can phrase the refusal for whoever hit it — the same split the step ledger's writer makes.
func InsertVoteWithID ¶
InsertVoteWithID inserts a vote row with a caller-supplied ID, skipping if the ID already exists. Must be called within an existing transaction. Returns true if inserted. findings_json and metadata are JSON-encoded (NULL when absent) identically to CastVote, so an export/import round trip carries a vote's provenance claim exactly as it carries its findings.
func InsertWorkflow ¶
func InsertWorkflow(db *sql.DB, wf *model.Workflow, nowMS int64) (stored *model.Workflow, created bool, err error)
InsertWorkflow registers a definition, or returns the existing row when the same bytes are already registered at that `name@version`.
The three outcomes, per §4.1:
- no row at name@version -> insert, created = true
- a row with the SAME source_sha256 -> return it, created = false
- a row with a DIFFERENT source_sha256 -> ErrWorkflowConflict
Idempotency is decided on the CONTENT HASH rather than on the parsed form: two files that parse identically but differ in comments are different registered bytes, and `source_sha256` is what pins and audits refer to.
func InsertWorkflowTx ¶
func InsertWorkflowTx( tx *sql.Tx, wf *model.Workflow, nowMS int64, ) (stored *model.Workflow, created bool, err error)
InsertWorkflowTx is InsertWorkflow inside a CALLER'S transaction.
It exists for S6's auto-registration, which runs inside activation's FAT TRANSACTION (docs/tdd/runs-dispatch.md §9.2 F8): a registration failure must refuse the whole activation and write nothing, and a registration that committed on its own could not be rolled back with the binding that followed it. It is also the only correct shape against a one-connection pool — the self-committing version would deadlock if called from inside a transaction.
The three outcomes and the immutability contract are §4.1's, unchanged: this is the SAME body InsertWorkflow ran, lifted so both callers share it rather than a second path drifting from the first (F7: "no `auto` variant with looser rules").
func IsDescendant ¶
IsDescendant returns true if potentialDescendantID is a descendant of issueID. This is used to detect cycles when reparenting an issue.
func IssueExists ¶
IssueExists returns true if an issue with the given ID exists.
func IssueOwnerPrefix ¶
IssueOwnerPrefix reports the prefix of the project that OWNS an issue, or "" when the issue does not exist or its project has none (DKT-256).
One indexed lookup joining the issue to its project. It is the single-id form deliberately: issue ids are store-wide and a rendering pass touches whichever handful it happens to render, so a caller that preloaded the whole store would pay for every issue to display five.
A missing row is "" AND NO ERROR. The caller is a renderer, and a renderer that failed because an id it was handed does not exist would turn a cosmetic question into a broken command — the fallback is the reader's own prefix, which is exactly what it rendered before this existed.
func IssueProjectID ¶
IssueProjectID returns the project an issue is homed in, or ErrNotFound.
It exists for validations that need the project WITHOUT the issue's whole row: attaching issues to runs checks a whole set before writing anything (DKT-21), and loading each candidate's labels and lease state to read one column would make that loop's cost proportional to data it discards.
func IssueScopeGlobs ¶
IssueScopeGlobs reads an issue's declared scope as stored JSON, or "" when none is declared.
func IssueScopeGlobsTx ¶
IssueScopeGlobsTx is IssueScopeGlobs inside a transaction — the snapshot's reader at activation stage 4.
func IssueStepRuns ¶
IssueStepRuns reads the ids of every run holding a step for one issue, in run order. `step list --issue ISSUE-N` needs it because an issue's steps are not confined to one run — a re-activation mints a fresh round under a new run — and the caller who asks about an issue rarely knows which runs those are (DKT-244).
func KnownConfigKeys ¶
func KnownConfigKeys() []string
KnownConfigKeys lists the fixed keys, plus the open-ended patterns.
func LeaseTTL ¶
LeaseTTL resolves the effective lease TTL for an executor class, falling back to lease.ttl.default when the class has no entry of its own. An empty class means "use the default".
func LinkDocIssue ¶
LinkDocIssue links a doc to an issue. Returns ErrNotFound if either side is missing; ErrConflict if the link already exists.
func LinkProposalDoc ¶
LinkProposalDoc links a proposal (vote) to a doc. Returns ErrNotFound if either side is missing; ErrConflict if the link already exists.
func LinkProposalIssue ¶
LinkProposalIssue links a proposal to an issue. Returns ErrNotFound if the proposal or issue does not exist. Returns ErrConflict if the link already exists.
func ListAllActivity ¶
ListAllActivity returns every activity_log row ordered by id ASC, for a full export.
func ListAllComments ¶
ListAllComments returns every comment in the database across all issues, ordered by insertion (`id`) for the reason given above ListComments — the same table read through a second query, so the two agree on what "in order" means.
func ListAllDocComments ¶
ListAllDocComments returns every doc_comments row ordered by id ASC, for a full export.
func ListAllDocIssueLinks ¶
ListAllDocIssueLinks returns every doc_issue_links row ordered by (doc_id, issue_id), for a full export.
func ListAllDocRevisions ¶
ListAllDocRevisions returns every doc_revisions row ordered by id ASC, for a full export.
func ListAllDocs ¶
ListAllDocs returns every doc row ordered by id ASC, for a full export.
func ListAllIssueFileMappings ¶
ListAllIssueFileMappings returns all rows from issue_files as IssueFileMapping structs. This is needed by the export command.
func ListAllIssueLabelMappings ¶
ListAllIssueLabelMappings returns all (issue_id, label_id) pairs from the issue_labels table.
func ListAllIssues ¶
ListAllIssues returns every issue in the database, including done issues, with no filters, sorting, or pagination. Labels are hydrated on all results.
func ListAllLabels ¶
ListAllLabels returns a project's labels along with the count of issues using each, sorted alphabetically by name. A zero projectID lists every project's labels.
func ListAllLabelsRaw ¶
ListAllLabelsRaw returns every label as a model.Label object (without issue counts), sorted alphabetically by name.
func ListAllProposalDocs ¶
ListAllProposalDocs returns every proposal_docs row ordered by (proposal_id, doc_id), for a full export.
func ListAllProposalIssues ¶
ListAllProposalIssues returns every proposal_issues row ordered by (proposal_id, issue_id), for a full export.
func ListAllProposals ¶
ListAllProposals returns every proposal row ordered by id ASC, for a full export.
func ListAllVotes ¶
ListAllVotes returns every vote row ordered by id ASC, for a full export.
func ListComments ¶
ListComments retrieves all comments for an issue, ordered by the auto-minted `id` alone — INSERTION ORDER, which for the activity trail is the only order that is always true (DKT-378).
It was `created_at ASC, id ASC`, and the tiebreak was the whole reason: the column is RFC3339 at SECOND resolution and the engine writes several trail comments per transaction, so same-second rows came back in whatever order SQLite chose. But `created_at` stopped being monotonic with insertion when InsertEngineComment began taking the CALLER's `nowMS` instead of reading the clock (that change is right — see its doc comment — and is not what is being undone here). The saga threads ONE `nowMS` through a whole gate execution and `next` drives every ready action step on a single one, so two comments committed seconds apart can carry the same stamp, or the later one an EARLIER stamp. A tiebreak cannot repair a primary key that is itself out of order.
`id` is `INTEGER PRIMARY KEY AUTOINCREMENT`: strictly ascending, never reused after a delete. Sorting by it alone makes the trail read in the order it was written, which is what a narrative of transitions IS.
Clamping the stamp instead (the way engine/event.go's `monotonicAtMS` does for events, against 57s of measured drift) was the other workable remedy and was rejected here: a clamped `created_at` is no longer the transition's own time, which is precisely the invariant threading `nowMS` exists to hold. Events have no monotonic insertion key to fall back on; comments do.
func ListDocComments ¶
ListDocComments returns all comments for a doc ordered by created_at ASC. Returns an empty slice (not nil) when the doc has no comments; returns ErrNotFound when the doc itself is missing.
func ListDocRevisions ¶
ListDocRevisions returns every revision row for the doc, ordered by revision_number ascending. Returns ErrNotFound when the doc itself is missing.
func ListDocs ¶
ListDocs returns docs matching opts, ordered and paginated. Returns the matching rows and the total count before limit/offset.
func ListIssues ¶
ListIssues retrieves issues matching the given filters. It returns the matching issues, the total count of matching rows (ignoring Limit/Offset), and an error.
func ListProjects ¶
ListProjects returns every project, default row first, then by id.
func ListProposals ¶
func ListProposals(db *sql.DB, projectID int, status string, criticality string, domainTag string, limit int) ([]*model.Proposal, int, error)
ListProposals returns proposals with optional filters. It returns the matching proposals and the total count (before limit). A non-zero projectID scopes the list to one project (v12); zero lists every project.
func ListRuns ¶
ListRuns returns runs newest first, and the TRUE total before the limit — the Collection contract (reliability-delta §4.1) needs a total a limit cannot distort, so truncation is computable rather than guessed.
func ListSchemas ¶
ListSchemas returns registered schemas and the TRUE total before the limit — the Collection contract (reliability-delta §4.1) requires a total a limit cannot distort.
func ListWorkflows ¶
ListWorkflows returns registered workflows, newest registration first, and the TRUE total before the limit — the Collection contract (reliability-delta §4.1) requires a total that a limit cannot distort, so truncation is computable rather than guessed.
func LookupIdempotencyKey ¶
LookupIdempotencyKey returns the entity id previously recorded for (scope, key), and whether such a record exists.
A hit means the caller already performed this create — the correct response is to return the original entity with exit 0, not an error. A retried create after a dropped response must succeed, or the key is useless to the caller it exists for.
func LookupIdempotencyKeyTx ¶
LookupIdempotencyKeyTx is LookupIdempotencyKey inside a CALLER'S transaction.
It exists for the same reason the plural form does: internal/db caps the pool at one connection, so a reader holding an open transaction cannot ask this question through the pool without deadlocking permanently. The single-key form is the one a caller wants when it knows exactly which key it is after — a prefix scan to find one row would read the whole family to discard it.
func LookupIdempotencyKeysTx ¶
LookupIdempotencyKeysTx returns every (key, entity id) in one scope whose key starts with prefix, inside a CALLER'S transaction.
It exists for the reader that needs MANY of these at once and holds the single pooled connection while asking: one query over a family of keys rather than one per key, resolved where a pool read would deadlock rather than fail. What a prefix MEANS is the caller's business — this matches bytes.
A `%` or `_` inside prefix is escaped rather than passed through to LIKE, so a caller cannot accidentally widen its own question with a key it built out of data.
func LookupProject ¶
LookupProject resolves an identity to its project WITHOUT creating anything.
It is the read half EnsureProject used to keep private, split out for DKT-58: every invocation must be able to ask "which project is this?", and only some of them may answer "a new one".
func LoopGrantsTx ¶
LoopGrantsTx reads how many ADDITIONAL fix loops an operator has authorized for one issue in one run (DKT-237). Zero on every issue nobody has reopened.
func MarkExpandedTx ¶
MarkExpandedTx stamps an issue as expanded — stage 6's record that this issue's phase is done, so re-activation (RA1) skips it.
func MarkStepAttemptFailedTx ¶
MarkStepAttemptFailedTx counts one claim ended by an explicit `step fail` into the step's outcome breakdown (v23, DKT-490).
It exists because `attempt` spends one count per claim WHATEVER the ending, and a consumer that needed "how many attempts genuinely failed" had nothing to read but the event log — which is prunable, and whose instance labels repeat across a run's issues. Both `step fail` branches call it (the below-budget return to the pool and the exhausted routing): each is a claim whose holder measured its own work and recorded the failure.
It is a COUNTER BUMP ONLY — the lease write it accompanies (ReapStepTx or RetireStepTokenTx) stays the caller's, per ReapStepTx's mechanism/ classification split. It follows SetStepMetadataTx's shape — row_version bump, updated_at_ms — because a counter move is a step-row mutation CAS-guarded readers must see.
func MarkStepClaimReapedTx ¶
MarkStepClaimReapedTx counts one claim reaped WITHOUT a recorded failure into the step's outcome breakdown (v23, DKT-490) — MarkStepAttemptFailedTx's other half, called on the reap paths only: the lazy expiry reap (`next`, `dispatch open`, `claim`) and the forced `step reap`.
The distinction is the whole point. A reaped claim spent an `attempt` without anything failing — the holder went silent, or an operator asserted it dead — and an escalation policy that cannot tell that from a measured failure escalates on it, which is the DKT-490 misread. `step fail`'s return to the pool shares ReapStepTx's row reset but never this counter.
func MarkStepUsageRecordedTx ¶
MarkStepUsageRecordedTx sets `steps.usage_recorded` — the fast path group 2's discrepancy probe reads (§2.3).
Group 1 writes it so the column is populated from the moment the ledger exists: a probe that arrived later and found the column empty on steps that DID record usage would report every one of them as a discrepancy.
WHAT THE COLUMN MEANS is "the ledger question is SETTLED for this step", and it has exactly one reader — engine's `missingUsage` — which is what makes that reading the operative one. There are three ways to settle it: recording usage (budget.go), backfilling it (backfill.go), and an operator's `dispatch close --accept-missing-usage` (DKT-315), which settles the question without answering it. The three are distinguished in the RECORD — the ledger rows exist for the first two and not the third, and the acceptance rides in the `dispatch-closed` event — not in this flag, whose whole job is to let the probe skip a join.
func Migrate ¶
Migrate checks the current schema version and applies any pending migrations sequentially. It is a no-op when already at the latest version.
func MoveIssueProject ¶
func MoveIssueProject( conn *sql.DB, issueID, targetProjectID int, author string, nowMS int64, ) ([]int, error)
MoveIssueProject migrates a ROOT issue — and its entire sub-issue tree — to another project (DKT-27).
Gaps recorded by `step complete --gap-file` land in the run's own project unconditionally, because cwd is the record's only routing; when the surfaced work belongs to another repository, this verb is how the residue gets re-homed without export/import. Labels are re-mapped by NAME into the target project (created there when missing, color preserved) because label rows are per-project; relations and comments ride along untouched — ids are store-wide, so cross-project relations stay resolvable.
It returns the migrated issue ids, the root first.
func NextActionOrdinalTx ¶
NextActionOrdinalTx returns the ordinal a new result for this step+action takes.
Ordinals are per (step, action) and ascend, carrying §4's "flaky-declared re-runs recorded individually" (A8) and, with it, the resume case: a routing stage re-entered after a crash must not collide with the interrupted attempt's row, which the UNIQUE(step_id, action, ordinal) index would refuse.
func NextGateOrdinal ¶
NextGateOrdinal returns the ordinal a new result for this step+gate takes.
Ordinals are per (step, gate) and ascend, which is what carries §4's "flaky-declared re-runs recorded individually": each attempt is its own row, never an overwrite and never an aggregate.
func Open ¶
Open opens or creates the SQLite database at the given path. It sets pragmas for WAL mode, foreign key enforcement, and busy timeout.
func OpenReader ¶
OpenReader opens a SEPARATE connection to the same database file, for best-effort point reads that must never contend with Open's single writer connection.
Open caps its pool at one connection because SQLite is single-writer — but that same cap means a query issued on THAT connection while it holds an open transaction (a caller mid-`BeginTx`) blocks forever: database/sql serializes all use of one *sql.DB through its pool, transaction included, and there is no second connection for the query to check out. That is a guaranteed self-deadlock, not a race — it fires every time a caller formats or looks up something from inside its own open transaction.
WAL mode (set by Open, and already on disk by the time this connects) is what makes a SECOND, independent connection the fix rather than a new hazard: a reader on its own connection sees a consistent snapshot without blocking, or being blocked by, an in-flight writer transaction on Open's connection.
This is for best-effort lookups only — id rendering, not decision logic — exactly the callers that already tolerate a failed read as "unknown" rather than an error.
func OrphanSubIssues ¶
OrphanSubIssues sets parent_id to NULL for all direct children of the given issue. Activity is recorded for each affected child within a transaction.
func PrefixHolder ¶
PrefixHolder reports the id of the project holding prefix, other than `exclude`. It returns 0 when the prefix is free.
func ProjectPrefix ¶
ProjectPrefix reads one project's display prefix — the root hook's second query, feeding model.SetDisplayPrefix before any command runs.
func ProjectRefCounts ¶
ProjectRefCounts reports how many rows in each project-scoped table belong to a project. Tables with no rows are omitted.
func ProposalStatusesTx ¶
ProposalStatusesTx reads many proposals' statuses in ONE query, inside a caller's transaction.
It exists for the reader that holds the single pooled connection and needs a handful of these at once: internal/db caps the pool at one connection, so a pool read from inside an open transaction deadlocks permanently rather than failing, and a per-proposal GetProposal loop from such a reader is the shape that produces it.
Status ONLY. A report that wanted the whole row would be asking for a different function; narrowing it here keeps this from becoming a second GetProposal that drifts from the first.
func ReapStepTx ¶
ReapStepTx returns an expired step to the unclaimed pool: lease cleared, status back to `pending`, `started_ms` cleared, `attempt` LEFT ALONE.
attempt already counted this try — it incremented at claim — so incrementing again here would double-count a single death. §6.3's "returning them to ready with attempt++" is satisfied by the claim's own increment: the trail records one attempt per claim, for all time (claims-leases §5), which is exactly what §9 item 4's "attempt trail is complete" asks for.
It is MECHANISM, not classification: `step fail`'s below-the-budget branch shares it to return a failed step to the pool, so the outcome counters (DKT-490) deliberately live outside it — each caller records what actually ended the claim, MarkStepClaimReapedTx on the reap paths and MarkStepAttemptFailedTx on the failure paths. Folding either bump in here would classify a failure as a reap at exactly the call site that knows better.
This is one of the two places a lease write may happen (§6.3: "lazy reaping confined to next/claim"). No read verb reaches it.
func RecordActivity ¶
RecordActivity logs a field change on an issue.
func RecordIdempotencyKeyTx ¶
RecordIdempotencyKeyTx records (scope, key) -> entityID inside tx.
It MUST be called in the same transaction as the insert it protects, so a crash between the two cannot orphan either. The (scope, key) primary key makes a concurrent duplicate a database constraint rather than an application-level check.
created_at_ms and seq are millisecond-resolution and monotonic per engine-spec.md §5. They live here, in a table created at v5, and never on a pre-existing column — mutating an existing timestamp format would break byte-compatibility for every existing verb.
func RefreshClaimLeaseTx ¶
RefreshClaimLeaseTx recomputes a live claim's `expires_ms` from the caller's own time, guarded by the claim identity (TDD docs/tdd/gates-trust.md §7.6.1.1 LR1/LR2).
WHY IT EXISTS: a claim with pre-gates runs subprocesses between the CAS and the response, and all of that wall time would otherwise be deducted from a lease the caller has not yet received. With a short TTL the caller can be handed an ALREADY-EXPIRED lease, so its first `step complete` fails on a lease it never had a chance to use — a livelock shaped like a too-short TTL but caused by docket's own pre-gate phase.
IT IS A HEARTBEAT BY ANOTHER NAME, NOT A SECOND AUTHORIZATION. The guard is the token hash transaction A wrote: the claimant still holds the CAS, which is exactly the condition the lease model already sanctions for extending a live claim. It can only FAIL, never award a claim (LR4), so the single-winner property is untouched — a claim lost during phase 2 matches zero rows here and the caller gets CONFLICT in the ordinary way.
It reports whether the refresh landed rather than erroring on a miss, because "the claim moved on" is an expected outcome the caller renders as CONFLICT, not a database failure.
func RegisteredVoteRules ¶
RegisteredVoteRules lists the rules that have a threshold set, so a refusal can name the alternatives rather than only the mistake.
func RegisteredVoteRulesTx ¶
RegisteredVoteRulesTx lists the set rules — store-wide plus the project's own — so a refusal inside activation can name the alternatives exactly as `workflow register`'s does.
func ReleaseIssue ¶
ReleaseIssue ends a live lease held by token.
attempt survives: it counts claims for all time, so releasing does not erase the trail of what has already been tried.
func ReleaseStepLeaseTx ¶
ReleaseStepLeaseTx returns a step to the UNCLAIMED pool without touching its status, its attempt, or its saga (DKT-259).
It exists because `pending` and a live lease are a CONTRADICTION that the system had no way to express and every reader disagreed about. `claimPredicate` says a step is claimable only when `owner IS NULL OR owner = ” OR expires_ms <= now`, so a step returned to `pending` with its lease intact is a step the scheduler offers and no claimant can take. What CAN still happen is the worst case: the ORIGINAL holder's token is still valid, so it re-records without re-claiming — and `attempt` increments only at claim, so the second execution lands on the first one's attempt number.
RUN-13 STEP-132 is what that costs. The step ran twice with two gate rounds and two artifact sets, `run report` said `attempts: 1`, and one execution's usage was permanently unrecordable because `usage_ledger`'s `UNIQUE(step_id, attempt, unit)` had already been taken by the other.
It is deliberately NARROWER than ReapStepTx: no status write, no `started_ms` clear. A reap decides what the step becomes; this decides only that nobody holds it, and leaves the caller to say the rest. Two callers with different intentions sharing one helper is how a release grows a status write that surprises one of them.
func RemoveLabelFromIssue ¶
RemoveLabelFromIssue detaches a label from an issue. Returns an error if the label is not found or is not attached to the issue. Activity is recorded and the issue's updated_at timestamp is touched.
func RemoveLabelsFromIssue ¶
RemoveLabelsFromIssue detaches multiple labels from an issue atomically within a single transaction. Returns an error if any label is not found or not attached — no labels are removed on failure. Activity is recorded for each removed label and the issue's updated_at timestamp is touched once.
func RemoveRunIssue ¶
RemoveRunIssue detaches an issue from a run. The caller enforces WHEN this is legal (DKT-53: only while the run is in `planning` — after activation the issue is bound, snapshotted, and possibly scheduled, and a bare row delete would strand all three). Returns ErrNotFound when the issue was not attached, so a typo reads as a miss rather than a success.
func ReportedUsageTx ¶
ReportedUsageTx sums the ledger for ONE unit — B16's `reported`.
One unit, never all of them. §4.5's whole argument is that summing `{tokens: 4000, seconds: 12}` to 4012 would be core asserting those add up.
func ResetStepRetryBudgetTx ¶
ResetStepRetryBudgetTx refreshes a step instance's retry budget — `step resolve --as retry` (§2: "retry = attempts reset") — by moving `attempt_base` to the current attempt. Exhaustion compares `attempt - attempt_base` against `max_attempts`, so the budget reads zero-spent from here on.
`attempt` ITSELF IS NEVER RESET (DKT-86, DKT-90). It is the usage ledger's key half (`UNIQUE(step_id, attempt, unit)`) and §11.4's "claims made against this step, ever": zeroing it here made a retried step's next claim re-mint an attempt number the ledger had already recorded, and the re-execution's genuinely distinct usage became permanently unrecordable through `dispatch backfill-usage`. The retry budget and the ledger key are different facts, and the base column is what lets one column serve as neither's lie.
This is still a DIFFERENT counter from the issue-level `attempt` v6 declared monotonic, on a different entity, and claims-leases §5 anticipated exactly this: the step's budget is live against `max_attempts`, the issue's trail is permanent. Both statements stay true because they are about different rows.
func RestoreWorkflow ¶
RestoreWorkflow clears a version's retirement, returning it to binding.
It exists because retirement is a routing decision and routing decisions get reversed. Without it the only way back would be to re-register the same name at a HIGHER version, which changes what runs pin for a change the operator did not intend to make.
func RetireStepTokenTx ¶
RetireStepTokenTx is §6.8 stage 1's hinge: the token retires when the artifact records.
Retirement is the same state change as release — no owner, no hash, no expiry — so it is the shared clearLeaseTx rather than a second UPDATE that must agree with it. What differs is the AUTHORITY: release is the holder giving the lease back, retirement is the engine taking ownership of a saga that no longer needs one. After this commits, `complete` is AUTH_ERROR (R9) and any engine invocation may resume the saga.
func RunEverDispatchedTx ¶
RunEverDispatchedTx answers §5.8 D2's SCOPE as the 2026-08-03 review fixed it: has this run EVER opened a dispatch?
The question is deliberately about HISTORY rather than about an open manifest. A relay that ever dispatched is accountable for every step of that run, including the ones it spawned outside a manifest — which is the drift D6 exists to catch. A run no relay ever drove has nobody owing usage, which is what keeps a solo rehearsal and a human-only demo refusal-free.
It reads the `dispatches` table rather than the event log because the row outlives nothing: a dispatch that was opened and closed leaves its row, and the row is the cheaper and more direct record of the same fact.
func RunPauseOriginTx ¶
RunPauseOriginTx reads where a run's park was decided. A run that is not parked, and one parked by its own steps, both read `model.RunPauseOriginNone`.
func RunProjectID ¶
RunProjectID resolves the project a run belongs to — the engine's way into the dimension: an engine verb operates on a run or step it was handed, and the run row, not any ambient state, says whose project that work is.
func RunProjectIDTx ¶
RunProjectIDTx is RunProjectID inside a caller's transaction.
func SchemaVersion ¶
SchemaVersion returns the current schema version from the meta table.
func SetConfig ¶
SetConfig stores a validated engine-configuration value. A non-zero projectID writes the project's override; zero writes the store-wide default every project falls back to.
func SetIssueFiles ¶
SetIssueFiles replaces all files for an issue (delete existing, insert new). Activity is recorded showing the change from old files to new files.
func SetIssueResolutionTx ¶
SetIssueResolutionTx records how a routing left an issue.
It touches ONLY `resolution`. `abandon-issue` is deliberate about not forcing the issue's status — the run stopping work is a statement about the run, and closing the issue here would take the operator's triage decision away — so the resolution is an additional fact beside the status, never a replacement for it (DKT-245). `updated_at` moves because the row changed; `version` does not, because this is the machine recording an outcome rather than a CAS-guarded edit competing with a caller's read.
func SetIssueScopeGlobs ¶
SetIssueScopeGlobs stores an issue's declared scope as a JSON array, or NULL when the list is empty. NULL and `[]` are different facts — "no scope declared" versus "declared to touch nothing" — and only NULL is the dormant default every pre-existing row carries.
func SetProjectPrefix ¶
SetProjectPrefix stores a project's display prefix, uppercased. Validation is the caller's (model.ValidateProjectPrefix); this only writes.
func SetRunBudgetTx ¶
SetRunBudgetTx writes a run's cap — `docket run budget --set` (docs/tdd/events-follow.md §7.2).
IT ALWAYS BUMPS `row_version`, whether or not a precondition was given (B-7). Before this verb existed, operations.md §4's runbook told an operator to raise a cap with `sqlite3` and reminded them to increment the column by hand — "or a concurrent `--if-version` check will pass against a row that changed underneath it". Making that structural rather than a step somebody remembers is most of why the verb is better than the edit.
`ifVersion` is the optimistic-concurrency precondition (B-6): non-nil means the UPDATE also requires that version, and a mismatch affects zero rows and becomes ErrVersionConflict. That is the same shape every other CAS verb uses, so `--if-version` behaves identically here and there.
It runs in the CALLER'S transaction because the write and its event are one fact: a cap that moved with nothing in the log explaining why is exactly the gap operations.md §4 warned about when the manual edit was the only option.
func SetRunPauseOriginTx ¶
SetRunPauseOriginTx records WHERE a run's park was decided, and clears the record — `model.RunPauseOriginNone` — when the park ends (DKT-305).
It is written beside a status transition, never on its own: `origin` is only meaningful for a run that IS `waiting-human`, and a stale origin on a running run would make the rollup decline to resume a run nobody parked. Every caller therefore pairs a set with the pause it describes and a clear with the move that ends it, in the same transaction.
It does NOT bump `row_version`, for the same reason `CacheRunFloorTx` does not: the caller's own status write in this transaction already bumped it, and a second bump for one fact would make `--if-version` collide with itself.
func SetRunStatus ¶
SetRunStatus moves a run's status and stamps `updated_at_ms`, bumping the CAS column. `reason` records why a run is parked or abandoned (engine-core §1.1); passing "" leaves the existing reason alone rather than clearing it, so a resume does not erase why the run was paused.
func SetRunStatusTx ¶
SetRunStatusTx is SetRunStatus inside a caller's transaction, for verbs that must write the transition and its event atomically (DKT-86): a status write that commits while its event does not is a transition the ledger never saw.
func SetStepGateTrailTx ¶
SetStepGateTrailTx records the accumulated gate results (TDD §6.1's `gate_trail`, the recorded storage-location deviation: §11.4's `gate result` shape rides here until v8's `gate_results` table).
func SetStepMetadataTx ¶
SetStepMetadataTx records a step's opaque KV bag, already merged.
The merge itself is engine-side, in Go (engine.mergeMetadata), so this stays a plain assignment and no JSON function dependency enters the schema layer. The bag is OPAQUE here as everywhere: this writes bytes it never parses.
It follows SetStepRoutingTx's shape — same row_version bump, same updated_at_ms — because a metadata write is a step-row mutation like any other and CAS-guarded readers must see it move.
func SetStepRoutingTx ¶
SetStepRoutingTx records the routing a step resolved to, alongside its final status. They are ONE statement because they are one fact — the step ended this way, for this reason — and a status without its routing is a step whose disposition cannot be explained.
func SetStepStatusTx ¶
SetStepStatusTx moves a step's status inside the caller's transaction, bumping the CAS column and refreshing `updated_at_ms`.
`activityMS` refreshes the saga's activity clock when non-zero — §6.8's "every stage commit refreshing the step's activity clock". Passing 0 leaves it alone, which is what a non-saga transition wants.
func SortedUnits ¶
SortedUnits orders unit names for a deterministic rendering. It is here rather than at the call site so every renderer of an opaque-unit map orders it the same way (R9), whatever the map carries beside the names.
func SplitNameList ¶
SplitNameList parses a KindNameList value into its names, ignoring the space an author may have written after a comma. An empty value yields no names, which is how every KindNameList key spells "unset".
It is the ONE reader of the encoding, so a caller can never disagree with the validator about where one name ends and the next begins.
func StartStepTx ¶
StartStepTx stamps `started_ms` — the schedule-to-close clock `max_step_duration` is evaluated against (§6.3).
It is set at CLAIM, not at first heartbeat, and only when NULL. That is what makes the bound schedule-to-close rather than activity-to-close: a runaway holder that heartbeats forever cannot push its own deadline out, because the deadline was fixed the moment it took the lease. A re-claim after expiry re-stamps it (the new holder gets its own full budget); the `IS NULL` guard is against the same holder's later stages, not against a new attempt.
func StepAttemptsFor ¶
StepAttemptsFor reports each step's attempt count for the report's R3 line, keyed by step id.
func StepOffScheduler ¶
StepOffScheduler reports whether a status takes a step OUT OF THE SCHEDULER'S ANSWER — a strictly wider question than StepTerminal's (DKT-65).
The two are different because a step's life can pause without ending. A `waiting-human` step is very much alive — an operator will approve, reject, or resolve it — but `next` does not offer it, so anything comparing a stored manifest against a fresh recomputation must expect it to be ABSENT rather than to have moved. `dispatch verify` used StepTerminal for that comparison and consequently reported
does not match its current rendering ... recomputed: (no row at this position)
with exit 4 for a step that had recorded correctly and legitimately parked.
THE ASK NAMED THREE STATUSES AND THE STEP MACHINE HAS ONE. `paused` is a RUN status and `held` is a SAGA STAGE (SagaHeld); neither is ever written to `steps.status`, whose persisted vocabulary is the nine constants above. A paused run removes its steps from the scheduler through their run, not through their own status, so this predicate covers what a step can actually say about itself and the run-level case stays the run's to answer.
It is a SEPARATE predicate rather than a widening of StepTerminal because the callers that ask "is this step over?" — reap, claim, re-offer — must keep getting `false` for `waiting-human`: a step waiting on a person is one an operator is still expected to act on.
func StepStatusCounts ¶
StepStatusCounts returns a run's steps grouped by status, sorted by status so the rendering is stable.
func StepTerminal ¶
StepTerminal reports whether a status ends a step's life. A terminal step is never re-offered by `next`, never claimable, and never reaped.
func UnlinkDocIssue ¶
UnlinkDocIssue removes a doc↔issue link. Returns ErrNotFound if no such link exists.
func UnlinkProposalDoc ¶
UnlinkProposalDoc removes a proposal↔doc link. Returns ErrNotFound if no such link exists.
func UnlinkProposalIssue ¶
UnlinkProposalIssue removes a link between a proposal and an issue. Returns ErrNotFound if the link does not exist.
func UpdateDoc ¶
UpdateDoc applies the changes in upd to the doc with the given ID and appends one revision row capturing the combined change_kind. Returns the new revision number (0 if no revision appended because nothing changed).
Per TDD §5.4 C8, every persisted field change appends one revision; the change_kind is comma-joined ("+" separator) for multi-field edits. Body equality uses strings.TrimRight(s, "\n") so trailing-newline-only edits are no-ops and do NOT append a revision (C6).
Returns ErrNotFound if id does not exist.
func UpdateIssue ¶
UpdateIssue updates an existing issue. Only keys present in the updates map are modified. The updated_at timestamp is always set to the current time. Activity is recorded for each changed field within the same transaction.
Field names are validated against validUpdateFields, but callers are responsible for validating field values (e.g. ensuring status/priority/kind are valid enums) before calling this function.
func UpdateIssueCAS ¶
func UpdateIssueCAS(db *sql.DB, id int, updates map[string]interface{}, changedBy string, ifVersion *int) error
UpdateIssueCAS is UpdateIssue with an optional optimistic-concurrency precondition. When ifVersion is non-nil the update applies only if the issue's current version matches, and returns ErrVersionConflict otherwise. The version is bumped either way, so concurrent CAS writers are detected even when this caller did not supply a precondition.
func UpdateIssueCASLease ¶
func UpdateIssueCASLease(db *sql.DB, id int, updates map[string]interface{}, changedBy string, ifVersion *int, token string) error
UpdateIssueCASLease is UpdateIssueCAS for a verb that ends a lease: when the issue carries a LIVE lease, the caller must hold it (token) and the lease is cleared as part of the same transaction — the issue-level analog of a step's token retiring when its artifact records (engine-spec.md §2).
When no live lease exists the token is ignored entirely and behavior is exactly UpdateIssueCAS's. That is the dormancy guarantee: an unclaimed issue is outside the lease mechanism, so a repo that never claims sees no change on any verb (engine-spec.md §9 item 8).
func UpdateIssueCASNote ¶
func UpdateIssueCASNote(db *sql.DB, id int, updates map[string]interface{}, changedBy string, ifVersion *int, note string) error
UpdateIssueCASNote is UpdateIssueCAS with a note recorded as an issue comment in the SAME transaction as the update (DKT-480), so moving an issue and saying why is one verb: the two either both land or neither does, where the two-verb `issue comment add` + `issue move` form can leave a comment narrating a move that was refused.
An empty note is exactly UpdateIssueCAS, down to the writes it makes. A note with no field to update still records — a move to the status an issue already holds must not swallow the reason it was given — and does NOT bump the issue's version, since a comment never has.
func UsageBudgetUnitTx ¶
UsageBudgetUnitTx reads `budget.usage.unit` — the unit the MEASURED cap counts (DKT-238). Empty leaves that whole dimension dormant.
func ValidateConfigValue ¶
func ValidateConfigValue(spec ConfigSpec, value string) error
ValidateConfigValue checks a value against its key's kind.
func ValidateNameList ¶
ValidateNameList checks a comma-separated list of opaque names: each entry's shape, no empty entry, and no duplicate.
`noun` names one entry in the refusal, for validateName's reason.
func ValidateUnitName ¶
ValidateUnitName is B36's shape rule for a unit name, shared by `--usage`'s parser and `docket config set budget.unit`.
`tokens`, `pages`, and `sheets` are equally valid: core has no list.
func VoteRuleCriticalityKey ¶
func VoteRuleExists ¶
VoteRuleExists reports whether a rule is registered.
A rule EXISTS IFF its threshold is set (§8.3). Criticality has a default, so it cannot be the existence test: a rule with only a criticality set would tally at no threshold at all.
func VoteRuleExistsTx ¶
VoteRuleExistsTx and RegisteredVoteRulesTx are the two above inside a CALLER'S transaction, for S6's auto-registration (docs/tdd/runs-dispatch.md §9.2 F7/F8): the scan validates a workflow's `vote_rule` references with the SAME rules `workflow register` applies, and it does so inside activation's fat transaction, where a pool read would deadlock against the one-connection pool.
They read `meta` directly rather than going through GetConfig, because a vote rule's key is DYNAMIC (`vote.rule.<name>.threshold`) and has no spec row to look up — which is exactly what VoteRuleExists' own `Source == "set"` check is testing for. "Set" here means "a row exists", and that is one query.
func VoteRuleSetElsewhere ¶
func VoteRuleSetElsewhere( db *sql.DB, projectID int, rule string, ) (projects int, value string, err error)
VoteRuleSetElsewhere counts how many OTHER projects configure a rule, and returns one of the values they use (DKT-264).
It exists because "vote_rule X is not registered" was true and useless in the one case that actually happens: a corpus workflow is shared across every project and its thresholds are not, so a freshly-registered project refuses a definition that works everywhere else in the store. The remedy is one store-wide set — the fallback for it has existed all along — and nothing said so, which is how thirteen projects came to hold thirteen per-project copies of the same three thresholds while `config.vote.rule.*` sat empty.
The VALUE is returned so the refusal can quote a real number instead of `<0-1>`. It is one of the values in use rather than a consensus: if two projects disagree, the operator is the one who decides which is right, and inventing an average would be core forming an opinion about a threshold.
The project's OWN row is excluded, so this answers "elsewhere" literally. A caller reaching this has already established the rule is unset here.
func VoteRuleSetElsewhereTx ¶
func VoteRuleSetElsewhereTx( tx *sql.Tx, projectID int, rule string, ) (projects int, value string, err error)
VoteRuleSetElsewhereTx is VoteRuleSetElsewhere inside a caller's transaction, for activation's auto-registration — where a pool read would deadlock against the one-connection pool rather than fail.
func VoteRuleThresholdKey ¶
VoteRuleThresholdKey and VoteRuleCriticalityKey build a rule's two keys, so the string concatenation lives in one place rather than at every reader.
Types ¶
type AckReapResult ¶
type AckReapResult int
AckReapResult is what an acknowledgment did, so the caller can tell A10's idempotent success from A9's forgery refusal without re-querying.
const ( // AckRecorded is the first acknowledgment of a real reap. AckRecorded AckReapResult = iota // AckAlreadyDone is A10: a second ack of the same seq succeeds and changes // nothing, so a relay retrying its hook does not fail. AckAlreadyDone // AckNoSuchReap is A9: the seq names no unacknowledged-or-acknowledged reap // of this run. The caller raises VALIDATION_ERROR — an ack must name a real // reap, and this is the forgery point. AckNoSuchReap )
func AckReapTx ¶
func AckReapTx(tx *sql.Tx, runID int, seq int64, ackedBy string, nowMS int64) (AckReapResult, error)
AckReapTx is A7: CAS on `acked_at_ms IS NULL` for the row whose `reaped_seq` matches, SCOPED TO THE RUN.
The run scope is A9's other half and it is not incidental: an ack naming another run's reap is a forgery in exactly the way naming a non-reap is, and a CAS without the scope would let a relay clear a hold on a run it is not driving.
The three-way return is what makes A10 and A9 distinguishable. A CAS that matched zero rows means EITHER already-acknowledged OR no-such-reap, and those need opposite answers — one is a success, the other is a refusal — so the existence probe runs on the zero-rows path rather than being inferred.
type ActionResultRow ¶
type ActionResultRow struct {
ID int
RunID int
StepID int
// Action is the `action` value — the builtin's name, or the trust entry
// name a non-builtin looked up. Core carries it and never interprets it.
Action string
Ordinal int
Argv []string
Exit *int
DurationMS int64
Output string
Truncated bool
Verdict string
// Builtin marks a result core computed itself. It is the field that lets an
// operator tell `aggregate` — which consults no trust store, by B1 — from a
// user-trusted command that happened to succeed.
Builtin bool
// Reason explains an `unmatched` verdict, a refusal, or a failure, for the
// same four-causes-one-verdict reason the gate row carries one.
Reason string
CreatedAtMS int64
}
ActionResultRow is one recorded action execution — the `action result` shape of docs/tdd/payloads-thresholds.md §6.3, recorded as an amendment.
IT IS `GateResultRow` FOR ACTIONS, deliberately and field for field. The alternative — recording nothing and leaving the routing reason as the only trace — makes an `unmatched` action invisible in exactly the way gates-trust T11's audit argument says it must not be, and makes a failed computation indistinguishable from a failed threshold in a run report. Same shape, same ordinal semantics, same reason discipline, so `run report` (S6) reads one pattern twice rather than two patterns once.
Argv and Exit are POINTERS for the reason they are on a gate result: a builtin spawns nothing (§6.1 B2) and an unmatched action never ran (§6.2 A3), so NULL is the honest encoding of "no process existed". A zero exit on something that did not execute reads as success.
func ActionResultsForStep ¶
func ActionResultsForStep(conn *sql.DB, stepID int) ([]ActionResultRow, error)
ActionResultsForStep returns every recorded result for a step, in insertion order.
type Artifact ¶
type Artifact struct {
ID int
RunID int
StepID int
Kind string
Body string
// Payload is the structured half, validated for SHAPE only at S3 (§6.8
// stage 0); the schema register is S5's. It is JSON text or "".
Payload string
SHA256 string
// Stub is 1 ONLY on artifacts the S3/S4 stub action runner produced, and it
// is `omitempty` for the reason gate_results' own marker is: a result THIS
// stage produces serializes with no `stub` key at all (§6.3 S4).
//
// NOTHING WRITTEN FROM NOW ON SETS IT. The column exists so a migrated
// artifact stays distinguishable forever — the migration marks the rows
// whose payload carries the old `{"stub":true,…}` wrapper and leaves their
// BYTES alone, because rewriting them would destroy the evidence that a
// computation did not run.
Stub bool `json:"stub,omitempty"`
// Supersedes names the artifact this one REVISES, or nil for an original
// (v15, DKT-70).
//
// A held cluster's resolution records a new artifact rather than annotating
// the old one (H13). It shares the original's `kind`; its body is
// regenerated from the resolved payload and its `sha256` covers body and
// payload both (DKT-112), so two links of the chain never share a content
// address while their payloads differ.
//
// The pointer says which of the two is the record of a computation and
// which is the record of a decision, WITHOUT rewriting either. A consumer
// counting work counts `Supersedes == nil`.
Supersedes *int `json:"supersedes,omitempty"`
CreatedAtMS int64
}
Artifact is one `artifacts` row: what a step produced.
func ListRunArtifacts ¶
ListRunArtifacts reads a run's artifacts ordered by id — the tie-break §6.7's resolution order ends on, so the read order and the resolution order agree without a second sort.
func ListRunArtifactsTx ¶
ListRunArtifactsTx is ListRunArtifacts inside a transaction — context assembly's reader, which must see the artifact set as of the claim it is part of.
func ListStepArtifacts ¶
ListStepArtifacts reads the artifacts ONE step produced, ordered by id.
The run-scoped reader above answers "what does this run hold", which is the question context assembly and the run report ask. This answers "what did this step produce", which is the question an operator reading a finished step asks — and which previously had no answer short of opening .docket/issues.db with sqlite.
`step_id` is nullable in the schema (a run-scoped artifact has none), so this deliberately matches on equality and never returns those: an artifact with no producing step is not this step's output.
type CastVoteResult ¶
type CastVoteResult struct {
Vote *model.Vote
ProposalStatus model.ProposalStatus
VotesCast int
VotesRequired int
QuorumReached bool
WeightedScore *float64
}
CastVoteResult holds the outcome of a CastVote operation, including whether quorum was reached and the proposal's updated status.
type ConfigEntry ¶
type ConfigEntry struct {
Key string `json:"key"`
Value string `json:"value"`
Source string `json:"source"` // "set" or "default"
}
ConfigEntry is one key's effective value and where it came from.
func GetConfig ¶
GetConfig returns a key's effective value for one project, and whether it was explicitly set.
Resolution (v12): the project's own override, then the store-wide value, then the builtin default. A zero projectID reads the store-wide value directly. An unset key returns its default with source "default", so a caller can tell "nobody configured this" from "somebody configured this to the same value".
func GetConfigTx ¶
GetConfigTx is GetConfig inside a CALLER'S transaction, for readers that must resolve a key while holding the single pooled connection — the scheduler's snapshot, which is loaded entirely inside one transaction and would deadlock against a pool read rather than fail (the same constraint VoteRuleExistsTx was written for).
It resolves in GetConfig's order — the project's override, the store-wide value, the builtin default — by delegating the shape decisions to the SAME spec lookup and the same key builders. What it does not share is the per-class TTL fallback: nothing reads a lease TTL from inside a transaction, and a second implementation of a fallback is how two readers of one key start disagreeing. A caller that needs one reaches for LeaseTTL outside the transaction, as every current caller already does.
func ListConfig ¶
func ListConfig(db *sql.DB, projectID int) ([]ConfigEntry, error)
ListConfig returns every fixed key's effective value for one project, plus every per-class TTL that has been explicitly set — store-wide or as a project override, the override winning. Sorted for deterministic output.
type ConfigSpec ¶
type ConfigSpec struct {
Key string
Kind ConfigValueKind
Default string
Doc string
}
ConfigSpec describes one engine-configuration key.
func LookupConfigSpec ¶
func LookupConfigSpec(key string) (ConfigSpec, error)
LookupConfigSpec resolves a key to its spec. Per-class lease TTLs (lease.ttl.<class>) are matched dynamically: the class is an opaque string, so the set of valid keys is open by design.
type ConfigValueKind ¶
type ConfigValueKind int
ConfigValueKind classifies a config key for validation at `set` time — so a bad value fails where the user can see it, not later at read time.
const ( // KindDuration is a Go duration string ("15m", "2h30m"). KindDuration ConfigValueKind = iota // KindPositiveInt is an integer >= 1. KindPositiveInt // KindNonNegativeNumber is a number >= 0. KindNonNegativeNumber // KindNonNegativeInt is an integer >= 0. KindNonNegativeInt // KindUnitFraction is a float in (0, 1] — a vote rule's approval // threshold, which the existing `vote create --threshold` already takes in // exactly that range. KindUnitFraction // KindCriticality is one of low|medium|high|critical, the criticality the // existing proposal machinery already understands. KindCriticality // KindRetentionWindow is a duration that may also be ZERO, where zero means // "retain everything" rather than "retain nothing" // (docs/tdd/events-follow.md §5.3 P13). // // It is a separate kind from KindDuration because that one rejects zero — // correctly, since a zero lease TTL would expire a claim the instant it was // made. Here zero is the DEFAULT and the safe end of the range: a retention // window nobody set must protect every event, not expose every event. KindRetentionWindow // KindUnitName is an OPAQUE unit name, or empty. Core never enumerates // units and never has a default one, so the only validation possible is the // shape a name must have to be usable as a ledger key — the same caps // `--usage` applies to the names it records (§4.9 B36): at most 64 bytes, // printable ASCII, no whitespace. Anything beyond that would be core // deciding which units exist. KindUnitName // KindName is a single OPAQUE name, or empty — KindUnitName's rule without // the unit vocabulary, for a key whose value names something other than a // recorded unit. KindName // KindNameList is a comma-separated list of OPAQUE names, or empty. // // It validates each entry's SHAPE and nothing else, for the same reason // KindUnitName does: core never enumerates the members of such a list and // holds no opinion about what any of them denotes. What it does check is // that the list can be split back into the names that were put in — no // empty entry, and no duplicate, since a caller that counts the entries // would count a repeat twice and demand a decision from a name that can // only make one. KindNameList // KindBool is a boolean, in any of strconv.ParseBool's spellings // ("true"/"false", "1"/"0", "t"/"f"). Stored and read back verbatim, like // every other kind — a reader that needs the parsed bool calls ParseBool // itself, the same way a reader of KindDuration calls ParseDuration. KindBool )
type CycleError ¶
type CycleError struct {
Path []int
}
CycleError wraps ErrCycleDetected and carries the path of IDs forming the cycle.
func (*CycleError) Error ¶
func (e *CycleError) Error() string
func (*CycleError) Unwrap ¶
func (e *CycleError) Unwrap() error
type Dispatch ¶
type Dispatch struct {
ID int
RunID int
Status string
// OpenedSeq is the event seq at open time — the manifest's place in the log,
// and §6's boundary for "reaps this relay has not yet seen" (P2).
OpenedSeq int64
ExpiresMS int64
ClosedAtMS *int64
CloseReason string
CreatedAtMS int64
RowVersion int
}
Dispatch is one manifest's row.
func GetDispatchTx ¶
GetDispatchTx reads one manifest by id, whatever its status — the read a closing verb makes after its CAS to report what actually happened.
func OpenDispatchTx ¶
OpenDispatchTx reads the run's open manifest, or ErrNoOpenDispatch.
It is the probe P24 runs and the one D2 asks about, so it is ONE query with one definition of "open": the status the partial index keys on.
func (*Dispatch) Expired ¶
Expired reports whether the manifest has outlived its TTL (P12).
It is a method on the row rather than a query so the ONE definition of expiry serves the lazy abandon, the refusal's message, and any read verb that renders a manifest — three callers that must not be able to disagree about whether a dispatch is still live.
type DispatchRow ¶
DispatchRow is one stored manifest row: the canonical bytes, their hash, and the identity they describe.
Both `RowJSON` and `RowSHA256` are stored. `verify` derives its stageless comparison from `RowJSON`, first asserting the bytes still hash to `RowSHA256` — the integrity check on the stored pair — and the spawn guard compares proposed rows against `RowSHA256` verbatim, stage included. The bytes are what a refusal SHOWS the operator: the differing row rather than a report that two rows differ.
func ListDispatchRowsTx ¶
func ListDispatchRowsTx(tx *sql.Tx, dispatchID int) ([]DispatchRow, error)
ListDispatchRowsTx reads a manifest's rows IN POSITION ORDER.
The order is the manifest (P1: "records the resulting rows in order"), so it is an ORDER BY rather than an insertion-order assumption: `verify` compares position by position, and a row set that came back in rowid order would make the comparison depend on how SQLite happened to store them.
type DocListOptions ¶
type DocListOptions struct {
ProjectID int // scope to one project (v12); 0 = every project
Types []string
Statuses []string
Author string
Sort string
SortDir string
Limit int
Offset int
}
DocListOptions holds filtering, sorting, and pagination options for ListDocs and ListDocsWithCounts. Mirrors ListOptions/issues but with the doc-table columns.
type DocSummary ¶
DocSummary is a row from ListDocsWithCounts: a Doc plus the JOIN-derived revision count and current revision number. Returned shape per TDD §6.3.
func ListDocsWithCounts ¶
func ListDocsWithCounts(db *sql.DB, opts DocListOptions) ([]*DocSummary, int, error)
ListDocsWithCounts returns DocSummary rows including JOIN-derived revisions_count and current_revision in a single query (TDD §5.4 S1 — no N+1). Returns total count (before limit) as the second value.
type DocUpdate ¶
DocUpdate is the set of fields UpdateDoc may change. Nil pointers mean "leave unchanged"; non-nil with the current value is detected as a no-op (body equality additionally applies trailing-newline trimming — C6).
type GateResultRow ¶
type GateResultRow struct {
ID int
RunID int
StepID int
Gate string
Ordinal int
Argv []string
Exit *int
DurationMS int64
Output string
Truncated bool
Verdict string
Pre bool
// Stub is 1 ONLY on rows migrated from an S3 `gate_trail`. Nothing this
// stage records sets it, which is what makes the S3->S4 window auditable
// after the fact.
Stub bool
// StubEntry is 1 when the trust entry that authorized this gate declared
// itself a PLACEHOLDER (DKT-265) — an echo, a `/usr/bin/true`, a script
// that exits 0 without looking at anything.
//
// IT IS A DIFFERENT FACT FROM `Stub` AND THE NAMES ARE NOT AN ACCIDENT.
// `Stub` is about WHICH ERA of this codebase produced the row; `StubEntry`
// is about WHAT RAN. A row can be either, both, or neither, and a reader
// asking "did a secret scan actually happen" is asking the second question
// only.
StubEntry bool
// Reason explains an `unmatched` verdict or a timeout (§6.3, amendment A6).
// An unmatched verdict has four distinct causes needing four different
// remedies, and without this field they render identically to an operator.
Reason string
CreatedAtMS int64
}
GateResultRow is one recorded gate execution — §11.4's `gate result` shape as it lives in the v8 table (TDD docs/tdd/gates-trust.md §4.2, §6.1).
Argv and Exit are POINTERS because NULL is meaningful here and zero is not: an `unmatched` gate never ran, so it has no argv and no exit code. Recording `exit = 0` for a process that does not exist is exactly the confusion the gate-forgery threat (T11) exists to prevent — a zero exit reads as success. NULL is the honest encoding of "no process existed".
func GateResultsForStep ¶
func GateResultsForStep(conn *sql.DB, stepID int) ([]GateResultRow, error)
GateResultsForStep returns every recorded result for a step, in insertion order.
func GateResultsForStepTx ¶
func GateResultsForStepTx(tx *sql.Tx, stepID int) ([]GateResultRow, error)
GateResultsForStepTx is GateResultsForStep inside a transaction, for the readers that run within one.
type ListOptions ¶
type ListOptions struct {
ProjectID int // scope to one project (v12); 0 = every project
Statuses []string // filter by status (multiple = OR)
Priorities []string // filter by priority (multiple = OR)
Labels []string // filter by label name (multiple = AND)
Types []string // filter by kind (multiple = OR)
Assignee string // filter by assignee
ParentID *int // filter by parent issue ID
RootsOnly bool // only issues with no parent
// RunID scopes the listing to one run's ROSTER — the issues bound to it in
// `run_issues` (DKT-405); 0 = every issue. It is the same membership
// ListRunIssues reads, expressed as a filter so it composes with the other
// filters, the sort, and the pre-limit COUNT rather than being intersected
// afterwards in the caller.
RunID int
IncludeDone bool // include done status (default: exclude)
Sort string // field name
SortDir string // "asc" or "desc"
Limit int // max results
Offset int // for pagination
}
ListOptions holds filtering, sorting, and pagination options for ListIssues.
type MetadataKeyRollup ¶
type MetadataKeyRollup struct {
Key string `json:"key"`
Values []MetadataValueCount `json:"values"`
}
MetadataKeyRollup is one metadata key and every distinct value recorded under it, with counts.
func MetadataRollup ¶
func MetadataRollup(db *sql.DB, runID int) ([]MetadataKeyRollup, error)
MetadataRollup is R7: step `metadata` keys rolled up to their distinct values with counts, VERBATIM AND UNINTERPRETED.
THIS IS THE GENERICITY LINE AT ITS THINNEST, so it is worth stating what the implementation does and does not do. It groups by key and by value, both as OPAQUE STRINGS, and reports counts. It does not know that any particular instance puts anything in particular there: it reports `{"tier": {"a": 3, "b": 1}}` for exactly the same reason it would report `{"desk": {"front": 3, "back": 1}}`.
TestMetadataRollupReadsNoKey asserts the implementation contains no key-name literal, which is the mechanical form of that promise — a rollup that special-cased one key would be core having an opinion about what a workflow author's bag of strings means.
The nesting is key -> value -> count, and both levels are returned SORTED so R9's determinism holds through a two-level structure that a naive implementation would emit in map order.
func VoteMetadataRollup ¶
func VoteMetadataRollup(db *sql.DB, scope, prefix string) ([]MetadataKeyRollup, error)
VoteMetadataRollup is MetadataRollup over CAST VOTES: every metadata bag the run's vote-step proposals collected, keys to distinct values with counts, verbatim and uninterpreted (DKT-71).
It exists because vote seats are the one spend the ledger cannot see: a vote step is never claimed, so nothing accrues usage rows for it, and until v13 nothing recorded what model a seat resolved to. The `--metadata` claim on `vote cast` closed the write half; this is the run-level read that makes routing drift measurable again — the same question the step rollup answers, asked of the casts.
The proposals are selected by the caller-supplied idempotency scope and prefix — the engine owns that spelling (voteIdempotencyPrefix) and this package must not restate it. The same genericity line holds here as in MetadataRollup: keys and values are opaque strings, counted, never interpreted, and TestMetadataRollupReadsNoKey covers both readers.
type MetadataValueCount ¶
MetadataValueCount is one value of one key, and how many steps carried it.
type Pin ¶
type Pin struct {
RunID int
Kind string
Ref string
SHA256 string
// Bytes is the pinned content's size, carried IN MEMORY ONLY during
// activation — it is not a column and is not persisted.
//
// It exists so the closure-size arithmetic (§1.5) can count a
// declared packet file without opening it a second time: the activation
// scan already read the bytes to hash them, and re-reading at expansion
// would both cost an extra pass and open a window where the two reads
// disagree.
Bytes int
}
Pin is one `pins` row.
type ReapAck ¶
type ReapAck struct {
ID int
RunID int
StepID int
Class string
Instance string
// ReapedSeq is the `seq` of the `lease-reaped` event this ack is OF. §2 says
// the relay acknowledges "the `reaped` event", so the event's seq is the
// ack's identity — which makes the ack idempotent for free (C7) and a forged
// ack impossible to construct without naming a real reap.
ReapedSeq int64
AckedAtMS *int64
AckedBy string
CreatedAtMS int64
}
ReapAck is one unacknowledged-or-acknowledged reap.
func UnacknowledgedReapsTx ¶
UnacknowledgedReapsTx lists a run's outstanding reaps, oldest first.
It is the query behind A12's predicate AND behind the human-readable reason `next` prints, so the two cannot name different rows: a headroom denial with nothing running is baffling unless the same rows that caused it are the ones reported.
The `instance` join is for the MESSAGE. The predicate needs only the class, but a refusal that named `STEP-7` rather than `implement@0` would make an operator look the step up to understand a sentence about their own run.
type ResultTrailRow ¶
type ResultTrailRow struct {
Instance string `json:"step"`
StepID string `json:"step_id"`
Issue string `json:"issue"`
Name string `json:"name"`
Ordinal int `json:"ordinal"`
Verdict string `json:"verdict"`
Reason string `json:"reason,omitempty"`
Output string `json:"output,omitempty"`
}
ResultTrailRow is one step's one result — the per-step trail R4 and R5 carry beside their counts.
StepID and Issue joined on (DKT-77): instance names collide across issues in one run — two issues running the same workflow both have an `implement@0` — so a trail keyed on instance alone was unattributable. Output rides ONLY on rows that did not pass, as a bounded tail: a failing gate's diagnosis used to require re-running it out-of-band, which is at its most expensive exactly where a false failure blocks a security path.
func ActionTrail ¶
func ActionTrail(db *sql.DB, runID int) ([]ResultTrailRow, error)
type RunBudgetFacts ¶
RunBudgetFacts are the stored budget columns a read verb renders: the cached floor and the breach reason, if any.
func RunBudgetFactsFor ¶
func RunBudgetFactsFor(db *sql.DB, runID int) (RunBudgetFacts, error)
RunBudgetFactsFor reads the v10 columns for one run.
func RunBudgetFactsTx ¶
func RunBudgetFactsTx(tx *sql.Tx, runID int) (RunBudgetFacts, error)
RunBudgetFactsTx is RunBudgetFactsFor inside a caller's transaction, for the one writer that must read the breach record while deciding whether a cap change resolved it (DKT-80). The pool is capped at one connection, so a pool read from inside a transaction deadlocks rather than merely racing.
type RunContext ¶
type RunContext struct {
ExecRoot string
Branch string
CommitSHA string
Hostname string
// UsageBudget is the cap over MEASURED usage (DKT-238), resolved at
// `run start` from `--usage-budget` or `budget.usage.default` and pinned
// on the row for the same reason `budget` is: a config change must not
// re-cap a live run.
UsageBudget float64
}
RunContext is the execution context stamped on a run at creation (G8): which checkout, on which branch and commit, on which machine. Empty fields record as empty — a run started outside a checkout has no branch, and inventing one would make the record an opinion.
type RunIssue ¶
type RunIssue struct {
RunID int
IssueID int
WorkflowID *int
BodySnapshot string
BodySHA256 string
IssueSnapshot string
ExpandedAtMS *int64
// LoopCount is the ISSUE's loop counter (§11.3 (1), TDD §7.1). It lives here
// rather than on `steps` because the spec says "the issue's loop counter":
// one issue loops, and `max_fix_loops` bounds that one number.
LoopCount int
}
RunIssue is one `run_issues` row: an issue's membership in a run, its binding, and the activation-time snapshots the context bundle reads.
func GetRunIssueTx ¶
GetRunIssueTx reads one `run_issues` row — the issue's binding, snapshots, and loop counter — inside a transaction.
The loop routing needs the counter under the SAME lock that increments it, so this reads inside the caller's transaction rather than through a *sql.DB helper. (It also must: internal/db caps the pool at one connection, so a pool read from inside a transaction deadlocks — the failure TestNoPoolReadsInsideTransactions exists to prevent.)
func ListRunIssues ¶
ListRunIssues returns a run's issues in ascending issue order, so every caller walks them the same way and two activations of the same inputs agree.
func ListRunIssuesTx ¶
ListRunIssuesTx is ListRunIssues inside a transaction.
type RunListOptions ¶
type RunListOptions struct {
// ProjectID scopes the list to one project (v12); 0 = every project.
ProjectID int
// ActiveOnly restricts the list to runs that are not terminal — the
// `--active` flag. `planning` counts as active: a run that exists but has
// not been activated is still live work an operator is mid-way through.
ActiveOnly bool
Limit int
}
RunListOptions filters `run status` without an id.
type SchemaListOptions ¶
type SchemaListOptions struct {
// ProjectID scopes the list to one project's visible schemas — its own
// plus builtins (v12); 0 = every project's.
ProjectID int
Name string
Limit int
}
SchemaListOptions filters `schema list`.
type Step ¶
type Step struct {
ID int
RunID int
IssueID int
WorkflowID int
StepName string
Ordinal int
SiblingIndex *int
Instance string
Kind string
Executor string
Class string
Status string
Attempt int
// AttemptBase is the attempt count the retry budget counts FROM (v16,
// DKT-86/DKT-90). `attempt` is monotonic for the step's whole life — it is
// the usage ledger's key half and §11.4's "claims made against this step,
// ever" — so `step resolve --as retry` refreshes the budget by moving this
// base to the current attempt instead of zeroing the counter. Exhaustion
// compares Attempt-AttemptBase against MaxAttempts.
AttemptBase int
// FailedAttempts and ReapedClaims are the OUTCOME breakdown of the claims
// Attempt counts (v23, DKT-490): how many ended in an explicit `step fail`,
// and how many were reaped without one (lease expiry, `max_step_duration`,
// `step reap`). Attempt alone cannot answer that — it spends one count per
// claim whatever the ending — and a consumer that read it as "attempts
// that failed" escalated on claims that never failed at all. A claim that
// RECORDED counts in neither, and `step resolve --as retry` touches
// neither. FailedAttempts+ReapedClaims never exceeds Attempt; the
// remainder is live claims, recorded completions, and pre-v23 history
// (the migration back-fills nothing — see migrateV22ToV23).
FailedAttempts int
ReapedClaims int
MaxAttempts *int
ExpectedCost float64
Owner string
TokenHash string
ExpiresMS int64
StartedMS *int64
ActivityMS *int64
SagaStage string
GateTrail string
Routing string
Metadata string
ContextBytes int
// Materialized reports a step the ENGINE minted rather than one the pinned
// definition declares — the `<step>-held` human step a tripped `hold_spread`
// creates (payloads-thresholds §7.7 H4). Its spec is synthesized from the
// pinned bytes, so nothing unpinned enters a run; the column exists so a
// reader can tell a declared question from a computed one.
Materialized bool
// UsageRecorded is the v10 column group 1 writes on every `--usage` and
// group 2's discrepancy probe reads (§2.3, §5.8 D2). It is the LEDGER'S OWN
// answer to "did this step report", written in the recording transaction, so
// the probe needs no join per step.
UsageRecorded bool
// WorkRoot is the checkout the step's work happened in — recorded at
// complete/record via --worktree (v12, G8), read by the diff stage so a
// resumed saga diffs the tree the work touched, not the resumer's cwd.
WorkRoot string
CreatedAtMS int64
UpdatedAtMS int64
RowVersion int
}
Step is one `steps` row, read whole. It is a superset of StepRow — which activation writes — because phase 3 reads the lease, saga, and routing columns activation never fills.
func GetStepTx ¶
GetStepTx is GetStep inside a transaction — every saga stage's reader, so the stage's guard and its mutation see one consistent row.
func ListActiveRunSteps ¶
ListActiveRunSteps reads the steps of every non-terminal run — `guard stop`'s reader (§6.12) and the scope-conflict check's, since a claimed step in ANOTHER active run excludes just as surely as one in this run.
func ListRunSteps ¶
ListRunSteps reads every step of a run, ordered by (issue, id) — creation order within an issue, which is declaration order, which is what the topology goldens compare against (§8.3).
func ListRunStepsTx ¶
ListRunStepsTx is ListRunSteps inside a transaction — the readiness predicate's reader, which must see one consistent snapshot of the run because R3 (predecessors done) and R4 (scope non-overlap) are questions about the same set of rows at the same instant.
func (*Step) InSaga ¶
InSaga reports whether the step is mid-saga — recorded but not yet routed. Such a step needs NO lease to advance: the token retired at stage 1, so any later engine invocation may resume it (§6.8).
type StepRow ¶
type StepRow struct {
ID int
RunID int
IssueID int
WorkflowID int
StepName string
Ordinal int
SiblingIndex *int
Instance string
Kind string
Executor string
Class string
Status string
MaxAttempts *int
ExpectedCost float64
Metadata string
ContextBytes int
// Materialized marks a step the engine minted rather than one expansion
// read out of the pinned definition (payloads-thresholds §7.7 H4). Ordinary
// expansion never sets it, so every row activation writes reads 0.
Materialized bool
CreatedAtMS int64
}
StepRow is one `steps` row as activation writes it. Phase 3 adds the lease, saga, and routing readers; expansion fills only the identity, the shape, and the initial status.
type StepUsageRow ¶
type StepUsageRow struct {
Step string `json:"step"`
Instance string `json:"instance"`
Attempt int `json:"attempt"`
Unit string `json:"unit"`
Quantity float64 `json:"quantity"`
Source string `json:"source"`
}
StepUsageRow is one row of the per-step ledger view: which step, which attempt, which unit, how much, and who measured it.
func UsageByStep ¶
func UsageByStep(db *sql.DB, runID int) ([]StepUsageRow, error)
UsageByStep lists the run's ledger row by row, joined to each step's instance, ordered by (step, attempt, unit).
It exists because nothing exposed per-step usage. `run report` rolled the ledger up per UNIT only, so the one question a back-fill's refusal sends you to answer — WHICH steps already have usage recorded — could not be answered from any read verb, and conductors hand-filtered batches by trial and error (DKT-241). The rollup is still the headline; this is the detail behind it.
ORDERED BY A TOTAL KEY (R9), like UsageByUnit and for the same reason: two reports of the same rows must be byte-identical.
type UnitTotal ¶
type UnitTotal struct {
Unit string `json:"unit"`
Quantity float64 `json:"quantity"`
Rows int `json:"rows"`
}
UnitTotal is one unit's rollup for the report (R2's reported-per-unit line).
func UsageByUnit ¶
UsageByUnit rolls the run's ledger up per unit, ordered by unit name.
ORDERED BY A TOTAL KEY, never by map iteration (R9): the report is deterministic given the same rows, for the same golden-stability reason `referencedSchemas` is ordered.
func VoteUsageRollup ¶
VoteUsageRollup sums the vote_usage ledger per unit over the run's vote-step proposals (DKT-95) — UsageByUnit's question, asked of the seats. The proposals are selected by the same caller-supplied idempotency scope and prefix VoteMetadataRollup reads by, and units stay opaque: summed and counted, never interpreted.
type UsageRow ¶
type UsageRow struct {
RunID int
StepID int
Attempt int
Unit string
Quantity float64
// Source is engine-core §7's "source recorded". Core writes
// UsageSourceReported and nothing else at this stage; the column exists so a
// harness back-filling from its own journal can record its own source later
// without a migration.
Source string
}
UsageRow is one `usage_ledger` row: what a step reported, in one unit, on one attempt.
func ParseUsage ¶
ParseUsage is B33, B35, and B36: `--usage '{"unit": n, …}'` parsed as a JSON object of string -> number, capped, with every refusal NAMING what it refused. It is the ONE implementation of the usage-report contract: the step ledger's writer (`step complete --usage`, via the engine) and the vote ledger's (`vote cast --usage`, DKT-95) both parse through it, so the two flags cannot drift on what a valid report is.
It returns the units in SORTED ORDER so a call's ledger rows land in a deterministic order — the same total-key discipline the report's rendering uses (R9), applied at the writer so two identical calls produce identical row ids.
Refusals are plain errors naming only the offending key or value, never a flag: each caller prefixes its own surface's name and shape (the engine as a VALIDATION_ERROR on `--usage`, the CLI as a `--usage` flag refusal), so this package never authors wording for a surface it cannot see.
type VerdictCount ¶
type VerdictCount struct {
Name string `json:"name"`
Pass int `json:"pass"`
Fail int `json:"fail"`
Unmatched int `json:"unmatched"`
// Skipped counts rows that MEASURED NOTHING (DKT-254). Before this column
// a skipped row landed in none of the three above and vanished from the
// report entirely — an absence that reads as green, which is the exact
// inversion of what the verdict means.
//
// It partitions with pass/fail/unmatched: every row lands in exactly one
// of the four. (`Stub` below does not — see its own note.)
Skipped int `json:"skipped"`
// Stub counts rows whose authorizing trust entry declared itself a
// PLACEHOLDER (DKT-265). It is a count of ROWS, not of passes, so it
// overlaps the three columns above rather than partitioning with them —
// `pass 3, stub 3` means every one of those passes was hollow.
//
// It is zero for actions, which have no trust-entry stub declaration. That
// is an honest zero and not a missing feature: an action's `builtin` column
// already says whether core computed it.
Stub int `json:"stub"`
}
VerdictCount is one subject's pass/fail/unmatched tally.
func ActionRollup ¶
func ActionRollup(db *sql.DB, runID int) ([]VerdictCount, error)
ActionRollup is the same rollup over `action_results` (R5).
The same shape, deliberately: gates and actions are the two execution seams and their results carry the same verdict vocabulary, so a reader who understands one section understands the other. Reusing the query rather than writing a second is what keeps the two from drifting into different definitions of "pass".
`skipped` is counted for actions too, and is always zero there today: no action path produces that verdict. That is an honest zero and the right shape — the alternative, omitting the column for one of the two seams, is how the two definitions of "what outcomes exist" start to drift.
func GateRollup ¶
func GateRollup(db *sql.DB, runID int) ([]VerdictCount, error)
GateRollup counts a run's gate results per gate name (R4).
type VoteUsageCoverage ¶
type VoteUsageCoverage struct {
// Casts is every vote cast on this run's vote-step proposals.
Casts int `json:"casts"`
// Reported is the subset that recorded at least one vote_usage row.
Reported int `json:"reported"`
}
VoteUsageCoverage is how many of a run's seat-casts reported their spend, and how many did not (DKT-257).
func VoteUsageCoverageFor ¶
func VoteUsageCoverageFor(db *sql.DB, scope, prefix string) (VoteUsageCoverage, error)
VoteUsageCoverageFor counts a run's seat-casts and how many reported spend.
It exists because a MISSING report and a FREE panel were the same number (DKT-257). The vote_usage ledger has existed since v14 and held zero rows for an entire store epoch while 21+ seat-votes did real verification work: the table, the writer, and the rollup were all wired, and nothing supplied a number, so `vote_usage: 0` read as "this panel cost nothing".
In-wave panels ARE counted, because their seats run as steps — RUN-22 STEP-379/381 journaled ~39.5k output tokens to step_usage. Conductor-side panels of identical shape recorded 287/0. Roughly 40k tokens per panel that the run's budget never saw, and the only difference was WHERE the ballot executed, not how much work it did.
Core cannot observe a conductor-side seat's spend, so it cannot fix the number. What it can do is stop the silence from looking like a zero, which is this: a run whose seats all reported reads `12/12`, and one whose seats reported nothing reads `0/12` instead of an absent section.
func (VoteUsageCoverage) Silent ¶
func (c VoteUsageCoverage) Silent() int
Silent is the gap: seats that deliberated and reported nothing.
type WorkflowListOptions ¶
type WorkflowListOptions struct {
// ProjectID scopes the list to one project (v12); 0 = every project.
ProjectID int
Name string
Limit int
}
WorkflowListOptions filters `workflow list`.