Documentation
¶
Index ¶
- func ClearAssignee(merged lib.TaskFrontmatter) string
- func ClearAssigneeIfHumanReview(merged lib.TaskFrontmatter, humans HumanAssignees) string
- func ClearAssigneeUnlessHuman(merged lib.TaskFrontmatter, humans HumanAssignees) string
- func ContainsTriggerCapEscalation(body string) bool
- func ExtractBody(ctx context.Context, content []byte) (string, error)
- func ExtractFrontmatter(ctx context.Context, content []byte) (string, error)
- func FindTaskFilePath(ctx context.Context, gitClient gitclient.GitClient, taskDir string, ...) (string, lib.TaskFrontmatter, error)
- func FrontmatterValueEqual(a, b any) bool
- func HealTargetVault(fm lib.TaskFrontmatter, vaultName string)
- func TriggerCapEngaged(fm lib.TaskFrontmatter) bool
- func TriggerCapEscalationSection(triggerCount, maxTriggers int, agentName string, now libtime.DateTime) string
- type GuardDecision
- type HumanAssignees
- type ResultWriter
- type TaskPathResolver
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ClearAssignee ¶ added in v0.22.2
func ClearAssignee(merged lib.TaskFrontmatter) string
ClearAssignee is the exported form of clearAssignee for callers outside this package (the increment executor). It preserves the exact semantics — capture the current assignee into previous_assignee when non-empty, then clear assignee — so the two escalation write sites produce an identical parked state.
func ClearAssigneeIfHumanReview ¶
func ClearAssigneeIfHumanReview(merged lib.TaskFrontmatter, humans HumanAssignees) string
ClearAssigneeIfHumanReview enforces the spec-039 / spec-042 doctrine: when merged frontmatter has phase == "human_review", clear assignee via clearAssignee (which captures the prior assignee into previous_assignee if non-empty, then sets assignee to ""). Returns the prior assignee name (empty string when no clear happened OR when the prior assignee was already empty). This is the single chokepoint for the human_review assignee-clear invariant; both the result writer and the partial-update executor (spec 042) route through here.
humans is the configured set of assignee values that name a human owner (see HumanAssignees). A record already carrying one of them is left untouched: the doctrine's purpose is that an *agent* must not keep a record it parked, and clearing a human's owner instead removes the record from that human's queue — the exact failure the routing stamp writes the owner to prevent. Everything not in the set is treated as an agent and cleared, so an empty set reproduces the pre-existing behaviour exactly. The set is supplied by configuration because this package cannot distinguish a human name from an agent name on its own.
func ClearAssigneeUnlessHuman ¶ added in v0.22.7
func ClearAssigneeUnlessHuman(merged lib.TaskFrontmatter, humans HumanAssignees) string
ClearAssigneeUnlessHuman is the exported form of clearAssigneeUnlessHuman for callers outside this package — today the update-frontmatter executor's own trigger-cap branch, which clears before its human-aware chokepoint runs and so cannot rely on that chokepoint to protect the owner.
Same contract as the method: capture the current assignee for the escalation body, clear it unless it names a configured human owner, and return the name the body should report.
func ContainsTriggerCapEscalation ¶ added in v0.22.2
ContainsTriggerCapEscalation reports whether body already carries the ## Trigger Cap Escalation section. Exported so the increment executor shares the duplicate guard without re-spelling the heading.
func ExtractBody ¶
ExtractBody returns the file body — the bytes after the closing "---\n" delimiter. Returns an empty string if the body is empty; error if delimiters are missing.
func ExtractFrontmatter ¶
ExtractFrontmatter returns the YAML frontmatter string between the opening and closing "---" delimiters. Returns an error if delimiters are missing.
func FindTaskFilePath ¶
func FindTaskFilePath( ctx context.Context, gitClient gitclient.GitClient, taskDir string, id lib.TaskIdentifier, resolver TaskPathResolver, ) (string, lib.TaskFrontmatter, error)
FindTaskFilePath returns the relative path of the .md file whose frontmatter has task_identifier == id, plus the parsed existing frontmatter.
It consults resolver first: on a hit it reads exactly that one file — no directory listing — and on a miss, on a nil resolver, and on a hit whose single read or frontmatter parse fails, it falls back to listing files in taskDir via gitClient and reading each one. A resolver error is returned as-is and does not trigger the walk.
Returns ("", nil, nil) when no match is found (not an error). Returns ("", nil, err) naming both paths when more than one file carries id: the match is ambiguous, so no caller may write. Callers must check the error before treating an empty path as "not found" — the two outcomes mean opposite things.
func FrontmatterValueEqual ¶ added in v0.20.0
FrontmatterValueEqual reports whether two frontmatter values compare equal, treating numeric int/float64 pairs as equal so a JSON-decoded incoming counter (float64) equals a YAML-decoded on-disk counter (int) at steady state — equal counters must produce no guard log line (DB 6).
func HealTargetVault ¶ added in v0.7.0
func HealTargetVault(fm lib.TaskFrontmatter, vaultName string)
HealTargetVault stamps the controller's vaultName into frontmatter when the target_vault key is absent. An existing value (any value) is never overridden. This is the heal-on-write half of the frontmatter-command routing guard: a legacy task file created before target_vault stamping is written by whichever controller owns the write, and this stamp ensures the non-owning controller permanently stops falling through to that file (ShouldProcessResult and ShouldProcessFrontmatterCommand both return false for a mismatched stamp). Mutates fm in place; no-op when the key is already present.
func TriggerCapEngaged ¶ added in v0.22.2
func TriggerCapEngaged(fm lib.TaskFrontmatter) bool
TriggerCapEngaged reports whether the trigger cap applies to this frontmatter.
The cap is engaged when max_triggers is explicitly set OR a ref is present. The ref arm mirrors the executor's refusal predicate (checkTriggerBudget in agent-task-executor), which caps a ref-bearing task at MaxTriggers() — the lib default of 3 — even when max_triggers is absent. Without the ref arm a ref-bearing task is refused by the executor and never escalated by either write site, so it parks silently at its lifecycle phase with a live assignee and no job: the silent-parking defect this predicate closes.
func TriggerCapEscalationSection ¶ added in v0.22.2
func TriggerCapEscalationSection( triggerCount, maxTriggers int, agentName string, now libtime.DateTime, ) string
TriggerCapEscalationSection renders the ## Trigger Cap Escalation body section following the established precedent. Exported so the increment executor — the only site the deadline-failure path reaches — writes the same section the result writer does, rather than a second, drifting copy.
Types ¶
type GuardDecision ¶ added in v0.6.2
GuardDecision records a single ownership-guard discard: the merge kept the on-disk value of a guarded field (Kept is nil when the field was absent on disk and stays absent) and rejected a differing incoming value. Equal values produce no decision.
func MergeFrontmatter ¶ added in v0.6.2
func MergeFrontmatter( existing, incoming lib.TaskFrontmatter, ) (lib.TaskFrontmatter, []GuardDecision)
MergeFrontmatter returns a new frontmatter map with all keys from existing, overridden by all keys from incoming, then applies the field-ownership guard: controller-owned counters take the on-disk value when present on disk and are absent from the result when not; a terminal on-disk status is pinned; operator-pinned fields (cluster) always take the on-disk value, are absent from the result when absent on disk, and report every differing discarded incoming value as a decision (Kept nil when the key was absent on disk); and operator-owned routing fields (assignee, previous_assignee) take the on-disk value when present on disk, with two exceptions that apply the incoming assignee instead — an incoming empty string (the deliverer's clear) and a fresh reassignment declaration (reassignmentDeclarationKey); a discarded incoming assignee also discards the incoming task_type (the routing tuple travels together). The accumulated counters (metrics_agent_turns, metrics_interaction_count) are summed when both sides carry a numeric value, keep the on-disk value when the incoming key is absent, and take the incoming value when the key is absent on disk; a non-numeric value on either side keeps the on-disk value verbatim and is reported as a decision when the two values differ. The returned decisions name every field whose differing incoming value was discarded (equal values produce no decision). Neither input map is modified.
type HumanAssignees ¶ added in v0.22.7
type HumanAssignees map[string]struct{}
HumanAssignees is the configured set of assignee values that name a human owner.
It exists because ClearAssigneeIfHumanReview cannot tell a human from an agent by looking at a name: every assignee is a free-form string, and the controller holds no Config lister, so the identities an agent answers to (a Config's spec.assignee, e.g. pr-reviewer-agent) are not readable from here. The set is therefore supplied by configuration rather than inferred.
An empty set retains nothing, which is the pre-existing behaviour: every human_review record is cleared. That default is deliberate rather than incidental — no person's name is hard-coded in this repository — and main logs the effective set at startup so an unconfigured deployment is visible in the log rather than inferred later from records that lost their owner.
⚠️ Membership is exact string equality. An assignee that is not in the set is treated as an agent and cleared, so a typo in the configuration silently removes a human's record from their queue — the failure this type exists to prevent, reachable through misconfiguration.
func ParseHumanAssignees ¶ added in v0.22.7
func ParseHumanAssignees(csv string) HumanAssignees
ParseHumanAssignees builds the set from a comma-separated list. Surrounding whitespace and empty entries are ignored, so an unset or blank value yields an empty set rather than a set containing "".
func (HumanAssignees) Contains ¶ added in v0.22.7
func (h HumanAssignees) Contains(name string) bool
Contains reports whether name is a configured human assignee. An empty name is never a member: an unowned record has no human owner to retain.
func (HumanAssignees) IsEmpty ¶ added in v0.22.7
func (h HumanAssignees) IsEmpty() bool
IsEmpty reports whether no human assignee is configured.
func (HumanAssignees) Names ¶ added in v0.22.7
func (h HumanAssignees) Names() []string
Names returns the configured assignees, sorted, so a startup log reports the EFFECTIVE set — what the predicate will actually match — rather than the raw configuration string, which can differ by whitespace and empty entries.
type ResultWriter ¶
ResultWriter writes a Task back to the vault task file.
func NewResultWriter ¶
func NewResultWriter( gitClient gitclient.GitClient, taskDir string, vaultName string, currentDateTime libtime.CurrentDateTimeGetter, m metrics.Metrics, waiter libtime.WaiterDuration, notificationSender notifcmd.NotificationPublishCommandSender, resolver TaskPathResolver, humans HumanAssignees, ) ResultWriter
NewResultWriter creates a ResultWriter that locates task files in the vault and writes the result, committing via gitClient. vaultName is the controller's VAULT_NAME, used to heal legacy task files that lack a target_vault stamp. resolver is the scanner's identifier→path index, consulted before the vault walk; a nil resolver selects the walk-only behaviour and is a test-only value — no production call site passes nil.
type TaskPathResolver ¶ added in v0.11.3
type TaskPathResolver interface {
Resolve(ctx context.Context, id lib.TaskIdentifier) (string, bool, error)
// Record tells the resolver that relPath is the vault-relative path of the task
// file carrying id, so a later Resolve(id) hits instead of missing. The caller
// must have just established that fact — by writing that file or by a walk that
// found exactly one file carrying id. It returns nothing: recording is a cache
// fill, and a failure to record only costs a later walk. An empty id or empty
// relPath is ignored. A resolver that already knows of two or more paths for id
// keeps them, so a known duplicate is never hidden.
Record(ctx context.Context, id lib.TaskIdentifier, relPath string)
// RecordDuplicate adds relPath alongside the path(s) already known for id,
// where Record would replace them. The caller must have just established that
// relPath carries id AND that the path already known still carries it too — so
// a different known path makes id a known duplicate that Resolve reports as an
// error, and every later write refuses instead of landing on one of the two
// files. Without a known path it records relPath alone, like Record. An empty id
// or empty relPath is ignored, and a relPath already known for id is a no-op.
RecordDuplicate(ctx context.Context, id lib.TaskIdentifier, relPath string)
}
TaskPathResolver resolves a task_identifier to the vault-relative path of the task file that carries it.
Three outcomes, and a caller must not conflate them:
- (path, true, nil) — the identifier is known. path is a path the resolver itself observed; it is never built from the identifier.
- ("", false, nil) — the identifier is unknown, so the caller runs its own lookup. A miss is NOT an error. The empty identifier is never indexed, so it is unknown by construction.
- ("", false, err) — more than one file carries the identifier, and the error names both paths. A resolver error is never a miss: treating it as one re-derives the ambiguity and can silently resolve to one of the two files.
Record fills the cache between rebuilds.