Documentation
¶
Index ¶
- Constants
- Variables
- func ActiveStatuses() []string
- func AfterMutation(parent context.Context, r StatusReader, deadlines Deadlines, ...) error
- func ApplyDeadlines(tp TaskProvider, d Deadlines)
- func AsTimeout(provider, op string, kind OpKind, deadline time.Duration, err error) error
- func AttachAuthoritativeReceiver(tp TaskProvider, store FenceStore)
- func AttachCoordinatorMinter(k *KaneoProvider, m *FenceBrokerMinter) error
- func AttachFenceHeaders(ctx context.Context, set func(key, value string))
- func AwaitLeaseAck(worktreePath string, cap *LeaseCapability, timeout time.Duration) error
- func BoundOp(ctx context.Context, d Deadlines, op OpKind) (context.Context, context.CancelFunc)
- func CanonicalClaimDir(startDir, override string) (string, error)
- func CanonicalRepoIdentity(startDir string) (string, error)
- func CapabilityPresent(worktreePath string) (bool, error)
- func ClaimRequestFor(key claim.LeaseKey, ownerID, role string) claim.ClaimRequest
- func ClassifyContextErr(ctx context.Context, provider, op string, kind OpKind, deadline time.Duration) error
- func ClearCoordinatorMinter(k *KaneoProvider)
- func CommentOpTaggedBody(body, opID string) string
- func CompareRefs(a, b string) int
- func ConfigureKaneoFenceBroker(k *KaneoProvider, client *FenceBrokerClient) error
- func DecodeJSONBytes(statusCode int, body []byte, v interface{}) error
- func DecodeJSONResponse(resp *http.Response, v interface{}) error
- func DefaultOwnerID() string
- func DetectErrorBody(statusCode int, body []byte) error
- func EmbedStatusReceiptInDescription(desc, receiptJSON string) string
- func EncodeRevision(t *Task) claim.ProviderRevision
- func EncodeStatusReceiptJSON(r *StatusMutationReceipt) (string, error)
- func EnsureTaskRoleLabel(ctx context.Context, p TaskLabelProvider, targetID, role string) (err error)
- func EnsureTaskRoleLabelWithOptions(ctx context.Context, p TaskLabelProvider, targetID, role string, ...) error
- func Handback(ctx context.Context, mgr *claim.ClaimManager, key claim.LeaseKey, ...) error
- func HandbackDoneMatches(worktreePath, owner string, gen int64) bool
- func Heartbeat(ctx context.Context, mgr *claim.ClaimManager, key claim.LeaseKey, ...) (*claim.Lease, error)
- func HerdrSessionOwnerID(tabID, paneID, agentName, receiptToken string) string
- func HoldIdentitiesFor(key claim.LeaseKey, role string) []lifecycle.HoldIdentity
- func IsActiveStatus(raw string) bool
- func IsAmbiguous(err error) bool
- func IsClaimConflict(err error) bool
- func IsRecoverableTimeout(err error) bool
- func IsTimeout(err error) bool
- func KeyFromCapability(cap *LeaseCapability) claim.LeaseKey
- func KnownImplementationRoles() []string
- func LeaseKey(repo, providerType, projectID, taskRef string) claim.LeaseKey
- func MatchCommentOp(liveBody, wantBody, opID string) bool
- func MatchStatusOpEvidence(liveBody, opID, status string) bool
- func MintChallenge() (string, error)
- func MintCommentCapability(secret, instanceID string, key claim.LeaseKey, ...) (string, error)
- func MintMutationCapability(secret, instanceID string, key claim.LeaseKey, ...) (string, error)
- func MustAcquireLease(t testing.TB, stack *ClaimStack, key claim.LeaseKey, ...) *claim.Lease
- func NormalizeStatus(raw string) string
- func ObservedLabelGeneration(ctx context.Context, p TaskLabelProvider, ids ...string) (string, error)
- func OperationKindComment(body string) string
- func OperationKindStatus(status string) string
- func PaginationTerminalError(d PageDecision) error
- func ParseHandbackDoneLegacyGen(s string) (int64, error)
- func ParseStatusReceiptFromDescription(desc string) string
- func PrepareRuntimeDefaults(startDir string) error
- func ProcessOwnerID() (string, error)
- func ProvisionSharedFenceForTest(t interface{ ... }, dir string)
- func ReadFenceVolumeSeal(claimDir string) (string, error)
- func ReadVolumeSeal(claimDir string) (string, error)
- func ReconcileStatus(ctx context.Context, r StatusReader, deadlines Deadlines, ...) error
- func RefreshLeaseCapabilityExpiry(worktreePath string, owner string, gen int64, expiresAt time.Time) error
- func RegisterProjectImplementationRoles(roles []string)
- func RemoveLeaseCapability(worktreePath string) error
- func RepairTaskRoleLabel(ctx context.Context, p TaskLabelProvider, sourceID, targetID, role string) (err error)
- func RepairTaskRoleLabelWithOptions(ctx context.Context, p TaskLabelProvider, sourceID, targetID, role string, ...) error
- func RequireTaskRole(task *Task, role string) (string, error)
- func ResolveKaneoAPIKey(override string) string
- func ResolveKaneoProfileCred() kaneoOriginCred
- func ResolveKaneoProjectID(rootDir string) string
- func ResolveTaskRole(task *Task, role string) string
- func RetryRead(ctx context.Context, policy RetryPolicy, fn func(context.Context) error) error
- func RunCLIOutput(ctx context.Context, name string, args ...string) ([]byte, error)
- func ScrubWorkerMintEnv()
- func StatusOpEvidenceBody(opID, status string) string
- func StatusReceiptKey() ([]byte, error)
- func StripStatusReceiptFooter(desc string) string
- func TaskHasVerifiedStatusReceipt(t *Task, wantTask, wantOp, wantStatus string, wantFence int64) bool
- func TaskOwnershipRole(task *Task, preferred string) (string, error)
- func TryAutoHandback(ctx context.Context, mgr *claim.ClaimManager, worktreePath string) error
- func ValidRelationType(t RelationType) bool
- func ValidateSharedMarker(dir string) error
- func VerifyFieldReadback(taskID, field, expected, actual string, fold bool) error
- func VerifyLeaseAck(cap *LeaseCapability, ack *LeaseAck) error
- func VerifyProviderContract(ctx context.Context, p TaskProvider, projectID string) error
- func VerifyStatusReadback(taskID, expected, actual string) error
- func VerifyStatusReceipt(r *StatusMutationReceipt, wantTask, wantOp, wantStatus string, wantFence int64) error
- func WithCASExpectation(ctx context.Context, status, commentBody string) context.Context
- func WithCASMeta(ctx context.Context, fenceToken int64, opID string) context.Context
- func WithMintIdentity(ctx context.Context, id MintIdentity) context.Context
- func WithOpDeadline(ctx context.Context, d Deadlines, op OpKind) (context.Context, context.CancelFunc)
- func WriteHandbackDone(worktreePath string, rec HandbackDoneReceipt) error
- func WriteHandbackIntent(worktreePath string, intent HandbackIntent) error
- func WriteHandbackNote(worktreePath string) error
- func WriteLaunchSession(worktreePath string, sess LaunchSession) error
- func WriteLeaseAck(worktreePath string, ack LeaseAck) error
- func WriteLeaseCapability(worktreePath string, cap LeaseCapability) error
- func WriteSharedMarker(dir string) error
- type AmbiguousMutationError
- type AuthBroker
- type AuthoritativeReceiver
- type AzureDevOpsProvider
- func (a *AzureDevOpsProvider) AddComment(ctx context.Context, taskID string, body string) error
- func (a *AzureDevOpsProvider) ClaimTask(ctx context.Context, taskID string, role string) error
- func (a *AzureDevOpsProvider) GetTask(ctx context.Context, id string) (*Task, error)
- func (a *AzureDevOpsProvider) ListComments(ctx context.Context, taskID string) ([]string, error)
- func (a *AzureDevOpsProvider) ListTasks(ctx context.Context, projectID string, status string) ([]*Task, error)
- func (a *AzureDevOpsProvider) UpdateStatus(ctx context.Context, taskID string, status string) error
- type BoundClient
- func (b *BoundClient) AddComment(ctx context.Context, taskID, body string) error
- func (b *BoundClient) AttachTaskLabel(ctx context.Context, taskID, labelID string) error
- func (b *BoundClient) ClaimTask(ctx context.Context, taskID, role string) error
- func (b *BoundClient) CreateRelation(ctx context.Context, sourceID, targetID string, typ RelationType) (*Relation, error)
- func (b *BoundClient) CreateTask(ctx context.Context, task *Task) (*Task, error)
- func (b *BoundClient) CreateTaskLabel(ctx context.Context, taskID, name string) (TaskLabel, error)
- func (b *BoundClient) DeleteRelation(ctx context.Context, relationID, sourceID, targetID string) error
- func (b *BoundClient) DeleteTaskLabel(ctx context.Context, labelID string) error
- func (b *BoundClient) DetachTaskLabel(ctx context.Context, labelID string) error
- func (b *BoundClient) GetTask(ctx context.Context, id string) (*Task, error)
- func (b *BoundClient) LabelMutationAuthority() (string, error)
- func (b *BoundClient) ListComments(ctx context.Context, taskID string) ([]string, error)
- func (b *BoundClient) ListProjectRelations(ctx context.Context, projectID string) ([]Relation, error)
- func (b *BoundClient) ListRelations(ctx context.Context, taskID string) ([]Relation, error)
- func (b *BoundClient) ListTaskLabels(ctx context.Context, taskID string) ([]TaskLabel, error)
- func (b *BoundClient) ListTaskLabelsBulk(ctx context.Context, taskIDs []string) (BulkTaskLabels, error)
- func (b *BoundClient) ListTasks(ctx context.Context, projectID, status string) ([]*Task, error)
- func (b *BoundClient) ProveLabelCreation(ctx context.Context, created TaskLabel, targetID, name string, ...) error
- func (b *BoundClient) UpdateStatus(ctx context.Context, taskID, status string) error
- type BulkRelationProvider
- type BulkTaskLabelProvider
- type BulkTaskLabels
- type CLIResult
- type CapabilityIssueRequest
- type ClaimStack
- func (s *ClaimStack) AcquireLease(ctx context.Context, key claim.LeaseKey, ownerID, role, taskRole string) (*claim.Lease, error)
- func (s *ClaimStack) Close() error
- func (s *ClaimStack) MutateClaimGuarded(ctx context.Context, key claim.LeaseKey, ...) (*claim.Lease, error)
- func (s *ClaimStack) MutateCommentGuarded(ctx context.Context, key claim.LeaseKey, ...) (generation int64, err error)
- func (s *ClaimStack) MutateStatusGuarded(ctx context.Context, key claim.LeaseKey, ...) (generation int64, err error)
- type Clock
- type CommentReader
- type ConfigDeadlineSource
- type CoordinatorBroker
- type CoordinatorBrokerOptions
- type Deadlines
- type EffectState
- type FenceBroker
- func (b *FenceBroker) Addr() string
- func (b *FenceBroker) ClientBaseURL() string
- func (b *FenceBroker) Close() error
- func (b *FenceBroker) GrantMintToChild(cmd *exec.Cmd) (func(), error)
- func (b *FenceBroker) InstanceID() string
- func (b *FenceBroker) SeedTestLease(ctx context.Context, key claim.LeaseKey, ownerID string, generation int64, ...) (*claim.Lease, error)
- func (b *FenceBroker) UnixSocket() string
- type FenceBrokerClient
- func (c *FenceBrokerClient) Live(ctx context.Context) error
- func (c *FenceBrokerClient) MutateComment(ctx context.Context, taskID, commentBody string, fence int64, ...) error
- func (c *FenceBrokerClient) MutateStatus(ctx context.Context, taskID, status string, fence int64, ...) error
- func (c *FenceBrokerClient) OpApplied(ctx context.Context, opID, taskID, wantStatus string) (bool, error)
- type FenceBrokerConfig
- type FenceBrokerMinter
- func CoordinatorMinterInProcess(b *FenceBroker) (*FenceBrokerMinter, error)
- func NewFenceBrokerMinterFromClaimDir(claimDir, brokerURL string) (*FenceBrokerMinter, error)
- func NewFenceBrokerMinterFromEnv() (*FenceBrokerMinter, error)
- func NewFenceBrokerMinterFromInheritedFD(brokerURL string) (*FenceBrokerMinter, error)
- type FenceStore
- type FencedBoard
- func (b *FencedBoard) AddComment(ctx context.Context, taskID string, fenceToken int64, body string) (claim.ProviderRevision, error)
- func (b *FencedBoard) AdvanceFence(ctx context.Context, taskID string, fenceToken int64) error
- func (b *FencedBoard) ClaimOptions() []claim.Option
- func (b *FencedBoard) ClaimTask(ctx context.Context, taskID string, fenceToken int64, role string) (claim.ProviderRevision, error)
- func (b *FencedBoard) MutateClaim(ctx context.Context, mgr *claim.ClaimManager, key claim.LeaseKey, ...) error
- func (b *FencedBoard) MutateComment(ctx context.Context, mgr *claim.ClaimManager, key claim.LeaseKey, ...) error
- func (b *FencedBoard) MutateStatus(ctx context.Context, mgr *claim.ClaimManager, key claim.LeaseKey, ...) error
- func (b *FencedBoard) UpdateStatus(ctx context.Context, taskID string, fenceToken int64, status string) (claim.ProviderRevision, error)
- type FencedCAS
- func (c *FencedCAS) AdvanceFence(ctx context.Context, taskID string, fenceToken int64) error
- func (c *FencedCAS) Close() error
- func (c *FencedCAS) CompareAndSwap(ctx context.Context, taskID string, expected claim.ProviderRevision, ...) (claim.ProviderRevision, error)
- func (c *FencedCAS) ReadRevision(ctx context.Context, taskID string) (claim.ProviderRevision, error)
- type GitHubProvider
- func (g *GitHubProvider) AddComment(ctx context.Context, taskID string, body string) error
- func (g *GitHubProvider) ClaimTask(ctx context.Context, taskID string, role string) error
- func (g *GitHubProvider) GetTask(ctx context.Context, id string) (*Task, error)
- func (g *GitHubProvider) ListComments(ctx context.Context, taskID string) ([]string, error)
- func (g *GitHubProvider) ListTasks(ctx context.Context, projectID string, status string) ([]*Task, error)
- func (g *GitHubProvider) UpdateStatus(ctx context.Context, taskID string, status string) error
- type HandbackDoneReceipt
- type HandbackIntent
- type JiraProvider
- func (j *JiraProvider) AddComment(ctx context.Context, taskID string, body string) error
- func (j *JiraProvider) ClaimTask(ctx context.Context, taskID string, role string) error
- func (j *JiraProvider) GetTask(ctx context.Context, id string) (*Task, error)
- func (j *JiraProvider) ListComments(ctx context.Context, taskID string) ([]string, error)
- func (j *JiraProvider) ListTasks(ctx context.Context, projectID string, status string) ([]*Task, error)
- func (j *JiraProvider) UpdateStatus(ctx context.Context, taskID string, status string) error
- type KaneoLinkConfig
- type KaneoProvider
- func (k *KaneoProvider) AddComment(ctx context.Context, taskID string, body string) error
- func (k *KaneoProvider) AttachTaskLabel(ctx context.Context, taskID, labelID string) error
- func (k *KaneoProvider) ClaimTask(ctx context.Context, taskID string, role string) error
- func (k *KaneoProvider) CreateRelation(ctx context.Context, sourceID, targetID string, typ RelationType) (*Relation, error)
- func (k *KaneoProvider) CreateTask(ctx context.Context, task *Task) (*Task, error)
- func (k *KaneoProvider) CreateTaskLabel(ctx context.Context, taskID, name string) (TaskLabel, error)
- func (k *KaneoProvider) DeleteRelation(ctx context.Context, relationID, sourceID, targetID string) error
- func (k *KaneoProvider) DeleteTaskLabel(ctx context.Context, labelID string) error
- func (k *KaneoProvider) DetachTaskLabel(ctx context.Context, labelID string) error
- func (k *KaneoProvider) GetTask(ctx context.Context, id string) (*Task, error)
- func (k *KaneoProvider) LabelMutationAuthority() (string, error)
- func (k *KaneoProvider) ListComments(ctx context.Context, taskID string) ([]string, error)
- func (k *KaneoProvider) ListLiveComments(ctx context.Context, taskID string) ([]string, error)
- func (k *KaneoProvider) ListProjectRelations(ctx context.Context, projectID string) ([]Relation, error)
- func (k *KaneoProvider) ListRelations(ctx context.Context, taskID string) ([]Relation, error)
- func (k *KaneoProvider) ListTaskLabels(ctx context.Context, taskID string) ([]TaskLabel, error)
- func (k *KaneoProvider) ListTaskLabelsBulk(ctx context.Context, taskIDs []string) (BulkTaskLabels, error)
- func (k *KaneoProvider) ListTasks(ctx context.Context, projectID string, status string) ([]*Task, error)
- func (k *KaneoProvider) ProveLabelCreation(ctx context.Context, created TaskLabel, targetID, name string, ...) error
- func (k *KaneoProvider) UpdateStatus(ctx context.Context, taskID string, status string) error
- type LabelCreationProof
- type LabelEvidenceReader
- type LabelEvidenceSink
- type LabelRepairEvidence
- type LabelRepairOptions
- type LabelTransactionError
- type LaunchSession
- type LeaseAck
- type LeaseCapability
- type LinearProvider
- func (l *LinearProvider) AddComment(ctx context.Context, taskID string, body string) error
- func (l *LinearProvider) ClaimTask(ctx context.Context, taskID string, role string) error
- func (l *LinearProvider) CreateRelation(ctx context.Context, sourceID, targetID string, relType RelationType) (*Relation, error)
- func (l *LinearProvider) DeleteRelation(ctx context.Context, relationID, sourceID, targetID string) error
- func (l *LinearProvider) GetTask(ctx context.Context, id string) (*Task, error)
- func (l *LinearProvider) ListComments(ctx context.Context, taskID string) ([]string, error)
- func (l *LinearProvider) ListProjectRelations(ctx context.Context, projectID string) ([]Relation, error)
- func (l *LinearProvider) ListRelations(ctx context.Context, taskID string) ([]Relation, error)
- func (l *LinearProvider) ListTasks(ctx context.Context, projectID string, status string) ([]*Task, error)
- func (l *LinearProvider) UpdateStatus(ctx context.Context, taskID string, status string) error
- type MemoryFenceStore
- func (m *MemoryFenceStore) Advance(_ context.Context, taskID string, fenceToken int64) (int64, error)
- func (m *MemoryFenceStore) BusyTimeout() time.Duration
- func (m *MemoryFenceStore) Close() error
- func (m *MemoryFenceStore) Highest(_ context.Context, taskID string) (int64, error)
- func (m *MemoryFenceStore) ListExpectedComments(_ context.Context, taskID string) ([]string, error)
- func (m *MemoryFenceStore) ListOpsForTask(_ context.Context, taskID string) ([]OpReceipt, error)
- func (m *MemoryFenceStore) LookupApplied(_ context.Context, opID string) (*OpReceipt, error)
- func (m *MemoryFenceStore) MarkAmbiguous(_ context.Context, rec OpReceipt) error
- func (m *MemoryFenceStore) MarkApplied(_ context.Context, rec OpReceipt) error
- func (m *MemoryFenceStore) WithExclusive(ctx context.Context, taskID string, fn func(ctx context.Context) error) error
- type MemoryProvider
- func (m *MemoryProvider) AddComment(ctx context.Context, taskID string, body string) error
- func (m *MemoryProvider) AddTask(t *Task)
- func (m *MemoryProvider) AttachTaskLabel(_ context.Context, taskID, labelID string) error
- func (m *MemoryProvider) ClaimTask(ctx context.Context, taskID string, role string) error
- func (m *MemoryProvider) Comments(taskID string) []string
- func (m *MemoryProvider) CreateRelation(ctx context.Context, sourceID, targetID string, typ RelationType) (*Relation, error)
- func (m *MemoryProvider) CreateTask(_ context.Context, task *Task) (*Task, error)
- func (m *MemoryProvider) CreateTaskLabel(_ context.Context, taskID, name string) (TaskLabel, error)
- func (m *MemoryProvider) DeleteRelation(ctx context.Context, relationID, sourceID, targetID string) error
- func (m *MemoryProvider) DeleteTaskLabel(_ context.Context, labelID string) error
- func (m *MemoryProvider) DetachTaskLabel(_ context.Context, labelID string) error
- func (m *MemoryProvider) GetTask(ctx context.Context, id string) (*Task, error)
- func (m *MemoryProvider) LabelMutationAuthority() (string, error)
- func (m *MemoryProvider) ListComments(_ context.Context, taskID string) ([]string, error)
- func (m *MemoryProvider) ListProjectRelations(ctx context.Context, projectID string) ([]Relation, error)
- func (m *MemoryProvider) ListRelations(ctx context.Context, taskID string) ([]Relation, error)
- func (m *MemoryProvider) ListTaskLabels(_ context.Context, taskID string) ([]TaskLabel, error)
- func (m *MemoryProvider) ListTasks(ctx context.Context, projectID string, status string) ([]*Task, error)
- func (m *MemoryProvider) ProveLabelCreation(_ context.Context, created TaskLabel, targetID, name string, ...) error
- func (m *MemoryProvider) UpdateStatus(ctx context.Context, taskID string, status string) error
- func (m *MemoryProvider) UpdateStatusAtomic(ctx context.Context, taskID, status, receiptJSON string) error
- type MintIdentity
- type MutationCapability
- type OpFailureClass
- type OpKind
- type OpReceipt
- type PageAccumulator
- type PageDecision
- type Priority
- type ProviderError
- type ReadbackDriftError
- type Relation
- type RelationProvider
- type RelationType
- type RetryPolicy
- type SQLiteFenceStore
- func (s *SQLiteFenceStore) Advance(ctx context.Context, taskID string, fenceToken int64) (int64, error)
- func (s *SQLiteFenceStore) BusyTimeout() time.Duration
- func (s *SQLiteFenceStore) Close() error
- func (s *SQLiteFenceStore) Highest(ctx context.Context, taskID string) (int64, error)
- func (s *SQLiteFenceStore) ListExpectedComments(ctx context.Context, taskID string) ([]string, error)
- func (s *SQLiteFenceStore) ListOpsForTask(ctx context.Context, taskID string) ([]OpReceipt, error)
- func (s *SQLiteFenceStore) LookupApplied(ctx context.Context, opID string) (*OpReceipt, error)
- func (s *SQLiteFenceStore) MarkAmbiguous(ctx context.Context, rec OpReceipt) error
- func (s *SQLiteFenceStore) MarkApplied(ctx context.Context, rec OpReceipt) error
- func (s *SQLiteFenceStore) WithExclusive(ctx context.Context, taskID string, fn func(ctx context.Context) error) error
- type StatusMutationReceipt
- type StatusReader
- type Task
- type TaskConfig
- type TaskCreator
- type TaskLabel
- type TaskLabelProvider
- type TaskProvider
- type TimeoutError
Constants ¶
const ( DefaultGetDeadline = 15 * time.Second DefaultListDeadline = 30 * time.Second DefaultMutateDeadline = 20 * time.Second DefaultCommentDeadline = 15 * time.Second DefaultReadbackDeadline = 15 * time.Second )
Default per-operation deadlines. Chosen so a hung board (observed 30–90s stalls) fails closed well before a worker lane parks indefinitely, while still allowing slow-but-alive pagination and readback.
const ( KaneoGraphDeadlineThreshold = 64 KaneoGraphMinDeadline = 2 * time.Minute )
const ( CapabilityFileName = ".herd/lease-capability.json" AckFileName = ".herd/lease-ack.json" HandbackNoteName = ".herd/LEASE-HANDBACK.md" HandbackDoneName = ".herd/lease-handback.done" HandbackIntentName = ".herd/lease-handback.intent" SessionFileName = ".herd/lease-session.json" )
const ( KaneoLargeBoardThreshold = 500 DefaultKaneoLargeBoardConcurrency = 64 MaxKaneoGraphConcurrency = 128 KaneoGraphBatchSize = 256 )
Large-board Kaneo snapshots need more parallelism because Kaneo exposes no project-level relation endpoint. Keep the increase provider-specific and capped: ordinary list deadlines remain unchanged and a large board cannot turn into an unbounded request storm.
const ( StatusToDo = "to-do" StatusInProgress = "in-progress" StatusInReview = "in-review" StatusDone = "done" StatusPlanned = "planned" StatusArchived = "archived" StatusUnknown = "unknown" )
Canonical task lifecycle statuses at the adapter boundary.
const ( DefaultReadMaxAttempts = 3 DefaultReadBaseBackoff = 50 * time.Millisecond DefaultReadMaxBackoff = 400 * time.Millisecond )
Default read-retry policy. Mutations must never use RetryRead — ambiguous writes go through ReconcileStatus instead of blind re-apply.
const CommentOpTagPrefix = "[herd-op:"
CommentOpTag is the live-board marker binding a comment body to one opID. Exactly-once comment identity: two ops with the same body remain distinct.
const DefaultBulkRelationConcurrency = 16
DefaultBulkRelationConcurrency bounds concurrent per-task relation fetches for ordinary O(board) project graph snapshots (measured ~4s for 164 tasks @16). Kaneo uses a larger, still bounded pool for genuinely large boards; see the Kaneo-specific constants below.
const DefaultFenceBusyTimeout = 3 * time.Second
const DefaultMaxListPages = 50
DefaultMaxListPages is the safety ceiling for multi-page listing loops. Hitting this cap without observing an empty page is a hard error.
const EnvKaneoRelationsCLI = "HERD_KANEO_RELATIONS_CLI"
EnvKaneoRelationsCLI is an explicit local-development escape hatch for boards whose HTTP relation endpoint is unavailable while the authenticated Kaneo CLI remains healthy. It is intentionally opt-in: production keeps the origin-bound HTTP graph path and still fails closed when credentials are not available.
const MaxCLIStderrBytes = 4096
MaxCLIStderrBytes caps captured CLI stderr so credential-bearing or voluminous error dumps cannot flood logs or status projections.
const OperationKindClaim = "claim"
OperationKindClaim is the kind for ClaimTask transitions.
Variables ¶
var ( ErrLabelOwnershipUnknown = errors.New("label ownership unknown") ErrLabelTransactionBlocked = errors.New("label transaction durably blocked") ErrLabelGenerationMismatch = errors.New("label generation mismatch") )
var ErrBoardFrozen = errors.New("provider: board is frozen")
ErrBoardFrozen is wrapped into every mutation BoundClient refuses while the FAC-103 board-freeze gate is active. Callers use errors.Is against this to distinguish a deliberate freeze refusal from a transport error.
var ErrDuplicatePage = fmt.Errorf("provider pagination: non-empty duplicate page without empty-page termination")
ErrDuplicatePage is returned when a non-empty page contributes zero new IDs without empty-page termination — incomplete listing, fail closed.
var ErrGraphCredentialsRequired = errors.New("kaneo: project graph snapshot requires HTTP credentials (KANEO_API_KEY or kaneo profile api_key); refusing silent CLI relation fan-out")
ErrGraphCredentialsRequired is returned when ListProjectRelations would otherwise fall through to N CLI subprocesses (use_cli without API key).
var ErrPaginationCap = fmt.Errorf("provider pagination: page cap reached without empty-page termination")
ErrPaginationCap is returned when DefaultMaxListPages (or the caller's cap) is exhausted without observing an empty page.
var ErrRedundantLabelAttach = fmt.Errorf("redundant label attach returned null success")
Functions ¶
func ActiveStatuses ¶
func ActiveStatuses() []string
ActiveStatuses lists the non-terminal columns, in board order. Callers that drop done/archived should read StatusActive rather than the whole board.
func AfterMutation ¶
func AfterMutation( parent context.Context, r StatusReader, deadlines Deadlines, provider, op, taskID, want string, writeErr error, ) error
AfterMutation handles the post-write path:
- writeErr == nil → VerifyStatusReadback via GetTask (normal readback)
- writeErr is timeout/cancel → ReconcileStatus (no second write)
- other writeErr → returned as-is
parent is the caller context (not the expired write child). deadlines bound the readback/reconcile GetTask.
func ApplyDeadlines ¶
func ApplyDeadlines(tp TaskProvider, d Deadlines)
ApplyDeadlines sets per-op deadlines on known production provider types. Unknown TaskProvider implementations are left unchanged (FAC-155 may wrap).
func AsTimeout ¶
AsTimeout wraps err as *TimeoutError when it represents a deadline/cancel, preserving an existing *TimeoutError. Non-timeout errors are returned as-is.
func AttachAuthoritativeReceiver ¶
func AttachAuthoritativeReceiver(tp TaskProvider, store FenceStore)
AttachAuthoritativeReceiver wires AuthBroker + RequireCASMeta on Kaneo.
func AttachCoordinatorMinter ¶
func AttachCoordinatorMinter(k *KaneoProvider, m *FenceBrokerMinter) error
AttachCoordinatorMinter grants a KaneoProvider the ability to mint capabilities at mutate time (coordinator processes only).
func AttachFenceHeaders ¶
AttachFenceHeaders sets X-Herd-Fence / X-Herd-Op when CAS meta is present. Production Kaneo HTTP and CLI-compatible sandboxes must honor these.
func AwaitLeaseAck ¶
func AwaitLeaseAck(worktreePath string, cap *LeaseCapability, timeout time.Duration) error
AwaitLeaseAck polls for a valid worker ack of the single capability.
func BoundOp ¶
BoundOp derives a child context for an external provider boundary call. Production callers (daemon/dispatch) must use this (or equivalent) instead of context.Background() across TaskProvider methods.
func CanonicalClaimDir ¶
CanonicalClaimDir resolves the single shared claim/fence/outbox directory. When HERD_CLAIM_DIR is set (fleet shared volume), that path is authoritative so fence-provision and every production command open the same directory. Otherwise: <canonical-root>/.herd/claim (never worktree-relative). override is typically $HERD_ROOT / $HERD_REPO_ROOT when HERD_CLAIM_DIR is unset.
func CanonicalRepoIdentity ¶
CanonicalRepoIdentity returns a stable absolute repo identity for LeaseKey.Repo so two worktrees of the same repository cannot mint independent generation-1 leases for the same card (FAC-147).
Prefer git --git-common-dir from startDir so linked worktrees collide on the main repository root. HERD_ROOT / HERD_REPO_ROOT short-circuit only when startDir is empty or "." (process cwd) — never when the caller passes an explicit worktree path, or independent checkouts would falsely share identity via the env override.
func CapabilityPresent ¶
CapabilityPresent reports whether a valid capability file exists.
func ClaimRequestFor ¶
func ClaimRequestFor(key claim.LeaseKey, ownerID, role string) claim.ClaimRequest
ClaimRequestFor is the production-shaped ClaimRequest for tests.
func ClassifyContextErr ¶
func ClassifyContextErr(ctx context.Context, provider, op string, kind OpKind, deadline time.Duration) error
ClassifyContextErr maps a finished context error to a TimeoutError when appropriate. Returns nil when ctx is still live (err should come from the call, not the context).
func ClearCoordinatorMinter ¶
func ClearCoordinatorMinter(k *KaneoProvider)
ClearCoordinatorMinter removes mint authority (tests / worker hardening).
func CommentOpTaggedBody ¶
CommentOpTaggedBody appends an op-bound marker so live ListLiveComments can prove this exact operation (not a prior substring-matching comment).
func CompareRefs ¶
CompareRefs orders ticket refs with numeric awareness: FAC-9 < FAC-61 < FAC-100. Plain string comparison would put FAC-100 before FAC-99, breaking the Priority DESC, Ref ASC claim-order invariant. Non-conforming refs fall back to lexical comparison.
func ConfigureKaneoFenceBroker ¶
func ConfigureKaneoFenceBroker(k *KaneoProvider, client *FenceBrokerClient) error
ConfigureKaneoFenceBroker attaches a worker FenceBroker client after live check. Refuses any client that appears to carry mint authority via type confusion.
func DecodeJSONBytes ¶
DecodeJSONBytes is the byte-slice form of DecodeJSONResponse for CLI/stdout paths.
func DecodeJSONResponse ¶
DecodeJSONResponse reads the full response body, rejects non-2xx statuses, rejects 2xx bodies that carry structured error payloads, then unmarshals into v. When v is nil, only status/error-body checks run (useful for mutations).
func DefaultOwnerID ¶
func DefaultOwnerID() string
DefaultOwnerID returns ProcessOwnerID or a fail-closed sentinel that cannot acquire leases (empty string is rejected by Claim). Callers that must surface the error should use ProcessOwnerID.
Deprecated for new call sites: prefer ProcessOwnerID and propagate err.
func DetectErrorBody ¶
DetectErrorBody inspects a response body for structured error payloads under any HTTP status, including 200 OK. Returns a non-nil *ProviderError when the body carries an error object, error string, or GraphQL errors array. Empty or non-JSON bodies are not treated as structured errors.
func EmbedStatusReceiptInDescription ¶
EmbedStatusReceiptInDescription replaces any prior receipt footer and appends the signed receipt so a single PUT of description+status is one atomic remote effect on real Kaneo.
func EncodeRevision ¶
func EncodeRevision(t *Task) claim.ProviderRevision
func EncodeStatusReceiptJSON ¶
func EncodeStatusReceiptJSON(r *StatusMutationReceipt) (string, error)
EncodeStatusReceiptJSON serializes the receipt for description embedding.
func EnsureTaskRoleLabel ¶
func EnsureTaskRoleLabel(ctx context.Context, p TaskLabelProvider, targetID, role string) (err error)
EnsureTaskRoleLabel repairs a label-less target without inventing a source task. It is the safe path for orphan/zero-label rows; an empty role remains blocked because no intended authority exists.
func EnsureTaskRoleLabelWithOptions ¶
func EnsureTaskRoleLabelWithOptions(ctx context.Context, p TaskLabelProvider, targetID, role string, opts LabelRepairOptions) error
func Handback ¶
func Handback(ctx context.Context, mgr *claim.ClaimManager, key claim.LeaseKey, ownerID string, generation int64) error
Handback releases the worker-held lease.
func HandbackDoneMatches ¶
HandbackDoneMatches reports whether a valid done receipt matches gen/owner.
func Heartbeat ¶
func Heartbeat(ctx context.Context, mgr *claim.ClaimManager, key claim.LeaseKey, ownerID string, generation int64) (*claim.Lease, error)
Heartbeat renews the lease under the worker session owner.
func HerdrSessionOwnerID ¶
HerdrSessionOwnerID binds lease handoff to a live Herdr agent session. Inputs are tab/pane/agent identity plus the proven prompt receipt token so a coordinator cannot invent a fake worker owner without the launch proof material.
func HoldIdentitiesFor ¶
func HoldIdentitiesFor(key claim.LeaseKey, role string) []lifecycle.HoldIdentity
HoldIdentitiesFor builds the exact lane/task composite Claim requires.
func IsActiveStatus ¶
IsActiveStatus reports whether a card's own status is non-terminal.
func IsAmbiguous ¶
IsAmbiguous reports whether err is (or wraps) an AmbiguousMutationError.
func IsClaimConflict ¶
IsClaimConflict reports whether err is an active-lease conflict.
func IsRecoverableTimeout ¶
IsRecoverableTimeout reports whether err is a pure timeout (not ambiguous mutation). Safe for capped read retries; unsafe for blind mutation retry.
func IsTimeout ¶
IsTimeout reports whether err is (or wraps) a provider TimeoutError or a context deadline/cancel. Prefer this over raw errors.Is for lane BLOCKED projection (provider_timeout).
func KeyFromCapability ¶
func KeyFromCapability(cap *LeaseCapability) claim.LeaseKey
KeyFromCapability rebuilds the lease key from a capability file.
func KnownImplementationRoles ¶
func KnownImplementationRoles() []string
KnownImplementationRoles is the effective vocabulary: generic roles plus any registered project roles. Error messages use this so an operator sees what the check actually accepts rather than a partial list.
func LeaseKey ¶
LeaseKey builds the claim.LeaseKey with git-common-dir canonical Repo identity for every non-empty path (not just "."). Two registered worktrees of the same repository MUST produce identical keys.
func MatchCommentOp ¶
MatchCommentOp reports whether liveBody is the op-bound comment for wantBody+opID. When opID is set, the [herd-op:id] marker is required — bare free-text equality is never sufficient (prevents cross-op collapse).
func MatchStatusOpEvidence ¶
MatchStatusOpEvidence is deliberately always false: forgeable substring tags ([herd-status-op:...]) are never authentic evidence (FAC-147 lm0ihu).
func MintChallenge ¶
MintChallenge returns a random challenge nonce for the handshake.
func MintCommentCapability ¶
func MintCommentCapability( secret, instanceID string, key claim.LeaseKey, boardTaskID, ownerID, opID, comment string, generation, expiresUnix int64, ) (string, error)
MintCommentCapability signs a comment-body-bound capability.
func MintMutationCapability ¶
func MintMutationCapability( secret, instanceID string, key claim.LeaseKey, boardTaskID, ownerID, opID, status string, generation, expiresUnix int64, ) (string, error)
MintMutationCapability signs a status-bound capability.
func MustAcquireLease ¶
func NormalizeStatus ¶
NormalizeStatus maps common provider spellings to canonical lifecycle values. Unknown non-empty statuses are returned as "unknown:<raw>" so callers never treat them as to-do, done, or empty by accident. Empty maps to "unknown".
func ObservedLabelGeneration ¶
func ObservedLabelGeneration(ctx context.Context, p TaskLabelProvider, ids ...string) (string, error)
ObservedLabelGeneration is a deterministic digest of complete task and owned-label readback. Callers may pass it back as the generation authority; arbitrary caller strings are rejected by the transaction path.
func OperationKindComment ¶
OperationKindComment builds a stable kind for a comment body.
func OperationKindStatus ¶
OperationKindStatus builds a stable Begin/Complete kind for a status write.
func PaginationTerminalError ¶
func PaginationTerminalError(d PageDecision) error
PaginationTerminalError maps a page decision to a hard error when listing must not succeed. PageStopEmpty returns nil (success). PageStopDuplicate returns ErrDuplicatePage. PageContinue returns nil so the caller loops.
func ParseHandbackDoneLegacyGen ¶
ParseHandbackDoneLegacyGen is for tests.
func ParseStatusReceiptFromDescription ¶
ParseStatusReceiptFromDescription extracts an embedded receipt footer. Format (last occurrence wins): <!-- herd-status-receipt-v1: <json> -->
func PrepareRuntimeDefaults ¶
PrepareRuntimeDefaults resolves the repo-local claim fence for a normal single-repo invocation. Fleet deployments may still provide HERD_CLAIM_DIR and HERD_FENCE_VOLUME_ID explicitly; local users should not have to copy a SQLite seal into every shell before running herd.
func ProcessOwnerID ¶
ProcessOwnerID returns a cryptographic process identity for lease ownership. Format: herd1.<host>.<pid>.<32-byte-hex-nonce>. Ambient HERD_OWNER_ID is never trusted. crypto/rand failure propagates — never falls back to low-entropy host+PID hashes.
func ProvisionSharedFenceForTest ¶
func ProvisionSharedFenceForTest(t interface {
Helper()
Setenv(key, value string)
Fatal(...any)
}, dir string)
ProvisionSharedFenceForTest provisions sealed fence store + env for tests.
func ReadFenceVolumeSeal ¶
ReadFenceVolumeSeal returns store_authority.volume_seal from claimDir/fences.db.
func ReadVolumeSeal ¶
ReadVolumeSeal returns the durable volume_seal from fences.db under claimDir. Used by `herd fence-provision` to print the mint for fleet distribution.
func ReconcileStatus ¶
func ReconcileStatus(ctx context.Context, r StatusReader, deadlines Deadlines, provider, op, taskID, want string, writeErr error) error
ReconcileStatus reads the task after an ambiguous write. Outcomes:
- read succeeds and status matches want → nil (write landed; no re-apply)
- read succeeds and status differs → *AmbiguousMutationError with Actual
- read fails → *AmbiguousMutationError with ReadErr
want is normalized before comparison. This never issues a second write. deadlines bound the reconciliation GetTask (defaults applied for zero fields).
func RefreshLeaseCapabilityExpiry ¶
func RefreshLeaseCapabilityExpiry(worktreePath string, owner string, gen int64, expiresAt time.Time) error
RefreshLeaseCapabilityExpiry updates only ExpiresAt on an existing capability after a successful Renew. Never clears handback intent/done/session — heartbeat must not erase terminal recovery evidence (parallel audit).
func RegisterProjectImplementationRoles ¶
func RegisterProjectImplementationRoles(roles []string)
RegisterProjectImplementationRoles records repository-configured ownership roles. Empty and duplicate entries are ignored. Passing nil clears them, which restores the generic-only vocabulary.
func RemoveLeaseCapability ¶
RemoveLeaseCapability removes capability + ack via openat unlinkat.
func RepairTaskRoleLabel ¶
func RepairTaskRoleLabel(ctx context.Context, p TaskLabelProvider, sourceID, targetID, role string) (err error)
RepairTaskRoleLabel transfers role authority without moving the source row. It is idempotent when target already has exactly one owned role label. The new label is created for target before it is attached, so a source label ID can never be reused for another task.
func RepairTaskRoleLabelWithOptions ¶
func RepairTaskRoleLabelWithOptions(ctx context.Context, p TaskLabelProvider, sourceID, targetID, role string, opts LabelRepairOptions) error
RepairTaskRoleLabelWithOptions is the production entrypoint. The legacy wrapper remains for adapters and focused unit tests that do not persist evidence.
func RequireTaskRole ¶
RequireTaskRole returns the exact label matching role, or role when the task is unlabeled. Fail-closed when labels exist but none match role — never substitutes an arbitrary first label (hold c6ic8im #4).
func ResolveKaneoAPIKey ¶
ResolveKaneoAPIKey is retained for tests/callers that only need a key string. Prefer ResolveKaneoProfileCred + origin checks for HTTP authorization. Order: explicit override → KANEO_API_KEY env → profile key (only when profile has a resolvable api_url; key alone is never returned from profile without origin).
func ResolveKaneoProfileCred ¶
func ResolveKaneoProfileCred() kaneoOriginCred
ResolveKaneoProfileCred loads the selected default_profile's api_key and api_url together and returns an origin-bound credential. Empty TrustedOrigin or Key means unusable. Never scans an arbitrary first profile; never logs the key.
func ResolveKaneoProjectID ¶
ResolveKaneoProjectID attempts to read project ID from .herd/kaneo.json, falling back to root .kaneo.json
func ResolveTaskRole ¶
ResolveTaskRole picks the exact role string ClaimManager.Claim will accept: a matching label when present, otherwise role (for unlabeled tasks). Prefer TaskOwnershipRole for production board mutations (pt5t7 #1).
func RetryRead ¶
RetryRead invokes fn until it succeeds, RetryIf rejects the error, attempts are exhausted, or ctx is done. fn receives the same ctx (caller should derive per-attempt deadlines inside fn if needed).
NEVER use for ClaimTask / UpdateStatus / AddComment — those are not idempotent under ambiguous timeout; use ReconcileStatus instead.
func RunCLIOutput ¶
func ScrubWorkerMintEnv ¶
func ScrubWorkerMintEnv()
ScrubWorkerMintEnv removes mint material from the process environment so workers cannot inherit launcher secrets. Safe to call on every worker start.
func StatusOpEvidenceBody ¶
StatusOpEvidenceBody is empty: never dual-write post-status comment tags.
func StatusReceiptKey ¶
StatusReceiptKey returns the HMAC key for status receipts. Production: HERD_FENCE_HMAC_KEY only (min 16 chars). Tests: fixed key when env unset (compiled binary must set the env).
func StripStatusReceiptFooter ¶
StripStatusReceiptFooter returns description without receipt footers (for human-facing display / re-embed).
func TaskHasVerifiedStatusReceipt ¶
func TaskHasVerifiedStatusReceipt(t *Task, wantTask, wantOp, wantStatus string, wantFence int64) bool
TaskHasVerifiedStatusReceipt reports whether the task carries a signed receipt (from description footer readback) for the exact binding.
func TaskOwnershipRole ¶
TaskOwnershipRole resolves the durable implementation role for board mutations. Coordinator/reviewer sessions claim under this task role while ownerID carries the session identity.
Order: preferred if it matches a label AND is a known implementation role (or preferred matches any label when preferred itself is known) → first known implementation label on the task → unlabeled uses preferred only if known → fail closed on unknown/sole-unknown labels.
func TryAutoHandback ¶
TryAutoHandback is receipt-driven and fail-closed on lost authority (pt5t7 #2): launched sessions require matching generation-bound done + remote release.
func ValidRelationType ¶
func ValidRelationType(t RelationType) bool
ValidRelationType reports whether t is a known provider relation type.
func ValidateSharedMarker ¶
ValidateSharedMarker reads volume_seal from fences.db (authoritative). A copied volume_id in SHARED alone cannot pass without the sealed DB row.
func VerifyFieldReadback ¶
VerifyFieldReadback is a generic string equality check for non-status fields (title, assignee, etc.). Whitespace is trimmed; comparison is case-sensitive unless fold is true.
func VerifyLeaseAck ¶
func VerifyLeaseAck(cap *LeaseCapability, ack *LeaseAck) error
VerifyLeaseAck checks ack matches the single capability challenge.
func VerifyProviderContract ¶
func VerifyProviderContract(ctx context.Context, p TaskProvider, projectID string) error
VerifyProviderContract sanity-checks that a TaskProvider implements basic API guarantees
func VerifyStatusReadback ¶
VerifyStatusReadback compares expected vs actual status after a mutation. Both sides are normalized before comparison so provider aliases do not produce false drift. Empty actual (missing task / unknown state) always fails.
func VerifyStatusReceipt ¶
func VerifyStatusReceipt(r *StatusMutationReceipt, wantTask, wantOp, wantStatus string, wantFence int64) error
VerifyStatusReceipt checks MAC and field binding (constant-time compare).
func WithCASExpectation ¶
WithCASExpectation records what the mutation must achieve for ambiguous reconciliation (status and/or comment body).
func WithCASMeta ¶
WithCASMeta attaches fence token + opID for TaskProvider HTTP transport (Kaneo X-Herd-Fence / X-Herd-Op). FencedCAS.CompareAndSwap injects these into the mutate context so the authoritative service can enforce them.
func WithMintIdentity ¶
func WithMintIdentity(ctx context.Context, id MintIdentity) context.Context
WithMintIdentity attaches immutable mint lease identity for one mutation call.
func WithOpDeadline ¶
func WithOpDeadline(ctx context.Context, d Deadlines, op OpKind) (context.Context, context.CancelFunc)
WithOpDeadline derives a child context bounded by the op deadline. If the parent already has a nearer deadline, that nearer bound wins (context.WithTimeout still respects parent).
Always pair with defer cancel(). Never pass context.Background() across a provider boundary without this wrapper — that is the FAC-150 hang class.
func WriteHandbackDone ¶
func WriteHandbackDone(worktreePath string, rec HandbackDoneReceipt) error
WriteHandbackDone writes a generation-bound durable receipt + parent fsync.
func WriteHandbackIntent ¶
func WriteHandbackIntent(worktreePath string, intent HandbackIntent) error
WriteHandbackIntent persists intent before remote Release (crash recovery).
func WriteHandbackNote ¶
WriteHandbackNote writes LEASE-HANDBACK.md under openat-held .herd.
func WriteLaunchSession ¶
func WriteLaunchSession(worktreePath string, sess LaunchSession) error
WriteLaunchSession persists session state (completion mark).
func WriteLeaseAck ¶
WriteLeaseAck is the worker-side acknowledgement of the single capability.
func WriteLeaseCapability ¶
func WriteLeaseCapability(worktreePath string, cap LeaseCapability) error
WriteLeaseCapability writes capability + launch session for a NEW handoff. Clears stale done/intent/ack only when generation changes. Same generation refresh must use RefreshLeaseCapabilityExpiry (heartbeat) so terminal recovery evidence is never erased.
func WriteSharedMarker ¶
WriteSharedMarker provisions the shared fence authority once:
- Creates fences.db with an internal store_authority.volume_seal row (trustworthy: seal lives only inside the atomic SQLite store).
- Writes SHARED human-readable pointer (not the source of truth).
ValidateSharedMarker reads the seal FROM THE DB, not from forgeable fields.
Types ¶
type AmbiguousMutationError ¶
type AmbiguousMutationError struct {
Provider string
Op string
TaskID string
Want string // desired status or comment marker
// WriteErr is the original timeout/cancel (or transport) failure.
WriteErr error
// ReadErr is set when post-timeout reconciliation read also failed.
ReadErr error
// Actual is the observed status when the read succeeded but did not match.
Actual string
}
AmbiguousMutationError reports that a write may or may not have landed after a timeout/cancel. Callers must NOT blind-retry the write and must NOT treat the mutation as success. Reconcile via readback / outbox.
func (*AmbiguousMutationError) Error ¶
func (e *AmbiguousMutationError) Error() string
func (*AmbiguousMutationError) Unwrap ¶
func (e *AmbiguousMutationError) Unwrap() error
type AuthBroker ¶
type AuthBroker struct {
// CrashAt is an optional injected test seam (never ambient env).
// Production is always nil. Test builds install via claim.TestSeams.
// Values: "before-remote" | "after-remote". May os.Exit / panic.
// after-remote fires AFTER remote backend, BEFORE local revision persist.
CrashAt func(phase string)
// RevisionOf optional live revision after a successful backend write
// (e.g. task.UpdatedAt). Bound into the applied receipt for evidence.
RevisionOf func(ctx context.Context, taskID string) (string, error)
// StatusOpEvidence is LIVE provider readback of op-bound status evidence
// for THIS op. Client HMAC/description receipts are NOT valid production
// evidence (audit con62fkm). Prefer ServerOpDedupe re-submit when the
// remote atomically dedupes op IDs with status.
StatusOpEvidence func(ctx context.Context, taskID, opID, expStatus string) (bool, error)
// ServerOpDedupe when true: empty-rev Present without StatusOpEvidence
// re-submits the same op (server must no-op if already applied) instead
// of fail-closed settle or forgeable client receipts.
ServerOpDedupe bool
// contains filtered or unexported fields
}
AuthBroker is the production authoritative receiver over FenceStore.
func NewAuthBroker ¶
func NewAuthBroker(store FenceStore) *AuthBroker
NewAuthBroker builds a broker over the shared ClaimStack FenceStore.
func NewLocalAuthReceiver ¶
func NewLocalAuthReceiver(store FenceStore) *AuthBroker
NewLocalAuthReceiver keeps the prior name as an alias for tests.
func (*AuthBroker) BindRevisionReader ¶
func (b *AuthBroker) BindRevisionReader(get func(ctx context.Context, taskID string) (*Task, error)) *AuthBroker
BindRevisionReader wires live task revision into applied receipts. Required for status mutations so ambiguous recovery can bind exact evidence. Uses EncodeRevision so AuthBroker and FencedCAS share one revision format.
type AuthoritativeReceiver ¶
type AuthoritativeReceiver interface {
// Execute is the sole mutate entrypoint. backend is the remote
// Kaneo CLI/HTTP call. effectMet is LIVE provider readback only
// (never local receipt substitution): Present / Absent / Unknown.
Execute(
ctx context.Context,
taskID string,
fenceToken int64,
opID string,
expStatus, expComment string,
backend func(ctx context.Context) error,
effectMet func(ctx context.Context) (EffectState, error),
) error
}
AuthoritativeReceiver is the Kaneo-compatible fence/op acceptance boundary (FAC-147). Upstream Kaneo CLI/API do not natively fence; every production mutate must pass through Execute, which:
- holds per-task exclusive lock
- short-circuits pure applied retries
- reconciles in_progress/ambiguous ops via effectMet (no blind re-mutate)
- durably records in_progress BEFORE the remote backend runs
- runs backend, then MarkApplied with revision evidence when available
A crash after remote success and before MarkApplied leaves in_progress; restart effectMet for the SAME op sees the board effect and commits applied without a second backend call — closing provider-success/local-failure.
If no receiver is attached, production Kaneo must fail closed.
type AzureDevOpsProvider ¶
type AzureDevOpsProvider struct {
OrgURL string
Project string
PAT string
HTTPClient *http.Client
Deadlines Deadlines
Retry RetryPolicy
}
func NewAzureDevOpsProvider ¶
func NewAzureDevOpsProvider(orgURL, project, pat string) *AzureDevOpsProvider
func (*AzureDevOpsProvider) AddComment ¶
func (*AzureDevOpsProvider) ListComments ¶
ListComments implements CommentReader (FAC-145 exact effect readback). Azure writes annotations to System.History, so a symmetric readback must include history revisions as well as the comments API — otherwise a delivered effect written to History is invisible and the coordinator would re-deliver it. Both sources are paginated/bounded.
func (*AzureDevOpsProvider) UpdateStatus ¶
type BoundClient ¶
type BoundClient struct {
Inner TaskProvider
Deadlines Deadlines
}
BoundClient is the production TaskProvider wrapper: every external board call gets a configured per-op deadline and timeout/ambiguous failures are labeled BLOCKED(provider_timeout) for control-plane consumers (FAC-150). Non-Kaneo live activation remains FAC-155; this type is provider-agnostic.
func NewBoundClient ¶
func NewBoundClient(inner TaskProvider, d Deadlines) *BoundClient
NewBoundClient wraps inner with d (normalized). Inner must be non-nil.
func (*BoundClient) AddComment ¶
func (b *BoundClient) AddComment(ctx context.Context, taskID, body string) error
func (*BoundClient) AttachTaskLabel ¶
func (b *BoundClient) AttachTaskLabel(ctx context.Context, taskID, labelID string) error
func (*BoundClient) ClaimTask ¶
func (b *BoundClient) ClaimTask(ctx context.Context, taskID, role string) error
func (*BoundClient) CreateRelation ¶
func (b *BoundClient) CreateRelation(ctx context.Context, sourceID, targetID string, typ RelationType) (*Relation, error)
func (*BoundClient) CreateTask ¶
func (*BoundClient) CreateTaskLabel ¶
func (*BoundClient) DeleteRelation ¶
func (b *BoundClient) DeleteRelation(ctx context.Context, relationID, sourceID, targetID string) error
func (*BoundClient) DeleteTaskLabel ¶
func (b *BoundClient) DeleteTaskLabel(ctx context.Context, labelID string) error
func (*BoundClient) DetachTaskLabel ¶
func (b *BoundClient) DetachTaskLabel(ctx context.Context, labelID string) error
func (*BoundClient) LabelMutationAuthority ¶
func (b *BoundClient) LabelMutationAuthority() (string, error)
func (*BoundClient) ListComments ¶
ListComments forwards the CommentReader capability when the wrapped provider supports it (FAC-145 exact effect readback).
func (*BoundClient) ListProjectRelations ¶
func (*BoundClient) ListRelations ¶
func (*BoundClient) ListTaskLabels ¶
func (*BoundClient) ListTaskLabelsBulk ¶
func (b *BoundClient) ListTaskLabelsBulk(ctx context.Context, taskIDs []string) (BulkTaskLabels, error)
func (*BoundClient) ProveLabelCreation ¶
func (b *BoundClient) ProveLabelCreation(ctx context.Context, created TaskLabel, targetID, name string, opts LabelRepairOptions) error
func (*BoundClient) UpdateStatus ¶
func (b *BoundClient) UpdateStatus(ctx context.Context, taskID, status string) error
type BulkRelationProvider ¶
type BulkRelationProvider interface {
RelationProvider
// ListProjectRelations returns the full project relation multiset (deduped
// by id, dual-end agreement). May be O(board) concurrent requests when the
// provider has no single project-relation endpoint; must honor ctx deadline
// and fail closed without credentials.
ListProjectRelations(ctx context.Context, projectID string) ([]Relation, error)
}
BulkRelationProvider is the project graph surface for SnapshotGraph (FAC-159). Kaneo 0.11.x exposes only GET /api/task-relation/:taskId (no project-level relation RPC). Production ListProjectRelations is therefore an honest O(board) credentialed concurrent HTTP fan-out under the list deadline — never silent CLI fan-out, never mislabeled as O(1) bulk.
type BulkTaskLabelProvider ¶
type BulkTaskLabelProvider interface {
ListTaskLabelsBulk(context.Context, []string) (BulkTaskLabels, error)
}
BulkTaskLabelProvider is optional so providers without a native task-list label projection remain compatible and fail closed at the capability boundary. The input identities may be provider IDs or human-readable refs; the result preserves each requested identity as a map key.
type BulkTaskLabels ¶
type BulkTaskLabels struct {
Labels map[string][]TaskLabel `json:"labels"`
Requested int `json:"requested"`
Retrieved int `json:"retrieved"`
Complete bool `json:"complete"`
Truncated bool `json:"truncated"`
}
BulkTaskLabels is the result of a board-wide label read. Complete is based on the requested task identities, not on whether the provider returned an error. Consumers must check it before using Labels for board analytics. Truncated is kept as an explicit positive marker for JSON/reporting paths where a missing task would otherwise look like an empty label set.
type CLIResult ¶
CLIResult is the bounded output of one CLI invocation.
func RunCLI ¶
RunCLI executes name+args under ctx, placing the child in its own process group so cancel/timeout kills the entire tree (not just the direct child). On deadline or cancel it returns a *TimeoutError; stdout/stderr are still populated when available.
stderr is truncated to MaxCLIStderrBytes. Never log the raw args slice when it may contain tokens — callers own redaction of mutation payloads.
func RunCLIEnv ¶
func RunCLIEnv(ctx context.Context, extraEnv []string, name string, args ...string) (*CLIResult, error)
RunCLIEnv is RunCLI with optional extra environment entries (KEY=value). Used to transport HERD_FENCE / HERD_OP into production Kaneo CLI mutates so fence meta is not dropped when use_cli: true (FAC-147).
type CapabilityIssueRequest ¶
type CapabilityIssueRequest struct {
BoardTaskID string
TaskID string
TaskRef string
Repo string
Provider string
Project string
OwnerID string
Generation int64
OpID string
Action string // status | comment
Status string
Comment string
}
CapabilityIssueRequest is the lease-bound mint request (minter only).
type ClaimStack ¶
type ClaimStack struct {
Dir string
Leases *claim.SQLiteLeaseStore
Outbox *claim.SQLiteOutbox
Fences FenceStore
CAS *FencedCAS
Board *FencedBoard
Manager *claim.ClaimManager
TP TaskProvider
Minter *FenceBrokerMinter // coordinator only; nil on workers
// OwnedBroker is set when THIS process hosts the fence broker (FAC-564).
// Its lifetime is the stack's, so Close releases the claim-dir lock.
OwnedBroker *CoordinatorBroker
}
ClaimStack is the production FAC-147 wiring: durable lease store + outbox + FencedCAS (ProviderCAS) + FencedBoard. cmd/herd, daemon, and dispatch open one of these so board mutations go through BeginProviderTransition/CompleteProviderTransition (and reclaim AdvanceFence) instead of bare TaskProvider writes.
Minter is coordinator-only: loaded only when HERD_FENCE_BROKER_MINT_TOKEN is set. Workers never receive the mint secret; they present pre-minted capabilities only.
func NewTestStack ¶
func NewTestStack(t testing.TB, tp TaskProvider) *ClaimStack
NewTestStack opens an isolated ClaimStack under t.TempDir() and registers Close. Use this in fixtures so every production fail-closed path is exercised with a real stack, never a nil Claims fallback.
func NewTestStackWithBusy ¶
func NewTestStackWithBusy(t testing.TB, tp TaskProvider, busy time.Duration) *ClaimStack
NewTestStackWithBusy builds a ClaimStack whose fence exclusive lock uses a short busy_timeout (contention / timeout tests).
func OpenCanonicalClaimStack ¶
func OpenCanonicalClaimStack(tp TaskProvider) (*ClaimStack, error)
OpenCanonicalClaimStack opens the production claim stack. Prefers HERD_CLAIM_DIR when set so provisioned shared volumes are not ignored.
func OpenClaimStack ¶
func OpenClaimStack(dir string, tp TaskProvider) (*ClaimStack, error)
OpenClaimStack opens (creating if needed) the durable claim/fence/outbox files under dir and wires a ClaimManager with WithProviderCAS + WithDurableOutbox over tp. Production callers must pass the path from CanonicalClaimDir / OpenCanonicalClaimStack — not a worktree-relative ".herd/claim".
func (*ClaimStack) AcquireLease ¶
func (s *ClaimStack) AcquireLease(ctx context.Context, key claim.LeaseKey, ownerID, role, taskRole string) (*claim.Lease, error)
AcquireLease acquires a durable claim lease for taskRef. role and taskRole must satisfy ClaimManager.Claim's exact-match rules. Fail-closed: does not invent generations on conflict.
func (*ClaimStack) Close ¶
func (s *ClaimStack) Close() error
Close releases all durable stores. Safe on nil.
func (*ClaimStack) MutateClaimGuarded ¶
func (s *ClaimStack) MutateClaimGuarded( ctx context.Context, key claim.LeaseKey, ownerID, role, taskRole, taskID string, ) (*claim.Lease, error)
MutateClaimGuarded is the production ClaimTask path (status → in-progress) under a live lease generation via Begin/Complete. Fail-closed on claim conflict.
func (*ClaimStack) MutateCommentGuarded ¶
func (s *ClaimStack) MutateCommentGuarded( ctx context.Context, key claim.LeaseKey, ownerID, role, taskRole, taskID, body string, ) (generation int64, err error)
MutateCommentGuarded posts a board comment under a live lease generation via Begin/Complete. Fail-closed on claim conflict.
func (*ClaimStack) MutateStatusGuarded ¶
func (s *ClaimStack) MutateStatusGuarded( ctx context.Context, key claim.LeaseKey, ownerID, role, taskRole, taskID, status string, ) (generation int64, err error)
MutateStatusGuarded is the production board status write: Claim must succeed (live lease), then AdvanceFence(taskID, generation) + Begin/ Complete. On claim conflict the write is refused — contenders must not mint high+1 and preempt a live owner (FAC-147 audit fix).
type Clock ¶
type Clock interface {
Now() time.Time
// After returns a channel that delivers once d has elapsed. Tests may
// use a fake that closes immediately or on demand.
After(d time.Duration) <-chan time.Time
}
Clock abstracts time for deterministic retry tests.
type CommentReader ¶
type ConfigDeadlineSource ¶
type ConfigDeadlineSource interface {
// Resolved returns get,list,mutate,comment,readback durations (0 = default).
ResolvedDeadlines() (get, list, mutate, comment, readback time.Duration, err error)
}
ConfigDeadlineSource is the subset of config needed without importing pkg/config (avoids cycles). cmd and packages pass Resolved() parts.
type CoordinatorBroker ¶
type CoordinatorBroker struct {
Broker *FenceBroker
Minter *FenceBrokerMinter
Client *FenceBrokerClient
// contains filtered or unexported fields
}
CoordinatorBroker is a broker owned by this process plus the authority to mint against it.
func StartCoordinatorBroker ¶
func StartCoordinatorBroker(opts CoordinatorBrokerOptions) (*CoordinatorBroker, error)
StartCoordinatorBroker starts a fence broker INSIDE the coordinator process and returns mint authority over it.
This is the production path FAC-564 was missing. Both credentials are generated here and never leave the process: the worker token is used by this process's own client, and the mint token backs the in-process minter. Nothing is written to the claim dir and nothing is placed in the environment, so no same-UID worker can read either one.
The claim-dir flock still guarantees one live broker, so this fails closed if a standalone broker is already serving that claim volume. When a coordinator hosts its own broker, do not also run a standalone one.
func (*CoordinatorBroker) Close ¶
func (c *CoordinatorBroker) Close() error
Close releases the broker and its claim-dir lock.
type CoordinatorBrokerOptions ¶
type CoordinatorBrokerOptions struct {
ClaimDir string
// ListenAddr defaults to "unix": a socket under the claim dir, gated by
// filesystem permissions as well as the token. Set "127.0.0.1:0" when the
// claim dir path is too long for a unix socket on this platform.
ListenAddr string
UpstreamURL string
UpstreamProject string
UpstreamCLI bool
}
CoordinatorBrokerOptions configures a coordinator that hosts its own broker.
type Deadlines ¶
type Deadlines struct {
Get time.Duration
List time.Duration
Mutate time.Duration
Comment time.Duration
Readback time.Duration
}
Deadlines holds per-operation bounds. Zero fields resolve to defaults via For / Normalize so callers can override a single op without restating all.
func DeadlinesFromParts ¶
DeadlinesFromParts builds a Deadlines value from optional config-resolved durations. Zero means "package default" after Normalize.
func DeadlinesFromResolved ¶
DeadlinesFromResolved maps Resolved() output to Deadlines.
func DefaultDeadlines ¶
func DefaultDeadlines() Deadlines
DefaultDeadlines returns the repository-safe defaults for every op kind.
type EffectState ¶
type EffectState int
EffectState is live-provider readback for recovery (FAC-147 hold). UNKNOWN must never be treated as ABSENT (would re-mutate blindly).
const ( // EffectAbsent: live read succeeded and the expected effect is not present. EffectAbsent EffectState = iota // EffectPresent: live read succeeded and the expected effect is present // for THIS op (status+revision or op-tagged comment). EffectPresent // EffectUnknown: live read failed/unavailable — stay ambiguous, never re-mutate. EffectUnknown )
type FenceBroker ¶
type FenceBroker struct {
// contains filtered or unexported fields
}
FenceBroker is the production-enforcing sidecar for FAC-147. Process exclusivity is the claim-dir flock (one live broker). Per-task serialization uses in-process mutexes — not fences.db.locks — so a client CAS holding the shared store lock while calling the broker cannot deadlock.
func StartFenceBroker ¶
func StartFenceBroker(cfg FenceBrokerConfig) (*FenceBroker, error)
StartFenceBroker acquires exclusive claim-dir lock and serves. Non-copyable authority is the exclusive flock on the claim volume — not a copyable absolute claim_path row. Same logical path on a shared volume cannot host two live brokers. Cross-host without shared flock is fail-closed/undefined.
func WireHermeticFenceBroker ¶
func WireHermeticFenceBroker(t testing.TB, k *KaneoProvider, upstreamURL, claimDir string) *FenceBroker
WireHermeticFenceBroker starts an in-process FenceBroker against upstreamURL for production-shaped tests (daemon pulse, compiled claim paths). It wires worker client + claim-dir minter (after scrubbing env mint material). claimDir must be the same directory used for OpenClaimStack so leases.db is shared.
func (*FenceBroker) Addr ¶
func (b *FenceBroker) Addr() string
func (*FenceBroker) ClientBaseURL ¶
func (b *FenceBroker) ClientBaseURL() string
func (*FenceBroker) Close ¶
func (b *FenceBroker) Close() error
Close shuts down and releases flock; aggregates errors (fail-closed report). Secrets (token, mintToken) are intentionally not exported — no Token()/MintToken().
func (*FenceBroker) GrantMintToChild ¶
func (b *FenceBroker) GrantMintToChild(cmd *exec.Cmd) (func(), error)
GrantMintToChild hands this broker's mint authority to one child process over an inherited pipe, and returns a closer the parent calls after Start.
The parent writes the secret into a pipe whose read end the child inherits. The secret is never placed in the child's environment or on disk. Only this exact child can read it, because only this child has the descriptor.
func (*FenceBroker) InstanceID ¶
func (b *FenceBroker) InstanceID() string
func (*FenceBroker) SeedTestLease ¶
func (b *FenceBroker) SeedTestLease(ctx context.Context, key claim.LeaseKey, ownerID string, generation int64, ttl time.Duration) (*claim.Lease, error)
SeedTestLease inserts a live lease for tests (same claim-dir leases.db).
func (*FenceBroker) UnixSocket ¶
func (b *FenceBroker) UnixSocket() string
type FenceBrokerClient ¶
type FenceBrokerClient struct {
BaseURL string // http://127.0.0.1:port or http://unix
Token string // worker token only (never mint)
UnixSocket string
Client *http.Client
}
FenceBrokerClient is the worker-facing broker client.
It holds ONLY the worker mutate credential. Per-op capabilities are passed immutably to MutateStatus (never stored on the shared client — concurrent tasks must not cross-wire grants).
func NewFenceBrokerClientForTest ¶
func NewFenceBrokerClientForTest(b *FenceBroker) *FenceBrokerClient
NewFenceBrokerClientForTest builds a worker client for a running broker. Uses unexported broker fields only (no exported secret getters).
func NewFenceBrokerClientFromEnv ¶
func NewFenceBrokerClientFromEnv() (*FenceBrokerClient, error)
NewFenceBrokerClientFromEnv builds a worker client (no mint authority). HERD_FENCE_BROKER_MINT_TOKEN is intentionally ignored if present.
func (*FenceBrokerClient) Live ¶
func (c *FenceBrokerClient) Live(ctx context.Context) error
Live reports whether the broker is reachable (liveness).
func (*FenceBrokerClient) MutateComment ¶
func (c *FenceBrokerClient) MutateComment(ctx context.Context, taskID, commentBody string, fence int64, opID, capability string) error
MutateComment performs broker-enforced comment mutation with per-op capability.
func (*FenceBrokerClient) MutateStatus ¶
func (c *FenceBrokerClient) MutateStatus(ctx context.Context, taskID, status string, fence int64, opID, capability string) error
MutateStatus performs broker-enforced status mutation with an immutable per-op pre-minted capability. Never mints. Never stores capability on client.
type FenceBrokerConfig ¶
type FenceBrokerConfig struct {
ClaimDir string
ListenAddr string // "unix" (default), 127.0.0.1:port — never non-loopback
Token string // worker token
MintToken string // mint token (required; must differ from Token)
UpstreamURL string
UpstreamProject string
UpstreamCLI bool
}
FenceBrokerConfig configures the sidecar.
type FenceBrokerMinter ¶
type FenceBrokerMinter struct {
// contains filtered or unexported fields
}
FenceBrokerMinter is the coordinator-only mint channel.
The mint secret is unexported and never returned by methods, String(), or JSON. Construction requires the claim-dir credential file written by StartFenceBroker (mode 0600) — not HERD_FENCE_BROKER_MINT_TOKEN in env. Workers must not receive that file path; env mint alone always fails.
func CoordinatorMinterInProcess ¶
func CoordinatorMinterInProcess(b *FenceBroker) (*FenceBrokerMinter, error)
CoordinatorMinterInProcess grants mint authority to the process that OWNS the broker.
The boundary is the address space: b.mintToken was generated in this process and is never written to a file, an environment variable, or the wire, so no same-UID worker can read it. Prefer this whenever the coordinator can host the broker itself.
func NewFenceBrokerMinterFromClaimDir ¶
func NewFenceBrokerMinterFromClaimDir(claimDir, brokerURL string) (*FenceBrokerMinter, error)
NewFenceBrokerMinterFromClaimDir is BLOCKED outside hermetic tests: a mode-0600 file in a shared claim dir is readable by any same-UID worker process and is not a non-forgeable OS boundary. Production mint authority is deferred to FAC-169 (process/UID/FD boundary). Tests may use this under testing.Testing() or prefer newMinterForTest (in-process unexported secret).
func NewFenceBrokerMinterFromEnv ¶
func NewFenceBrokerMinterFromEnv() (*FenceBrokerMinter, error)
NewFenceBrokerMinterFromEnv is intentionally disabled for induction resistance. Mint secret must not be loadable from process environment alone.
func NewFenceBrokerMinterFromInheritedFD ¶
func NewFenceBrokerMinterFromInheritedFD(brokerURL string) (*FenceBrokerMinter, error)
NewFenceBrokerMinterFromInheritedFD constructs a minter from a descriptor inherited from the broker process.
The descriptor must be a PIPE. A worker cannot satisfy this by writing its own secret to a file and pointing the variable at it, and cannot satisfy it at all without an inherited descriptor: it simply has nothing at that number.
The descriptor is consumed exactly once and closed, so the secret does not linger in the descriptor table for a later exec to inherit.
func (*FenceBrokerMinter) IssueCapability ¶
func (m *FenceBrokerMinter) IssueCapability(ctx context.Context, req CapabilityIssueRequest) (string, error)
IssueCapability mints one single-use op-bound capability JSON for a worker. Returns only the capability document — never the mint secret.
func (*FenceBrokerMinter) MarshalJSON ¶
func (m *FenceBrokerMinter) MarshalJSON() ([]byte, error)
MarshalJSON never serializes the mint secret.
func (*FenceBrokerMinter) String ¶
func (m *FenceBrokerMinter) String() string
String redacts secrets (safe for logs).
type FenceStore ¶
type FenceStore interface {
Highest(ctx context.Context, taskID string) (int64, error)
Advance(ctx context.Context, taskID string, fenceToken int64) (int64, error)
WithExclusive(ctx context.Context, taskID string, fn func(ctx context.Context) error) error
// LookupApplied returns the receipt for opID if present.
LookupApplied(ctx context.Context, opID string) (*OpReceipt, error)
// ListOpsForTask returns all receipts for taskID (applied + ambiguous).
// Used for empty-rev recovery competing-same-status attribution.
ListOpsForTask(ctx context.Context, taskID string) ([]OpReceipt, error)
// MarkApplied persists a successful application. Errors must propagate.
MarkApplied(ctx context.Context, rec OpReceipt) error
// MarkAmbiguous records provider-success/local-failure ambiguity.
MarkAmbiguous(ctx context.Context, rec OpReceipt) error
BusyTimeout() time.Duration
Close() error
}
FenceStore: fence high-water + applied op receipts + per-task exclusive lock.
type FencedBoard ¶
type FencedBoard struct {
CAS *FencedCAS
TP TaskProvider
}
FencedBoard is the production consumer of FencedCAS: lease-guarded board mutations go through Begin/Complete + CompareAndSwap with a stable per-operation idempotency key (FAC-147).
func NewFencedBoard ¶
func NewFencedBoard(cas *FencedCAS, tp TaskProvider) (*FencedBoard, error)
func OpenFencedBoard ¶
func OpenFencedBoard(fenceDBPath string, tp TaskProvider) (*FencedBoard, error)
func (*FencedBoard) AddComment ¶
func (b *FencedBoard) AddComment(ctx context.Context, taskID string, fenceToken int64, body string) (claim.ProviderRevision, error)
AddComment is CAS-only helper; production uses MutateComment.
func (*FencedBoard) AdvanceFence ¶
func (*FencedBoard) ClaimOptions ¶
func (b *FencedBoard) ClaimOptions() []claim.Option
func (*FencedBoard) ClaimTask ¶
func (b *FencedBoard) ClaimTask(ctx context.Context, taskID string, fenceToken int64, role string) (claim.ProviderRevision, error)
ClaimTask is CAS-only helper; production uses MutateClaim.
func (*FencedBoard) MutateClaim ¶
func (b *FencedBoard) MutateClaim( ctx context.Context, mgr *claim.ClaimManager, key claim.LeaseKey, ownerID string, generation int64, taskID, role string, ) error
MutateClaim is Begin + Complete with kind claim.
func (*FencedBoard) MutateComment ¶
func (b *FencedBoard) MutateComment( ctx context.Context, mgr *claim.ClaimManager, key claim.LeaseKey, ownerID string, generation int64, taskID, body string, ) error
MutateComment is Begin + Complete with kind comment:<body-hash>.
func (*FencedBoard) MutateStatus ¶
func (b *FencedBoard) MutateStatus( ctx context.Context, mgr *claim.ClaimManager, key claim.LeaseKey, ownerID string, generation int64, taskID, status string, ) error
MutateStatus is Begin + Complete with kind status:<canonical>.
func (*FencedBoard) UpdateStatus ¶
func (b *FencedBoard) UpdateStatus(ctx context.Context, taskID string, fenceToken int64, status string) (claim.ProviderRevision, error)
UpdateStatus is a CAS-only helper for tests. Production status writes use MutateStatus (Begin/Complete). Each call mints a fresh op UUID so two same-state writes are distinct logical operations at the broker.
type FencedCAS ¶
type FencedCAS struct {
// contains filtered or unexported fields
}
FencedCAS is the local half of FAC-147 fencing. Authoritative acceptance of fence+op MUST occur inside mutate (Kaneo HTTP with X-Herd-* headers or an equivalent broker). Local receipts are a cache for reconcile; the provider-side applied set is the crash-safe source of truth.
func NewFencedCAS ¶
func NewFencedCAS(fences FenceStore, reader TaskProvider) (*FencedCAS, error)
func OpenFencedCAS ¶
func OpenFencedCAS(path string, reader TaskProvider) (*FencedCAS, error)
func (*FencedCAS) AdvanceFence ¶
func (*FencedCAS) CompareAndSwap ¶
func (*FencedCAS) ReadRevision ¶
type GitHubProvider ¶
type GitHubProvider struct {
Token string
Owner string
Repo string
Client *http.Client
Deadlines Deadlines
Retry RetryPolicy
}
func NewGitHubProvider ¶
func NewGitHubProvider(token string, owner string, repo string) *GitHubProvider
func (*GitHubProvider) AddComment ¶
func (*GitHubProvider) ListComments ¶
ListComments implements CommentReader (FAC-145 exact effect readback).
func (*GitHubProvider) UpdateStatus ¶
type HandbackDoneReceipt ¶
type HandbackDoneReceipt struct {
OwnerID string `json:"owner_id"`
Generation int64 `json:"generation"`
TaskRef string `json:"task_ref"`
Receipt string `json:"receipt"`
Challenge string `json:"challenge"`
ReleasedAt time.Time `json:"released_at"`
}
HandbackDoneReceipt is a generation-bound durable local handback receipt.
func ReadHandbackDone ¶
func ReadHandbackDone(worktreePath string) (*HandbackDoneReceipt, error)
ReadHandbackDone loads and validates a generation-bound done receipt.
type HandbackIntent ¶
type HandbackIntent struct {
OwnerID string `json:"owner_id"`
Generation int64 `json:"generation"`
TaskRef string `json:"task_ref"`
Repo string `json:"repo"`
Provider string `json:"provider"`
Project string `json:"project"`
Receipt string `json:"receipt"`
Challenge string `json:"challenge"`
IntentAt time.Time `json:"intent_at"`
RemoteReleased bool `json:"remote_released"`
}
HandbackIntent is written BEFORE remote Release so a crash after remote success can complete local durability without re-releasing (idempotent).
func ReadHandbackIntent ¶
func ReadHandbackIntent(worktreePath string) (*HandbackIntent, error)
ReadHandbackIntent loads pending handback intent if any.
type JiraProvider ¶
type JiraProvider struct {
BaseURL string
APIToken string
UserEmail string
HTTPClient *http.Client
Deadlines Deadlines
Retry RetryPolicy
}
func NewJiraProvider ¶
func NewJiraProvider(baseURL, userEmail, apiToken string) *JiraProvider
func (*JiraProvider) AddComment ¶
func (*JiraProvider) ListComments ¶
ListComments implements CommentReader (FAC-145 exact effect readback). Atlassian document bodies are flattened to their text runs.
func (*JiraProvider) UpdateStatus ¶
type KaneoLinkConfig ¶
type KaneoProvider ¶
type KaneoProvider struct {
APIURL string
ProjectID string
UseCLI bool
// APIKey authenticates HTTP calls (Bearer). Loaded from api_key_env / KANEO_API_KEY.
// Bulk project graph snapshots prefer HTTP fan-out even when UseCLI is true
// to avoid N CLI subprocesses (FAC-159 live-path stampede).
APIKey string
// KeyTrustedOrigin is the operator-controlled origin to which APIKey may be
// sent. It comes from KANEO_API_URL or the selected Kaneo profile, never
// from repository-controlled APIURL.
KeyTrustedOrigin string
Client *http.Client
// Deadlines bound every op; zero fields resolve to DefaultDeadlines.
Deadlines Deadlines
// Retry applies to idempotent reads only (GetTask/ListTasks).
Retry RetryPolicy
// BulkConcurrency bounds concurrent relation fetches in ListProjectRelations.
// Zero => DefaultBulkRelationConcurrency.
BulkConcurrency int
// Receiver is the local AuthBroker over fences.db (in_progress / applied).
// It is NOT a substitute for server-side fence+op enforcement.
Receiver AuthoritativeReceiver
// RequireCASMeta refuses UpdateStatus/AddComment/ClaimTask without
// CAS meta (fence+op). Set true when attached to a ClaimStack so
// unfenced bypass cannot skip the receiver.
RequireCASMeta bool
// AtomicFenceServer is true when a live FenceBroker (or hermetic enforcing
// board under test) enforces fence+op+op-dedupe with status. Production
// sets this only via ConfigureKaneoFenceBroker after health check — not a
// bare env toggle. Stock Kaneo alone is never sufficient.
AtomicFenceServer bool
// FenceBroker is the worker-facing sidecar client (mutate + op readback).
// Never holds mint credentials.
FenceBroker *FenceBrokerClient
// contains filtered or unexported fields
}
func NewKaneoProvider ¶
func NewKaneoProvider(apiURL string, projectID string, useCLI bool) *KaneoProvider
func (*KaneoProvider) AddComment ¶
func (*KaneoProvider) AttachTaskLabel ¶
func (k *KaneoProvider) AttachTaskLabel(ctx context.Context, taskID, labelID string) error
func (*KaneoProvider) CreateRelation ¶
func (k *KaneoProvider) CreateRelation(ctx context.Context, sourceID, targetID string, typ RelationType) (*Relation, error)
CreateRelation creates a directed relation with dual-end readback. Rejects self-edges and unknown types. Ambiguous create is reconciled against both source and target listings so retries never duplicate edges.
func (*KaneoProvider) CreateTask ¶
CreateTask creates a backlog card through Kaneo's authenticated API.
func (*KaneoProvider) CreateTaskLabel ¶
func (*KaneoProvider) DeleteRelation ¶
func (k *KaneoProvider) DeleteRelation(ctx context.Context, relationID, sourceID, targetID string) error
DeleteRelation deletes a relation and verifies absence on BOTH true endpoints. Caller endpoints are a hint; authoritative source/target/type are captured from readback before delete. Ambiguous delete/timeouts never succeed.
func (*KaneoProvider) DeleteTaskLabel ¶
func (k *KaneoProvider) DeleteTaskLabel(ctx context.Context, labelID string) error
func (*KaneoProvider) DetachTaskLabel ¶
func (k *KaneoProvider) DetachTaskLabel(ctx context.Context, labelID string) error
func (*KaneoProvider) LabelMutationAuthority ¶
func (k *KaneoProvider) LabelMutationAuthority() (string, error)
func (*KaneoProvider) ListComments ¶
ListComments implements CommentReader (FAC-145): exact effect readback for verdict delivery. Comment bodies are returned in board order.
func (*KaneoProvider) ListLiveComments ¶
ListLiveComments is authoritative live-provider comment readback (CLI or HTTP). NEVER substitutes local AuthBroker receipts. Failures return error (EffectUnknown); empty success means ABSENT.
func (*KaneoProvider) ListProjectRelations ¶
func (k *KaneoProvider) ListProjectRelations(ctx context.Context, projectID string) ([]Relation, error)
ListProjectRelations builds the project relation multiset for SnapshotGraph.
Honest budget (Kaneo 0.11.x / upstream): only GET task-relation/:taskId exists. This is O(board) concurrent HTTP fan-out (not O(1) bulk), measured ~4s for ~164 IDs at concurrency 16 — fits DefaultListDeadline (30s) when credentialed. Without origin-bound HTTP credentials it FAILS CLOSED before any fan-out (never silent CLI N-way stampede).
func (*KaneoProvider) ListRelations ¶
ListRelations lists Kaneo relations for a task (as source or target).
func (*KaneoProvider) ListTaskLabels ¶
func (*KaneoProvider) ListTaskLabelsBulk ¶
func (k *KaneoProvider) ListTaskLabelsBulk(ctx context.Context, taskIDs []string) (BulkTaskLabels, error)
ListTaskLabelsBulk uses the labels already projected by task list. It is a single provider operation instead of one task label command per identity. Missing identities are returned as an explicit truncated result so callers cannot mistake a partial board scan for a complete one.
func (*KaneoProvider) ProveLabelCreation ¶
func (k *KaneoProvider) ProveLabelCreation(ctx context.Context, created TaskLabel, targetID, name string, opts LabelRepairOptions) error
ProveLabelCreation binds compensation to the workspace pre/post identity: the row must have been absent before this provider's create and present with the exact name and source-free state afterward. An existing Kaneo orphan is therefore never detached or deleted by the transaction.
func (*KaneoProvider) UpdateStatus ¶
type LabelCreationProof ¶
type LabelCreationProof interface {
ProveLabelCreation(context.Context, TaskLabel, string, string, LabelRepairOptions) error
}
LabelCreationProof is required before a returned create identity may be used for attach or compensation. Backends that cannot prove generation and transaction ownership fail closed rather than risking a foreign row.
type LabelEvidenceReader ¶
type LabelEvidenceReader interface {
ReadLabelRepairEvidence(transactionID, generation, phase string) (LabelRepairEvidence, error)
}
type LabelEvidenceSink ¶
type LabelEvidenceSink interface {
RecordLabelRepairEvidence(LabelRepairEvidence) error
}
type LabelRepairEvidence ¶
type LabelRepairEvidence struct {
Repository string
Provider string
Project string
SourceTaskID string
TargetTaskID string
PreSourceLabels string
PostSourceLabels string
PreTargetLabels string
PostTargetLabels string
PreSourceSnapshot string
PostSourceSnapshot string
PreTargetSnapshot string
PostTargetSnapshot string
CanonicalRole string
TransactionID string
Generation string
Outcome string
BlockedReason string
Phase string
Revision string
Operation string
CreatedLabelID string
}
LabelRepairEvidence is the durable transaction record for a role-label mutation. It is deliberately provider-neutral so the daemon can persist it without depending on a particular board adapter.
type LabelRepairOptions ¶
type LabelRepairOptions struct {
Repository string
Provider string
Project string
Evidence LabelEvidenceSink
// TransactionID and Generation are caller-owned identities. Empty values
// are filled with a unique local transaction identity by the repair path.
TransactionID string
Revision string
Operation string
Generation string
}
type LabelTransactionError ¶
LabelTransactionError means compensation could not establish the original source and target state. Callers must persist this as BLOCKED; it is never a successful repair.
func (*LabelTransactionError) Error ¶
func (e *LabelTransactionError) Error() string
func (*LabelTransactionError) Unwrap ¶
func (e *LabelTransactionError) Unwrap() error
type LaunchSession ¶
type LaunchSession struct {
OwnerID string `json:"owner_id"`
Generation int64 `json:"generation"`
TaskRef string `json:"task_ref"`
Repo string `json:"repo"`
Provider string `json:"provider"`
Project string `json:"project"`
Receipt string `json:"receipt"`
Challenge string `json:"challenge"`
LaunchedAt time.Time `json:"launched_at"`
Completed bool `json:"completed"`
CompletedAt time.Time `json:"completed_at,omitempty"`
}
LaunchSession distinguishes never-launched from lost authority (pt5t7 #2).
func ReadLaunchSession ¶
func ReadLaunchSession(worktreePath string) (*LaunchSession, error)
ReadLaunchSession loads the durable launch session if present.
type LeaseAck ¶
type LeaseAck struct {
OwnerID string `json:"owner_id"`
Generation int64 `json:"generation"`
Receipt string `json:"receipt"`
Challenge string `json:"challenge"`
AckedAt time.Time `json:"acked_at"`
}
LeaseAck is the worker-written acknowledgement of the single capability.
func ReadLeaseAck ¶
ReadLeaseAck loads worker acknowledgement via openat O_NOFOLLOW.
type LeaseCapability ¶
type LeaseCapability struct {
OwnerID string `json:"owner_id"`
Generation int64 `json:"generation"`
TaskRef string `json:"task_ref"`
Repo string `json:"repo"`
Provider string `json:"provider"`
Project string `json:"project"`
TabID string `json:"tab_id,omitempty"`
PaneID string `json:"pane_id,omitempty"`
AgentName string `json:"agent_name,omitempty"`
Receipt string `json:"receipt,omitempty"`
Challenge string `json:"challenge,omitempty"`
ExpiresAt time.Time `json:"expires_at"`
WrittenAt time.Time `json:"written_at"`
ClaimDBHint string `json:"claim_db_hint,omitempty"`
}
LeaseCapability is the durable worker-session authority written into the task worktree after atomic handoff. Written once with final owner+receipt.
func ReadLeaseCapability ¶
func ReadLeaseCapability(worktreePath string) (*LeaseCapability, error)
ReadLeaseCapability loads capability via openat (fail-closed on any error).
type LinearProvider ¶
type LinearProvider struct {
APIKey string
ProjectID string
Client *http.Client
BaseURL string
Deadlines Deadlines
Retry RetryPolicy
// BulkConcurrency bounds concurrent relation fetches in ListProjectRelations.
// Zero => DefaultBulkRelationConcurrency. Honest O(board) fan-out — not O(1).
BulkConcurrency int
// contains filtered or unexported fields
}
func NewLinearProvider ¶
func NewLinearProvider(apiKey string) *LinearProvider
func (*LinearProvider) AddComment ¶
func (*LinearProvider) CreateRelation ¶
func (l *LinearProvider) CreateRelation(ctx context.Context, sourceID, targetID string, relType RelationType) (*Relation, error)
CreateRelation creates a dependency edge source → target.
Protocol:
- reject blank/self/unsupported (including subtask)
- idempotent exact dual-end precheck
- one mutation attempt under WithOpDeadline (never blindly retried)
- success=false / nil / malformed payload fails closed
- on timeout/ambiguous: reconcile exact edge both ends; return existing only if proven
- successful mutation requires exact dual-end readback/agreement
func (*LinearProvider) DeleteRelation ¶
func (l *LinearProvider) DeleteRelation(ctx context.Context, relationID, sourceID, targetID string) error
DeleteRelation removes a dependency edge by ID.
Protocol:
- require relation/source/target IDs
- authoritative pre-capture + exact dual-end agreement (already absent both = idempotent success)
- one mutation attempt under WithOpDeadline
- ambiguous timeout → success only if fresh absence both ends proven
- successful mutation verifies absence both ends; lingering/unknown fails closed
func (*LinearProvider) ListComments ¶
ListComments implements CommentReader (FAC-145 exact effect readback), PAGINATED via GraphQL cursors so a verdict effect stays findable behind any number of earlier comments.
func (*LinearProvider) ListProjectRelations ¶
func (l *LinearProvider) ListProjectRelations(ctx context.Context, projectID string) ([]Relation, error)
ListProjectRelations enumerates every relation involving any task in the project. Honest O(board) fan-out via ListTasks plus a fixed, bounded worker pool.
Dual-end visibility rules:
- both endpoints in project → identical dual-end observation required
- exactly one endpoint outside project → single in-project observation allowed
- half-visible in-project relation → hard error (never silently skipped)
func (*LinearProvider) ListRelations ¶
ListRelations returns every dependency edge involving taskID (outgoing + incoming).
func (*LinearProvider) UpdateStatus ¶
type MemoryFenceStore ¶
type MemoryFenceStore struct {
// contains filtered or unexported fields
}
MemoryFenceStore for tests.
func NewMemoryFenceStore ¶
func NewMemoryFenceStore() *MemoryFenceStore
func (*MemoryFenceStore) BusyTimeout ¶
func (m *MemoryFenceStore) BusyTimeout() time.Duration
func (*MemoryFenceStore) Close ¶
func (m *MemoryFenceStore) Close() error
func (*MemoryFenceStore) ListExpectedComments ¶
ListExpectedComments returns durable applied comment bodies for taskID (ambiguous/in_progress rows are excluded so pre-backend journals cannot false-prove effectMet).
func (*MemoryFenceStore) ListOpsForTask ¶
func (*MemoryFenceStore) LookupApplied ¶
func (*MemoryFenceStore) MarkAmbiguous ¶
func (m *MemoryFenceStore) MarkAmbiguous(_ context.Context, rec OpReceipt) error
func (*MemoryFenceStore) MarkApplied ¶
func (m *MemoryFenceStore) MarkApplied(_ context.Context, rec OpReceipt) error
func (*MemoryFenceStore) WithExclusive ¶
type MemoryProvider ¶
type MemoryProvider struct {
// contains filtered or unexported fields
}
func NewMemoryProvider ¶
func NewMemoryProvider() *MemoryProvider
func (*MemoryProvider) AddComment ¶
func (*MemoryProvider) AddTask ¶
func (m *MemoryProvider) AddTask(t *Task)
func (*MemoryProvider) AttachTaskLabel ¶
func (m *MemoryProvider) AttachTaskLabel(_ context.Context, taskID, labelID string) error
func (*MemoryProvider) Comments ¶
func (m *MemoryProvider) Comments(taskID string) []string
Comments returns recorded comments for tests (FAC-147 fence coverage).
func (*MemoryProvider) CreateRelation ¶
func (m *MemoryProvider) CreateRelation(ctx context.Context, sourceID, targetID string, typ RelationType) (*Relation, error)
CreateRelation implements RelationProvider with dual-end readback.
func (*MemoryProvider) CreateTask ¶
func (*MemoryProvider) CreateTaskLabel ¶
func (*MemoryProvider) DeleteRelation ¶
func (m *MemoryProvider) DeleteRelation(ctx context.Context, relationID, sourceID, targetID string) error
DeleteRelation implements RelationProvider with dual-end absence verification.
func (*MemoryProvider) DeleteTaskLabel ¶
func (m *MemoryProvider) DeleteTaskLabel(_ context.Context, labelID string) error
func (*MemoryProvider) DetachTaskLabel ¶
func (m *MemoryProvider) DetachTaskLabel(_ context.Context, labelID string) error
func (*MemoryProvider) LabelMutationAuthority ¶
func (m *MemoryProvider) LabelMutationAuthority() (string, error)
func (*MemoryProvider) ListComments ¶
ListComments implements CommentReader for exact effect readback.
func (*MemoryProvider) ListProjectRelations ¶
func (m *MemoryProvider) ListProjectRelations(ctx context.Context, projectID string) ([]Relation, error)
ListProjectRelations implements BulkRelationProvider — O(edges) in-memory.
func (*MemoryProvider) ListRelations ¶
ListRelations implements RelationProvider (FAC-159).
func (*MemoryProvider) ListTaskLabels ¶
func (*MemoryProvider) ProveLabelCreation ¶
func (m *MemoryProvider) ProveLabelCreation(_ context.Context, created TaskLabel, targetID, name string, opts LabelRepairOptions) error
func (*MemoryProvider) UpdateStatus ¶
func (*MemoryProvider) UpdateStatusAtomic ¶
func (m *MemoryProvider) UpdateStatusAtomic(ctx context.Context, taskID, status, receiptJSON string) error
UpdateStatusAtomic applies status and optional signed receipt in one step (hermetic board model of Kaneo single-PATCH atomicity). Resolves taskID by Ref when the map lookup misses (FAC-159 ref-keyed callers).
type MintIdentity ¶
type MintIdentity struct {
Repo, Provider, Project, TaskRef, OwnerID string
}
MintIdentity is immutable per-call lease binding for coordinator capability mint. Never stored on shared KaneoProvider fields (avoids concurrent cross-wire).
func MintIdentityFrom ¶
func MintIdentityFrom(ctx context.Context) (MintIdentity, bool)
MintIdentityFrom returns per-call mint identity if present.
type MutationCapability ¶
type MutationCapability struct {
Repo string `json:"repo"`
Provider string `json:"provider"`
Project string `json:"project"`
TaskRef string `json:"task_ref"`
BoardTaskID string `json:"board_task_id"`
Generation int64 `json:"generation"`
OwnerID string `json:"owner_id"`
OpID string `json:"op_id"`
Action string `json:"action"` // status | comment
Status string `json:"status"`
Comment string `json:"comment,omitempty"`
ExpiresUnix int64 `json:"expires_unix"`
InstanceID string `json:"instance_id"`
Nonce string `json:"nonce"`
MAC string `json:"mac"`
}
MutationCapability is a single-use, op-bound grant issued only after authoritative lease validation (mint credential). Workers never mint.
Action is "status" (default) or "comment". Status binds status mutations; Comment binds exact comment body for comment mutations.
func VerifyCommentCapability ¶
func VerifyCommentCapability(secret, instanceID, raw string, boardTaskID, opID, comment string, fence int64) (*MutationCapability, error)
VerifyCommentCapability checks comment-bound capability.
func VerifyMutationCapability ¶
func VerifyMutationCapability(secret, instanceID, raw string, boardTaskID, opID, status string, fence int64) (*MutationCapability, error)
VerifyMutationCapability checks status-bound capability.
func (*MutationCapability) LeaseKey ¶
func (c *MutationCapability) LeaseKey() claim.LeaseKey
LeaseKey returns the full canonical lease key bound in the capability.
type OpFailureClass ¶
type OpFailureClass string
OpFailureClass is the recovery/projection class for a provider operation error. Fleet lanes may map these to BLOCKED(provider_timeout) etc. without importing adapter internals. Empty string means no failure (err == nil).
FAC-150 owns these classes; FAC-155 owns which configured provider is live.
const ( // OpOK means err was nil. OpOK OpFailureClass = "" // OpTimeout is a bounded deadline or cancel (IsTimeout). OpTimeout OpFailureClass = "provider_timeout" // OpAmbiguous is a timed-out mutation whose write may or may not have landed. OpAmbiguous OpFailureClass = "provider_ambiguous" // OpProvider is a typed *ProviderError (HTTP/board rejection). OpProvider OpFailureClass = "provider_error" // OpOther is any other hard failure. OpOther OpFailureClass = "error" )
func ClassifyOpError ¶
func ClassifyOpError(err error) OpFailureClass
ClassifyOpError maps a provider operation error to a recovery class. Order: nil → timeout → ambiguous → ProviderError → other.
type OpReceipt ¶
type OpReceipt struct {
OpID string
TaskID string
FenceToken int64
Revision string
// BaseRevision is the live provider revision captured BEFORE remote mutate.
// Empty-rev Present recovery requires live != BaseRevision to attribute
// the effect to THIS op; otherwise refuse (pt5t7 #3).
BaseRevision string
ExpectedStatus string
ExpectedComment string
Ambiguous bool
}
OpReceipt is durable evidence that a logical mutation was accepted.
type PageAccumulator ¶
type PageAccumulator struct {
// contains filtered or unexported fields
}
PageAccumulator deduplicates items across pages by ID and reports how many fresh IDs each page contributed. Callers terminate via DecidePagination.
func NewPageAccumulator ¶
func NewPageAccumulator() *PageAccumulator
NewPageAccumulator returns an empty page accumulator.
func (*PageAccumulator) Add ¶
func (a *PageAccumulator) Add(id string) bool
Add records id if unseen. Returns true when the id is new.
func (*PageAccumulator) IDs ¶
func (a *PageAccumulator) IDs() []string
IDs returns a copy of collected unique IDs in first-seen order.
func (*PageAccumulator) IngestPage ¶
func (a *PageAccumulator) IngestPage(ids []string) (fresh int, decision PageDecision)
IngestPage adds ids from one page and returns (freshCount, decision).
func (*PageAccumulator) Len ¶
func (a *PageAccumulator) Len() int
Len returns the number of unique IDs collected so far.
type PageDecision ¶
type PageDecision int
PageDecision is the pagination control signal for multi-page listing. Termination is EMPTY-page based: a short-but-nonempty page must continue. Successful listing requires PageStopEmpty; PageStopDuplicate and the page cap are hard errors (incomplete enumeration is not success).
const ( // PageContinue means more pages may exist; caller should request next page. PageContinue PageDecision = iota // PageStopEmpty means the page had zero items — definitive end of listing. PageStopEmpty // PageStopDuplicate means the page added no new IDs (server repeating a page). // Callers must treat this as ErrDuplicatePage, not a successful soft stop. PageStopDuplicate )
func DecidePagination ¶
func DecidePagination(pageLen, freshCount int) PageDecision
DecidePagination chooses the next pagination action for a received page.
Rules (fail-closed listing completeness):
- empty page (pageLen == 0) → PageStopEmpty (only successful termination)
- page with only already-seen IDs (freshCount == 0) → PageStopDuplicate (hard error)
- otherwise continue, even when pageLen < pageSize
type Priority ¶
type Priority string
func ParsePriorityString ¶
ParsePriorityString maps common priority label strings to domain Priority type
type ProviderError ¶
type ProviderError struct {
Provider string
Op string
StatusCode int
Message string
RequestID string
Retryable bool
Body string
}
ProviderError is a typed adapter failure preserving HTTP status, retryability, and a safe body snippet for diagnostics. Callers must treat any non-nil ProviderError as a hard failure (fail-closed).
func (*ProviderError) Error ¶
func (e *ProviderError) Error() string
type ReadbackDriftError ¶
ReadbackDriftError reports a write-then-read mismatch after a mutation. Callers must not treat the mutation as successful when this is returned.
func (*ReadbackDriftError) Error ¶
func (e *ReadbackDriftError) Error() string
type Relation ¶
type Relation struct {
ID string
SourceTaskID string
TargetTaskID string
Type RelationType
CreatedAt time.Time
}
Relation is one provider relation row (immutable IDs).
type RelationProvider ¶
type RelationProvider interface {
ListRelations(ctx context.Context, taskID string) ([]Relation, error)
// CreateRelation rejects self-edges and unknown types; readbacks both ends;
// reconciles ambiguous create so retries do not duplicate.
CreateRelation(ctx context.Context, sourceID, targetID string, typ RelationType) (*Relation, error)
// DeleteRelation requires captured endpoints and verifies absence on BOTH
// source and target listings. Ambiguous delete/timeouts never return success.
DeleteRelation(ctx context.Context, relationID, sourceID, targetID string) error
}
RelationProvider is the optional dependency surface. Providers that do not implement it fail the FAC-159 gate with explicit capability unsupported. Kaneo implements full list/create/delete with dual-end readback.
type RelationType ¶
type RelationType string
RelationType is a board relation kind (Kaneo: blocks|related|subtask).
const ( RelationBlocks RelationType = "blocks" RelationRelated RelationType = "related" RelationSubtask RelationType = "subtask" )
type RetryPolicy ¶
type RetryPolicy struct {
MaxAttempts int
BaseBackoff time.Duration
MaxBackoff time.Duration
JitterFraction float64 // 0..1; 0 = no jitter (deterministic)
Clock Clock
// RetryIf decides whether an error is retryable. nil → defaultRetryable.
RetryIf func(error) bool
}
RetryPolicy configures capped exponential backoff for idempotent reads. Jitter is optional; when JitterFraction is 0, backoff is deterministic.
func DefaultReadRetry ¶
func DefaultReadRetry() RetryPolicy
DefaultReadRetry is the safe default for GetTask / ListTasks only.
type SQLiteFenceStore ¶
type SQLiteFenceStore struct {
// contains filtered or unexported fields
}
SQLiteFenceStore: durable fences + receipts; per-task flock for exclusion.
func NewSQLiteFenceStore ¶
func NewSQLiteFenceStore(path string) (*SQLiteFenceStore, error)
func NewSQLiteFenceStoreWithBusy ¶
func NewSQLiteFenceStoreWithBusy(path string, busy time.Duration) (*SQLiteFenceStore, error)
func (*SQLiteFenceStore) BusyTimeout ¶
func (s *SQLiteFenceStore) BusyTimeout() time.Duration
func (*SQLiteFenceStore) Close ¶
func (s *SQLiteFenceStore) Close() error
func (*SQLiteFenceStore) ListExpectedComments ¶
func (s *SQLiteFenceStore) ListExpectedComments(ctx context.Context, taskID string) ([]string, error)
ListExpectedComments returns durable applied comment bodies for taskID.
func (*SQLiteFenceStore) ListOpsForTask ¶
func (*SQLiteFenceStore) LookupApplied ¶
func (*SQLiteFenceStore) MarkAmbiguous ¶
func (s *SQLiteFenceStore) MarkAmbiguous(ctx context.Context, rec OpReceipt) error
func (*SQLiteFenceStore) MarkApplied ¶
func (s *SQLiteFenceStore) MarkApplied(ctx context.Context, rec OpReceipt) error
func (*SQLiteFenceStore) WithExclusive ¶
type StatusMutationReceipt ¶
type StatusMutationReceipt struct {
OpID string `json:"op_id"`
TaskID string `json:"task_id"`
Status string `json:"status"`
FenceToken int64 `json:"fence_token"`
BaseRevision string `json:"base_revision"`
Actor string `json:"actor"`
Nonce string `json:"nonce"`
IssuedAtUnix int64 `json:"issued_at_unix"`
MAC string `json:"mac"`
}
StatusMutationReceipt is an authenticated binding of one status mutation to exact op, task, status, fence, actor, and base revision.
On real Kaneo (live-probed 2026-08-03): custom JSON keys such as herdStatusReceipt are NOT persisted. The only proven atomic multi-field write is PUT /api/task/{id} with required schema fields including both status and description. The signed receipt is therefore embedded in the description footer of that same PUT — not a second comment, not an ignored custom field.
Verification uses constant-time HMAC + field equality, never substring tags.
func DecodeStatusReceiptJSON ¶
func DecodeStatusReceiptJSON(s string) (*StatusMutationReceipt, error)
DecodeStatusReceiptJSON parses a receipt from description readback.
func MintStatusReceipt ¶
func MintStatusReceipt(taskID, opID, status, baseRev, actor string, fence int64) (*StatusMutationReceipt, error)
MintStatusReceipt builds and signs a receipt for an imminent status mutate.
type StatusReader ¶
StatusReader is the read surface required for post-mutation reconciliation. TaskProvider implementations satisfy this via GetTask.
type Task ¶
type Task struct {
ID string `json:"id"`
Ref string `json:"ref"`
Title string `json:"title"`
Description string `json:"description"`
Status string `json:"status"`
Priority Priority `json:"priority"`
ProjectID string `json:"project_id"`
Labels []string `json:"labels"`
CreatedAt time.Time `json:"created_at"`
// UpdatedAt is the provider's last-mutation timestamp when available
// (Kaneo updatedAt, GitHub updated_at). Used as part of the opaque
// ProviderCAS revision token (FAC-147). Zero means the provider did
// not supply one; revision encoding falls back to status+id+createdAt.
UpdatedAt time.Time `json:"updated_at,omitempty"`
// StatusReceipt is the signed receipt extracted from the task description
// footer after an atomic status PUT (FAC-147). Not a native Kaneo field.
StatusReceipt string `json:"-"`
// Position is Kaneo's board rank; required for full-schema PUT rebuilds.
// HasPosition is true only when the provider returned a position field —
// zero is a valid board rank and must not be confused with "unknown".
Position float64 `json:"-"`
HasPosition bool `json:"-"`
// Residuals are revision-bound incompleteness records propagated into every
// dependent task packet. They never grant completion authority.
Residuals []residual.Record `json:"residuals,omitempty"`
}
func DTOToTask ¶
func DTOToTask(id, ref, title, description, status string, p Priority, projectID string, labels []string, createdAt time.Time) *Task
DTOToTask converts raw API DTO parameters to unified Herdforge Task model
func ListActiveTasks ¶
ListActiveTasks returns every non-terminal card without ever reading the terminal columns.
An unfiltered ListTasks walks all six columns, and on a real board the terminal columns dominate: the FAC board carried 525 done cards across 7 pages costing ~45s to page through, against 6 active cards. That single column exceeded the OpList deadline on its own, so every caller that discards done/archived -- deps migrate, dependency fences -- failed with a provider timeout for work it was going to throw away.
This deliberately fans out over the single-status API that every adapter already implements rather than introducing an "active" sentinel status. A sentinel would be silently misread as an unknown status by adapters that have not been taught about it, and would return zero tasks instead of failing -- an empty active board is indistinguishable from a broken query, which is exactly the failure mode a dependency fence must never hit.
Results are sorted by ref so callers get a deterministic order regardless of which column returned first.
type TaskConfig ¶
type TaskConfig struct {
Type string
APIURL string
ProjectID string
UseCLI bool
// APIKey for HTTP bulk graph fan-out (even when UseCLI is true).
APIKey string
// APIKeyTrustedOrigin is operator-controlled (KANEO_API_URL or selected
// profile origin). It must never be inferred from repository APIURL.
APIKeyTrustedOrigin string
// Optional resolved deadline parts (0 = package default).
Get, List, Mutate, Comment, Readback time.Duration
// Enabled is the repository's explicit activation policy (FAC-155): the
// only provider types this repository may activate. Empty means "exactly
// the declared Type" — never a discovered or inherited default. A Type
// outside a non-empty Enabled list is a hard error, so editing
// task_provider.type without also moving the operator policy fails closed
// instead of silently pointing the fleet at a different board.
Enabled []string
}
TaskConfig is the production config surface for building a board provider. Mirrors config.TaskProvider fields used at activation (FAC-150).
type TaskCreator ¶
TaskCreator creates one board card from an already-normalized task shape. ID, Ref, and timestamps are provider-owned on input and may be ignored by providers that mint those values.
type TaskLabel ¶
type TaskLabel struct {
ID string `json:"id"`
Name string `json:"name"`
TaskID string `json:"taskId,omitempty"`
}
TaskLabel is an ownership-bearing label row. A task-bound label must never be attached to a different task; an empty TaskID is unknown ownership and is therefore unsafe for mutation.
type TaskLabelProvider ¶
type TaskLabelProvider interface {
GetTask(context.Context, string) (*Task, error)
ListTaskLabels(context.Context, string) ([]TaskLabel, error)
CreateTaskLabel(context.Context, string, string) (TaskLabel, error)
AttachTaskLabel(context.Context, string, string) error
DetachTaskLabel(context.Context, string) error
DeleteTaskLabel(context.Context, string) error
}
TaskLabelProvider is deliberately separate from TaskProvider: old adapters cannot accidentally claim to support destructive label operations.
type TaskProvider ¶
type TaskProvider interface {
GetTask(ctx context.Context, id string) (*Task, error)
ListTasks(ctx context.Context, projectID string, status string) ([]*Task, error)
ClaimTask(ctx context.Context, taskID string, role string) error
UpdateStatus(ctx context.Context, taskID string, status string) error
AddComment(ctx context.Context, taskID string, body string) error
}
TaskProvider defines the interface for task tracking backends (Kaneo, GitHub, Linear)
func MustProductionProvider ¶
func MustProductionProvider(tc TaskConfig) TaskProvider
MustProductionProvider is for tests; panics on error.
func NewFromHerdConfig ¶
func NewFromHerdConfig(cfg *config.Config) (TaskProvider, error)
NewFromHerdConfig activates the configured task provider with FAC-150 deadlines. Linear credentials are read only from its configured api_key_env; it never falls back to Kaneo's ambient credential.
func NewProductionProvider ¶
func NewProductionProvider(tc TaskConfig) (TaskProvider, error)
NewProductionProvider builds the live TaskProvider for herd/daemon/dispatch. Each live provider requires explicit credentials; callers that need in-process tests use NewMemoryProvider / NewBoundClient directly.
func UnwrapTaskProvider ¶
func UnwrapTaskProvider(tp TaskProvider) TaskProvider
UnwrapTaskProvider returns the innermost non-BoundClient provider.
type TimeoutError ¶
type TimeoutError struct {
Provider string
Op string
Kind OpKind
Deadline time.Duration
// Cause is typically context.DeadlineExceeded or context.Canceled.
Cause error
}
TimeoutError is a typed provider-operation timeout / cancellation failure. Callers must treat it as a hard error: never map it to empty success, zero tasks, free capacity, or board advancement.
func (*TimeoutError) Error ¶
func (e *TimeoutError) Error() string
func (*TimeoutError) Timeout ¶
func (e *TimeoutError) Timeout() bool
Timeout reports true so net.Error and timeout-aware callers can classify it.
func (*TimeoutError) Unwrap ¶
func (e *TimeoutError) Unwrap() error
Source Files
¶
- active_tasks.go
- auth_receiver.go
- azure.go
- bound_client.go
- cas.go
- cas_board.go
- cas_context.go
- claim_stack.go
- cli.go
- deadline.go
- factory.go
- factory_config.go
- fence_broker.go
- fence_broker_client.go
- fence_broker_minter.go
- fence_mint_authority.go
- fence_store.go
- fromconfig.go
- github.go
- helpers.go
- herd_dir.go
- hermetic_broker.go
- jira.go
- kaneo.go
- kaneo_fence.go
- kaneo_labels.go
- kaneo_relations.go
- labels.go
- lease_capability.go
- lease_session.go
- linear.go
- linear_relations.go
- memory.go
- opstatus.go
- owner.go
- pagination.go
- provider.go
- rawcontrol.go
- readback.go
- reconcile.go
- refs.go
- relations.go
- response.go
- retry.go
- runtime_defaults.go
- shared_marker.go
- status_receipt.go
- teststack.go