role

package
v0.0.0-...-c9e24ca Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 29, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package role formats per-task messages and parses per-task results for each long-lived agent role.

The agent's system prompt lives in `.claude/agents/<role>.md`. The daemon's responsibility is to construct the per-task user message in a stable, parseable shape and to validate the typed result the agent emits via submit_result. This package owns those two ends of the contract; see docs/agents.md for the canonical schema.

All formatters are deterministic: the same input must produce the same output (no map iteration, no time.Now). Determinism matters for the prompt cache — a stable prefix + a varying tail caches well; a tail whose ordering jitters does not.

Index

Constants

View Source
const (
	// AssemblyFixerActionFileImplementationIssue files one new
	// implementation child onto the plan's MaterializedChildren and
	// dispatches its implementer in a worktree carved from the
	// cumulative plan-state. This action IS the SOR-1448 add-children
	// primitive — the operator CLI `sorcerer plan add-children` hits
	// the same daemon entrypoint.
	AssemblyFixerActionFileImplementationIssue = "file_implementation_issue"
	// AssemblyFixerActionReferBackChild transitions a parked child
	// (`awaiting_integration`) back into `feedback` with the supplied
	// concerns; the implementer re-runs the child.
	AssemblyFixerActionReferBackChild = "refer_back_child"
	// AssemblyFixerActionEscalateCapabilityGap files a NEW b/sorcerer
	// planning issue capturing the autonomy gap that prevented
	// decomposition. The original plan stays in plan_blocked for
	// operator visibility, but the autonomy bug becomes tracked
	// sorcerer-side work — there is NO operator-wait-for-fix state on
	// the managed-project plan.
	AssemblyFixerActionEscalateCapabilityGap = "escalate_capability_gap"
	// AssemblyFixerActionRetryGateTransientFlake classifies a cumulative
	// dry-run gate failure as a load-induced transient flake (different
	// timeout-class tests fail across re-runs; the failing tests sit
	// outside every parked child's diff). The daemon triggers a bounded
	// gate RE-RUN rather than referring an innocent child back — a flake
	// is none of the three fixable shapes (cross-cutting defect / single
	// owning child / planner capability gap). The judgment is the LLM's
	// (only it can read "different tests fail across runs, all
	// timeout-class, none owned by any parked child"); the new kind is
	// the actuator. Required args carry the flake evidence so the agent
	// must substantiate the call rather than emit a bare "it's a flake".
	AssemblyFixerActionRetryGateTransientFlake = "retry_gate_transient_flake"
	// AssemblyFixerActionFileMainSideFix classifies a cumulative dry-run
	// gate failure as a PRE-EXISTING, cohort-unowned defect: the SAME
	// named test fails persistently across the gate re-runs, the failing
	// test lives in a file no parked child's diff touched, and no parked
	// child owns it. SOR-2916's plan was driven to plan_blocked because
	// this cell did not exist — such a failure was forced into the
	// transient-flake bucket (AssemblyFixerActionRetryGateTransientFlake),
	// exhausted assemblyFixerFlakeRetryMaxRetries, and escalated. The new
	// kind is the missing FailureCause × Disposition cell (spec
	// SPEC-SOR-3081-v1 witness W5): the daemon files a durable fix issue
	// against the failing test's OWNING repo main line WITHOUT consuming
	// the flake-retry budget to exhaustion. Required args carry the owning
	// `repo` (validated against the failing assembly's touched repos
	// exactly as file_implementation_issue does), the fix-issue `title` /
	// `body_markdown`, and the failing-test evidence (`failing_test_names`
	// plus `preexisting_reason` — why no parked child owns them).
	AssemblyFixerActionFileMainSideFix = "file_main_side_fix"
)

AssemblyFixer action kinds. SOR-1443 Fix 4. The daemon's applyAssemblyFixerResult routes each kind to a specific mutation surface; see .claude/agents/assembly_fixer.md § "Action kinds" for the per-kind args contract and `internal/daemon/plan_assembly_fixer.go` for the dispatch routing.

View Source
const (
	CycleInitial        = "initial"
	CycleRebase         = "rebase"
	CycleFeedbackPrefix = "feedback-"
	// CyclePlanPRFix is the SOR-1329 autonomous plan-PR refer-back fix
	// cycle: the daemon checks out the plan branch directly and dispatches
	// an implementer carrying PlanPRReferBack to address the final
	// reviewer's concerns on the cumulative diff. The implementer commits +
	// pushes the plan branch directly (no per-child branch, no new PR) and
	// emits FEEDBACK_OK / IMPLEMENT_OK.
	CyclePlanPRFix = "plan-pr-fix"
)

CycleInitial / CycleRebase / CycleFeedbackPrefix are the three task modes the implementer handles on one conversation. Feedback cycles are numbered ("feedback-2") so the agent can recognize repeat rounds in its own conversation history.

View Source
const (
	PrereqBlocking = "blocking"
	PrereqFollowup = "followup"
)

PrereqKind classifies a discovered-prereq report's relationship to the origin issue. See implementer.md § "IMPLEMENT_DISCOVERED_PREREQ" for the worked examples.

  • PrereqBlocking — origin cannot proceed without the prereq; origin rolls back to waiting and the new issue is appended to origin's depends_on (current pre-SOR-933 semantics).
  • PrereqFollowup — origin ships as-is; the new issue is filed with blocked_by=origin (i.e. the new issue waits for origin, NOT the other way around). Origin's state and depends_on are untouched.

Per the prompt, FOLLOWUP is the safe default when the implementer is unsure; the structural inversion makes the wrong-direction prereq cycle (SOR-846 → SOR-927/SOR-928) impossible by construction.

View Source
const (
	PrereqShapeNew      = "new"
	PrereqShapeExisting = "existing"
)

PrereqShape discriminates the two discovered-prereq report shapes:

  • PrereqShapeNew — free-text root cause; the daemon spawns a planner to author one or more child issues.
  • PrereqShapeExisting — the implementer cites an existing Linear issue key whose work satisfies the prereq; the daemon links the dep directly without filing a Planning Issue.

SOR-939 introduced the existing shape so implementers don't force a duplicate Planning Issue when the prereq is already filed (the SOR-898/SOR-860/SOR-856 episode).

View Source
const (
	StatusNoop                     = "noop"
	StatusDiscoveredPrereqNew      = "discovered-prereq-new"
	StatusDiscoveredPrereqExisting = "discovered-prereq-existing"
	StatusPlanDefect               = "plan-defect"
	StatusSpecDefect               = "spec-defect"

	// StatusDiscoveryBlocked is the status ParseImplementerResult sets when
	// the read-only discovery phase emits the IMPLEMENT_DISCOVERY_BLOCKED
	// marker — discovery detected a problem that prevents it from completing
	// the file-mapping (an AC text contradiction, a referenced surface absent
	// from the default branch, a vacuous gate that makes the prediction
	// trivially empty). Discovery's role ends at DETECTION: it does not
	// categorize the defect (no Defect type: header), so this status carries
	// only the one-line observation in Details. The supervisor's discovery-
	// result handler routes it to blocked_user with the observation as the
	// blocked_reason. Distinct from StatusPlanDefect, which is the post-
	// discovery implementer's typed, categorized plan-validity verdict.
	StatusDiscoveryBlocked = "discovery-blocked"
)

StatusDiscoveredPrereqNew / StatusDiscoveredPrereqExisting are the two status values returned by ParseImplementerResult for the discovered-prereq path. Distinct values mean the supervisor's switch can route to different structural handlers without re-parsing the detail body.

The legacy alias "discovered-prereq" (no shape suffix) is accepted on input for backward compatibility but is normalized to StatusDiscoveredPrereqNew before reaching ImplementerResult.Status.

StatusPlanDefect (SOR-209) is the peer outcome for plan-validity defects: the issue as written cannot be honestly completed because of how it was decomposed (a forward-referencing AC, a duplicate terminal gate, an unsatisfiable-environment AC, or a falsified-premise issue), not because the code is hard. Reporting a typed plan_defect lets the supervisor route the resolution structurally instead of falling through the free-text `IMPLEMENT_FAILED` path the triager can't act on. StatusSpecDefect (spec-driven phase D) is the peer outcome for spec-validity defects: the post-discovery implementer determined the APPROVED SPEC itself is wrong — a cited requirement is unsatisfiable, self-contradictory, or falsified by origin/main — not the AC. Distinct from StatusPlanDefect (which targets the AC body via the scoped-amend cycle); a spec defect carries the cited requirement IDs + prose and routes to the spec_drafter for a narrowly-scoped spec amendment. The downstream amendment-dispatch handler is a separate child; this vocabulary only adds the typed parser outcome.

View Source
const (
	DefectForwardReferencingAC       = "forward-referencing-ac"
	DefectDuplicateTerminalGate      = "duplicate-terminal-gate"
	DefectUnsatisfiableEnvironmentAC = "unsatisfiable-environment-ac"
	DefectFalsifiedPremiseIssue      = "falsified-premise-issue"
)

DefectType classifies the kind of plan-validity defect the implementer found. The set is closed — the supervisor's plan-defect dispatch seam routes on these values, and adding a new arm requires both a new constant and a new branch.

  • DefectForwardReferencingAC: an AC references work or surfaces that haven't been filed yet / aren't on origin/main, so the implementer would have to invent them to satisfy the AC.
  • DefectDuplicateTerminalGate: two or more ACs (often across sibling issues) describe the same terminal gate, so satisfying one trivially satisfies the others and the issue's scope is vacuous.
  • DefectUnsatisfiableEnvironmentAC: an AC depends on environment state the implementer cannot influence (a build flag the CI doesn't carry, a service the worktree can't reach, an OS capability the runner doesn't have).
  • DefectFalsifiedPremiseIssue: the issue's body asserts a premise that is demonstrably false on origin/main (the file it asks to change doesn't exist, the function has the opposite behavior, the bug it describes can't be reproduced).
View Source
const (
	// PlanDefectReportedKind is the events.kind AND escalation.rule the
	// supervisor's seam writes when the implementer reports a
	// plan-validity defect. The triager's assembly keys on the escalation
	// rule to recognize a plan-defect escalation in its input queue.
	PlanDefectReportedKind = "plan_defect_reported"

	// PlanDefectEscalationAssembledKind is the events.kind AND
	// escalation.rule the triager's escalate_plan_defect executor writes
	// when it bundles the offending issue body + dependency-chain
	// neighbor bodies + plan_defect report + a concrete recommended
	// correction into a single one-glance operator escalation (SOR-215).
	// It is a signal-bearing event the recognizer must see by default —
	// internal/config keeps it out of the LowSignalEventKinds set.
	PlanDefectEscalationAssembledKind = "plan_defect_escalation_assembled"

	// PlanDefectCommentHeader is the marker line the supervisor posts at
	// the head of the Linear comment carrying the full plan_defect
	// report. The triager's assembly locates the report by matching this
	// prefix against the issue's mirrored comments in the issuestore.
	PlanDefectCommentHeader = "🧙 **sorcerer: implementer found a PLAN DEFECT**"
)

Plan-defect wire vocabulary (SOR-209 / SOR-215). These string constants are the shared contract between the supervisor's plan-defect dispatch seam (internal/daemon), the triager's per-tick payload assembly (internal/triager), and the triager's escalate_plan_defect executor (internal/daemon). They live in internal/role — the lowest shared package both internal/daemon and internal/triager already import — so the triager can detect and re-assemble a plan-defect escalation without taking a daemon import (which would re-introduce the cycle the package split avoids).

View Source
const (
	DiagnosisKindCitedPathsMissingOnMain = "cited_paths_missing_on_main"
	DiagnosisKindBranchModelMismatch     = "branch_model_mismatch"
	DiagnosisKindDepUnsatisfiable        = "dep_unsatisfiable"
	DiagnosisKindACFalsifiedPremise      = "ac_falsified_premise"
	DiagnosisKindUnknown                 = "unknown"
)

DiagnosisKind classifies the operator-side defect the implementer identified, separately from the marker's primary payload (_PLAN_DEFECT body, _DISCOVERED_PREREQ body, _NOOP reason). The audit-event surface emits `implementer_diagnosed_<kind>` so operators (and the recognizer) can grep, route, and act on the structured diagnosis without spelunking the session's JSON files (SOR-1240).

The set is closed; an emitted value outside this set is normalized to DiagnosisKindUnknown at parse time so the supervisor never has to branch on a typo.

View Source
const (
	DiagnosisActionCancelIssue = "cancel_issue"
	DiagnosisActionAmendIssue  = "amend_issue"
	DiagnosisActionWaitForDep  = "wait_for_dep"
	DiagnosisActionUnknown     = "unknown"
)

DiagnosisAction is the implementer's recommended next action for the operator. Like DiagnosisKind, the set is closed and an out-of-set value normalizes to DiagnosisActionUnknown.

View Source
const (
	TriggerInitial          = "initial"
	TriggerDiscoveredPrereq = "implementer-discovered-prereq"
	// TriggerPlanDefectScopedAmend drives the bounded scoped AC-amend
	// cycle (SOR-214): an implementer reported a plan-validity defect
	// that needs decomposition judgment rather than a mechanical fix,
	// but does NOT warrant a full replan. The planner re-emits ONLY the
	// corrected acceptance criteria on the named issues; it does not
	// re-decompose the plan or touch dependency edges.
	TriggerPlanDefectScopedAmend = "plan-defect-scoped-amend"
	// TriggerRevising drives the operator-revise cycle: the operator
	// amended a planning issue's body via `sorcerer plan refactor`,
	// putting it in plan_revising. It renders the same `TASK: replan`
	// header as TriggerDiscoveredPrereq (so the planner's Replan workflow
	// phase applies) but carries the planning issue's own key on the
	// `ISSUE:` line and the amended body on `DESCRIPTION:` — no implementer
	// escalated and no prerequisite was discovered. The distinct value
	// keeps the audit-event cycle label and the planner prompt accurate
	// instead of masquerading as an implementer-discovered-prereq replan.
	TriggerRevising = "plan-revising"
)

Planner task triggers.

View Source
const (
	ActionKindCancelIssue                 = "cancel_issue"
	ActionKindTransitionIssue             = "transition_issue"
	ActionKindAmendIssue                  = "amend_issue"
	ActionKindRejectPlan                  = "reject_plan"
	ActionKindEditConfigKnob              = "edit_config_knob"
	ActionKindFileNewIssue                = "file_new_issue"
	ActionKindEditRolePromptAndRegenerate = "edit_role_prompt_and_regenerate"
	ActionKindDefer                       = "defer"
	// ActionKindFlagRedeploy is the operator-signal escape valve for the
	// class of patterns whose resolution is OPERATIONAL — a running daemon
	// binary lags a merged invariant-behavior fix — rather than a daemon
	// mutation. It mutates no issue / plan / config state: the executor
	// appends one `recognizer_redeploy_needed` audit event keyed on the
	// running binary's build commit, deduplicated so a single stale binary
	// surfaces exactly one signal per window. The symmetric counterpart to
	// the triager→recognizer bridge.
	ActionKindFlagRedeploy = "flag_redeploy"
)

Action kinds the recognizer is trained to emit. The daemon's Tick path dispatches each one through a specific mutation surface; see `.claude/agents/recognizer.md` for the per-kind args contract and `internal/recognizer/recognizer.go` for the dispatch routing.

`ActionKindDefer` is the escape hatch for genuinely-ambiguous patterns the recognizer cannot classify. Dispatching a `defer` action writes a single `RECOGNIZER_DEFERRED: <signature> — <reason>` audit event; it is the rare path, not the default.

View Source
const (
	DispatchModeReactive   = "reactive"
	DispatchModeReflective = "reflective"
)

Dispatch modes for the recognizer (SOR-1177). Reflective is the existing timer-driven dispatch with the wide raw-event payload; reactive is the event-write-triggered dispatch with a narrow per-trigger payload. FormatRecognizerTask renders an empty DispatchMode as DispatchModeReflective for back-compat.

View Source
const (
	DecisionMerge     = "merge"
	DecisionReferBack = "refer-back"
	DecisionRebase    = "rebase"
	DecisionEscalate  = "escalate"
)

Reviewer decisions.

View Source
const (
	ConcernClassCoverageRegression       = "coverage_regression"
	ConcernClassCoverageGap              = "coverage_gap"
	ConcernClassCoverageMissingInvariant = "coverage_missing_invariant"
	// ConcernClassACContradiction is the typed carrier of an
	// implementer-acknowledged AC-contradiction signal (SOR-2674). It
	// replaces the removed free-text AC-gap phrase grep: instead of
	// scanning the reviewer's verdict prose, the reviewer stamps this
	// class on a concern, and ParseReviewerVerdict refuses a merge verdict
	// carrying it REGARDLESS of that concern's severity. Disclosing the
	// gap honestly in prose is no longer a rejection trigger.
	ConcernClassACContradiction = "ac_contradiction"
)

Coverage-check concern classes. A reviewer raising a concern from the COVERAGE_SUMMARY check (see .claude/agents/reviewer.md § "Coverage check") stamps the concern's ConcernClass with one of these so the implementer's next refer-back cycle knows which coverage rule fired:

  • ConcernClassCoverageRegression: the change modifies a covered surface and the verifier chain's pre-push gate failed against the invariant's declarer test (the change broke the invariant).
  • ConcernClassCoverageGap: the change adds NEW behavior on a covered surface without extending the invariant's declarer test to cover it.
  • ConcernClassCoverageMissingInvariant: the change touches an uncovered sensitive surface and neither the issue body justifies the gap nor the PR adds a covering invariant.
View Source
const (
	RequirementVerdictPass = "pass"
	RequirementVerdictFail = "fail"
)

Per-requirement verdict values the reviewer emits for each R-ID in a spec-driven dispatch's verifies list.

View Source
const (
	ActivationStatusPending   = "pending"
	ActivationStatusSatisfied = "satisfied"
	ActivationStatusFailed    = "failed"
)

Activation-probe outcome statuses an activation-class requirement's verdict carries (SOR-2502 / spec R10), distinct from the pre-merge RequirementVerdict Pass/Fail above: an activation criterion's truth is provable only by executing the merged system, so its cell is sourced from a daemon-evaluated probe rather than a reviewer judgment at PR-set review time. Pending is the not-yet-terminal state (the probe has produced no evidence yet); satisfied / failed are the terminal results recorded into the per-requirement verdict store.

View Source
const (
	ReviewerDetailFieldLegacy = "per_criterion"
	ReviewerDetailFieldSpec   = "per_requirement_verdict"
)

The two reviewer verdict-detail field names, single-sourced here so the clauderunner schema builder and the ParseReviewerVerdict boundary validator reference identical strings and cannot drift. The legacy (spec_id IS NULL) dispatch class requires per_criterion; the spec-driven class (a non-empty VERIFIES R-ID list) requires per_requirement_verdict.

View Source
const (
	// SpecDrafterTaskDraft is the initial multi-turn domain-elicitation
	// task: a /sorcerer request → domain + requirements + witnesses.
	SpecDrafterTaskDraft = "draft"
	// SpecDrafterTaskAmend is an operator-requested narrow change to an
	// existing spec. Same emit contract + validation pipeline as draft;
	// the new specs row chains previous_version_id to the prior version.
	SpecDrafterTaskAmend = "amend"
	// SpecDrafterTaskRevise packs the open SMT-pipeline findings → one
	// autonomous resolution per finding. One revise dispatch covers all
	// currently-open findings; the drafter returns, per finding, the single
	// resolution it judged best — an RFC-6902 JSON-Patch for a genuine defect
	// or a suppress rationale for a benign artifact (docs/spec-driven.md
	// § 7.4). Unlike draft / amend the output is a structured ReviseOutput
	// (not a fenced spec), validated against internal/spec/drafter; the daemon
	// applies each resolution and re-verifies with no operator option selection.
	SpecDrafterTaskRevise = "revise"
	// SpecDrafterTaskScopedAmend is the autonomous spec-amendment task
	// (spec-driven phase D): a materialized child reported a spec defect
	// (IMPLEMENT_SPEC_DEFECT) citing specific R-IDs. Unlike the operator
	// TASK: amend (which re-authors the full spec), the drafter emits a
	// SCOPED RFC-6902 JSON-Patch constrained to the cited R-IDs' content
	// (the daemon applies it through the revise-path ApplyPatchAtomic seam
	// and classifies it local|structural). The output rides
	// SpecDrafterResult.Patch, validated by ParseSpecDrafterScopedAmendResult.
	SpecDrafterTaskScopedAmend = "scoped_amend"
)

spec_drafter task kinds.

View Source
const (
	SpecReviewerKindCoherence        = "coherence"
	SpecReviewerKindIntentEntailment = "intent_entailment"
)

Finding-kind discriminators on a SpecReviewerFinding. SpecReviewerKindCoherence is a prose<->formal coherence drift (the default, back-compat kind); SpecReviewerKindIntentEntailment is an adversarial intent counterexample — a satisfying assignment of the formal spec under which the originating request's claim is false (orthogonal to coherence; it fires even on a coherent spec). Both map to the spec_findings.kind column verbatim.

View Source
const (
	StewardBriefLabelFlowDeltas       = "Flow-metric deltas"
	StewardBriefLabelEventDigest      = "Event digest"
	StewardBriefLabelBlockedCohort    = "Blocked cohort"
	StewardBriefLabelSuppressedRollup = "Suppressed-signal rollup"
)

Brief-component labels rendered into every wake envelope (R2). Exported so the daemon-side wake tests and the brief generator (SOR-2660) reference the canonical label strings rather than re-typing them. The four together are the "brief is complete" contract: every steward wake envelope carries all four labeled slots regardless of whether a component's content is empty.

View Source
const (
	// StewardQuestionActionTransition drives ApplyOperatorTransition with the
	// option's ToState + Reason.
	StewardQuestionActionTransition = "transition"
	// StewardQuestionActionCancel drives ApplyOperatorCancel with the option's
	// Reason.
	StewardQuestionActionCancel = "cancel"
)

Steward operator-question action kinds (R17). Each names an EXISTING operator mutation bridge the answer endpoint drives — no new writer path.

View Source
const (
	TriagerActionArchiveSession          = "archive_session"
	TriagerActionRequeueIssue            = "requeue_issue"
	TriagerActionMarkBlockedUser         = "mark_blocked_user"
	TriagerActionDoNothing               = "do_nothing"
	TriagerActionUnblockViaDepDrop       = "unblock_via_dep_drop"
	TriagerActionCascadeAbandonOrphan    = "cascade_abandon_orphan_proposal"
	TriagerActionForceConvergeToTerminal = "force_converge_to_terminal"
	TriagerActionEscalateToRecognizer    = "escalate_to_recognizer"
	TriagerActionProposeAmend            = "propose_amend"
	// TriagerActionEscalatePlanDefect (SOR-215) is the scope-ambiguity
	// plan-defect escalation. The triager reaches for it when a
	// plan-defect escalation in its input is genuine scope ambiguity —
	// not mechanically resolvable and not a clean AC re-emit — so it
	// must go to the operator, but with the full decision context
	// pre-assembled. The executor bundles the offending issue body, its
	// dependency-chain neighbors' bodies, the implementer's plan_defect
	// report, and the agent-supplied recommended_correction into a
	// single one-glance operator escalation.
	TriagerActionEscalatePlanDefect = "escalate_plan_defect"
	// TriagerActionFileSorcererSelfIssue (SOR-236) is the triager's
	// direct path to file a sorcerer-self code-fix issue without
	// routing up through the recognizer. The agent reaches for it when
	// a pattern is already correctly identified as fixable by a single
	// sorcerer-self code change. high-confidence-gated for autonomous
	// filing; medium AND low surface to the operator for approval via
	// Propose. Dedupe-gated through ApplyOperatorCreateIssue so it
	// can't spam duplicate issues.
	TriagerActionFileSorcererSelfIssue = "file_sorcerer_self_issue"
)

Triager action keys. Two cohorts: the four legacy actions inherited from the v1 one-shot escalation handler, and the five widened actions that the polling steward role can also emit. The complete vocabulary is the union; ParseTriagerResult schema-validates each row against this set.

View Source
const (
	TriagerConfidenceLow    = "low"
	TriagerConfidenceMedium = "medium"
	TriagerConfidenceHigh   = "high"
)

TriagerConfidence values. Mirrors issuestore.TriagerConfidence{Low, Medium,High} but lives here so the role package has no inbound issuestore dep.

View Source
const DiffSummaryMaxLines = 40

DiffSummaryMaxLines is the cap that ExistingIssue.DiffSummary applies per issue. The supervisor populates DiffSummary from SummarizeDiffStat(files, DiffSummaryMaxLines); the planner sees at most this many lines (39 file rows + 1 truncation marker, or 40 file rows when nothing was dropped).

View Source
const HermeticityMarker = "Hermetic generation bar"

HermeticityMarker is the load-bearing anchor the implementer role spec must carry verbatim: ExtractHermeticitySection locates the hermetic-generation guidance by this exact text, so a paraphrase in the spec would silently break the location step. The marker is asserted, not inferred.

It is the single canonical source for both the role-spec coherence test (hermeticity_rolespec_test.go, same package) and the product-surface bar resolver (daemon.HermeticGenerationBarForProject), so a marker drift breaks the location step and the R1 test together rather than only one.

View Source
const LocalDaemonName = "sorcerer-self"

LocalDaemonName is the sentinel daemon name the recognizer's validator allowlist falls back to in single-daemon mode (no monitored_daemons configured). All locally-assembled events get tagged with this name so the per-event daemon map carried in the validator context is uniform.

View Source
const ProposalRejectionBudget = 3

ProposalRejectionBudget is the maximum number of consecutive proposal-time lint rejections a planning issue may accumulate before the planner-bridge routes it to plan_blocked with the proposal_rejection_budget_exhausted reason. It mirrors the narration_retry budget (3). Tuning is out of scope; the recognizer's auto-amend loop can adjust the planner spec if the recurrence rate proves problematic.

View Source
const StewardMarkerOK = "STEWARD_OK"

StewardMarkerOK is the steward's success terminal marker. The role submits it on every wake (the per-subprocess terminal-marker contract); a payload carrying any other marker is a malformed result.

View Source
const SynthesizedRawPayloadCap = 500

SynthesizedRawPayloadCap is the per-event byte cap on the raw rejected payload the audit-event surface carries. Keeps the events table from bloating on a pathological payload while preserving enough context for post-hoc forensics.

View Source
const WedgeInvestigatorMarkerOK = "WEDGE_INVESTIGATOR_OK"

WedgeInvestigatorMarkerOK is the wedge_investigator's success terminal marker. The role submits it on every dispatch (the per-subprocess terminal-marker contract); a payload carrying any other marker is a malformed result.

Variables

Compile-time sanity that the action kind constants are exported in a stable order — used by the schema-conformance test's iteration.

View Source
var AssemblyFixerRequiredFields = map[string][]string{
	AssemblyFixerActionFileImplementationIssue: {
		"title", "body_markdown", "repo",
	},
	AssemblyFixerActionReferBackChild: {
		"child_key", "concerns",
	},
	AssemblyFixerActionEscalateCapabilityGap: {
		"sorcerer_issue_title", "sorcerer_issue_body_markdown",
	},
	AssemblyFixerActionRetryGateTransientFlake: {
		"flaky_test_names", "unowned_reason",
	},
	AssemblyFixerActionFileMainSideFix: {
		"repo", "title", "body_markdown", "failing_test_names", "preexisting_reason",
	},
}

AssemblyFixerRequiredFields enumerates every required arg name the validator enforces per kind. The schema-conformance test walks this map and asserts each name appears in the role spec body so a validator/spec drift is caught by `go test`.

CauseToDisposition is the deterministic, total three-way routing the spec (SPEC-SOR-3081-v1 R1/R2/R3) pins: every FailureCause maps to exactly one Disposition. It is the SINGLE source the daemon's disposition-dependent behavior reads, so the role-side classification vocabulary and the daemon-side apply routing can never disagree.

View Source
var MergeResolverRequiredFields = []string{
	mergeResolverFieldStatus,
	mergeResolverFieldResolvedPaths,
	mergeResolverFieldCommitSHA,
	mergeResolverFieldFailureReason,
}

MergeResolverRequiredFields is the canonical set of submit_result field names ParseMergeResolverResult validates: `status` is required on every payload, `resolved_paths` + `commit_sha` are required when status=resolved, and `failure_reason` is required when status=gave_up — so each field is required under some terminal status. The merge_resolver agent spec (.claude/agents/merge_resolver.md) documents the same set in its marker-delimited "## Submit_result schema" block. The daemon-start doctor probe merge_resolver_spec_validator_conformance (cmd/sorcererd/doctor_merge_resolver_conformance.go) reads this slice and refuses the HTTP listener bind on any field-set drift between the two — the spec-validator-conformance class's structural fix applied at the merge_resolver boundary. Keep the slice and the spec's schema block in sync; the probe is the gate that enforces it.

Functions

func ActivationResultRecordable

func ActivationResultRecordable(status string) bool

ActivationResultRecordable reports whether an activation-probe status is a terminal result that must be recorded into the per-requirement verdict store (spec R10: a non-pending probe result is recorded with evidence pointers). A satisfied / failed result is recordable; pending (and any unrecognized status) is not — the probe has produced no terminal evidence yet, so nothing is written.

func AssemblyFixerResultMarshal

func AssemblyFixerResultMarshal(r AssemblyFixerResult) string

AssemblyFixerResultMarshal is a debug helper. Not used in production; kept so a failing parse can be re-marshaled into the dispatch error message without the daemon importing encoding/json at the call site.

func AssemblyFixerSchemaContains

func AssemblyFixerSchemaContains(specBody, needle string) bool

AssemblyFixerSchemaContains is a tiny self-check helper used by the schema-conformance test to assert every action kind + every required-field name from the validator appears in the role spec body. Mirrors the pattern SOR-1424 introduced for the cross-role schema audit.

func AssemblyFixerSubmitFields

func AssemblyFixerSubmitFields() []string

AssemblyFixerSubmitFields returns the envelope field names the assembly_fixer role's submit_result validator (ParseAssemblyFixerResult) always requires: `status` (the ok/failed discriminant). The clauderunner SubmitResultContracts registry sources the role's typed-schema `required` list from here so the schema's declared properties and the validator's required-field set share ONE source of truth (the DiscoverySubmitFields precedent).

func AssemblyFixerSubmitSchema

func AssemblyFixerSubmitSchema() map[string]any

AssemblyFixerSubmitSchema is the JSON Schema for the assembly_fixer's submit_result input. Threaded into the dispatcher's RunOptions.SubmitResultValidatorCtxJSON path so the MCP-side validator rejects the same malformed shapes the post-exit ParseAssemblyFixerResult does, with the same error prose, so the agent revises in-conversation (SOR-1378).

func CanonicalExamplePayload

func CanonicalExamplePayload(role string) ([]byte, error)

CanonicalExamplePayload returns the embedded happy-path side-effect- file payload for a role. The slice is the raw JSON the agent would write to <state_dir>/<sidefile> on the success path. Callers merge in the success marker before invoking the role's typed parser to mirror what clauderunner's parseTerminal does.

Returns an error for roles without a structured side-effect file (e.g. planner). Used by cmd/sorcererd's role-contract checks.

func ConcernIsSubstantive

func ConcernIsSubstantive(severity string) bool

ConcernIsSubstantive reports whether a concern's severity warrants a refer-back feedback cycle rather than deferral to the plan PR. The ReviewConcern severity vocabulary is blocker | major | minor (enforced by ParseReviewerVerdict); blocker and major are substantive, minor is deferrable. Single-sourced so the daemon's CIPB merge-with-defer verdict-resolution rule and any follow-on invariant reuse one definition of "substantive" rather than re-deriving it (drift surface).

func DedupeSubmitFields

func DedupeSubmitFields() []string

DedupeSubmitFields returns the envelope field names the dedupe role's submit_result validator (ParseDedupeVerdict) always requires on the DEDUPE_OK path, in the order the parser enforces them: `marker` then `is_duplicate`. The clauderunner SubmitResultContracts registry sources the role's typed-schema `required` list from here so the schema's declared properties and the validator's required-field set share ONE source of truth (the DiscoverySubmitFields precedent).

func DeriveOverallVerdict

func DeriveOverallVerdict(perReq map[string]RequirementVerdict, overall string) (string, error)

DeriveOverallVerdict reconciles the reviewer's per-requirement verdict map against the overall decision it stated, returning the deterministic overall verdict. The reviewer emits the per-R-ID judgments and an overall decision independently (genuine LLM analysis); this function only checks the two agree and resolves the overall verdict — it never re-judges a requirement.

Rules, in priority order:

  • Any "fail" entry with overall == rebase or escalate: the override wins (a rebase/escalate disposition supersedes the per-requirement signal).
  • Any "fail" entry with overall == refer-back: refer-back.
  • Any "fail" entry with overall == merge (or any other value): the decision contradicts the per-requirement verdicts — return an error carrying the reviewer_invalid_output marker.
  • No "fail" entries (all pass, or an empty/nil map): the reviewer's stated overall decision is returned unchanged.

A nil map ranges zero times, so it is vacuously all-pass and yields overall.

func DescribeSynthesizedOutcome

func DescribeSynthesizedOutcome(synthesized any) string

DescribeSynthesizedOutcome returns a short human-readable descriptor of the synthesized escalation result (e.g. "decision=escalate" for a reviewer escalation, "status=gave_up" for a merge_resolver escalation). Used by the daemon's audit-event encoder so the submit_result_validation_synthesized_escalation event surface carries one searchable string per outcome shape without callers having to re-decode the typed struct.

Returns an empty string when the synthesized value's type is not one of the production role result shapes — defensive, lets the audit row still record the rest of the metadata without a typed-result detour.

func DiscoverySubmitFields

func DiscoverySubmitFields() []string

DiscoverySubmitFields returns the six typed submit_result field names the implementer_discovery dispatch requires, in canonical order. The clauderunner TypedSchemaDispatches registry sources its RequiredFields from here so the typed schema's declared `properties` and the validator's required-field set share ONE source of truth — the submit-result-schema- completeness invariant then holds for the discovery entry by construction.

func EncodeAssemblyFixerValidatorCtx

func EncodeAssemblyFixerValidatorCtx(vctx AssemblyFixerValidatorContext) []byte

EncodeAssemblyFixerValidatorCtx marshals an AssemblyFixerValidatorContext to the canonical wire bytes the assembly_fixer dispatch threads onto RunOptions.SubmitResultValidatorCtxJSON. The two sets are rendered as sorted lists for a deterministic encoding; DecodeAssemblyFixerValidatorCtx folds each back into a boolean-presence map.

func EncodeRecognizerValidatorCtx

func EncodeRecognizerValidatorCtx(vctx RecognizerValidatorContext) []byte

EncodeRecognizerValidatorCtx marshals a RecognizerValidatorContext to the canonical wire bytes the recognizer dispatch threads onto RunOptions.SubmitResultValidatorCtxJSON. DecodeRecognizerValidatorCtx is its exact inverse.

func EncodeTriagerValidatorCtx

func EncodeTriagerValidatorCtx(vctx TriagerValidatorContext) []byte

EncodeTriagerValidatorCtx marshals a TriagerValidatorContext to the canonical wire bytes the triager dispatch threads onto RunOptions.SubmitResultValidatorCtxJSON. DecodeTriagerValidatorCtx is its exact inverse.

func ExtractHermeticitySection

func ExtractHermeticitySection(body string) string

ExtractHermeticitySection returns the hermetic-generation paragraph from a role-spec body: the run of text from the start of the line carrying HermeticityMarker up to the next blank line (paragraph break) or end of body. Returns the empty string when the marker is absent. Scoping the language-literal check to this single paragraph keeps the bar honest — the bar itself must name no language, even though the rest of the spec freely discusses Go gates, Rust workspaces, etc.

It is the single canonical hermetic-section parser, shared by the role-spec coherence test (which scopes its language-literal check to the returned paragraph) and the product-surface bar resolver (daemon.HermeticGenerationBarForProject, which returns it as the project's hermetic-generation bar). Living in the role package keeps it free of any config import (config imports role, so the reverse would cycle).

func Family

func Family(role string) string

Family maps a role string that reaches the daemon's openSession seam to the single role-family that shares one execution claim per issue. Every role that opens a session belongs to exactly one family: the implementer-phase roles (implementer / implementer_discovery / per_child_pr_open / scoped_amender) collapse to "implementer", the reviewer-phase roles (reviewer / reviewer_discovery / plan_pr_open) collapse to "reviewer", the planner roles (planner / plan_reviewer) collapse to "planner", and merge / spec / assembly_fixer each stand alone. Collapsing the discovery- and PR-open phases of a role into one family is what makes the one-claim-per-(issue, family) property hold: a discovery session and the implementing session it feeds are the same claim, not two.

A role string not in the map returns the empty string. claimDispatch reads an empty family as "unknown role — pass through unguarded" rather than treating "" as a real family, so a future openSession role added without updating this map dispatches normally instead of silently colliding with every other unmapped role. The exhaustiveness of the mapping over the known openSession roles is locked by family_test.go.

func FindAcGapPhrase

func FindAcGapPhrase(text string) (string, bool)

FindAcGapPhrase scans text for any AC-gap phrase. Returns the matched phrase (lowercased canonical form) and ok=true on a match. Empty text never matches.

func FormatAssemblyFixerTask

func FormatAssemblyFixerTask(t AssemblyFixerTask) string

FormatAssemblyFixerTask renders an AssemblyFixerTask as the structured task message. Deterministic — no map iteration, no time.Now() — so repeated calls with the same inputs produce byte-identical output (the determinism guarantee the test suite pins).

Format follows .claude/agents/assembly_fixer.md § "Inputs".

func FormatDedupeTask

func FormatDedupeTask(t DedupeTask) string

FormatDedupeTask renders a DedupeTask as the structured task message the agent's system prompt is trained to read. Format matches .claude/agents/dedupe.md § "Inputs".

PayloadJSON is emitted as an indented YAML literal block so the daemon's escaping is symmetric with how the recognizer's PAYLOAD block is structured — the agent strips one level of indentation before passing to a JSON parser.

func FormatImplementerTask

func FormatImplementerTask(t ImplementerTask) string

FormatImplementerTask renders an ImplementerTask as the structured task message the agent's system prompt is trained to read. Format matches docs/agents.md § "Role: implementer".

Output is deterministic: maps are sorted by key.

func FormatMergeResolverTask

func FormatMergeResolverTask(t MergeResolverTask) string

FormatMergeResolverTask renders a MergeResolverTask as the structured task message. Deterministic — no map iteration, no time.Now() — so repeated calls with the same inputs produce byte-identical output (the determinism guarantee the test suite pins).

Format follows .claude/agents/merge_resolver.md § "Inputs".

func FormatPlanReviewerTask

func FormatPlanReviewerTask(t PlanReviewerTask) string

FormatPlanReviewerTask renders the structured task message the plan_reviewer prompt is trained to read.

func FormatPlannerTask

func FormatPlannerTask(t PlannerTask) string

FormatPlannerTask renders a PlannerTask as the structured message the planner system prompt is trained to read. Format follows agents.md "Role: planner".

func FormatRecognizerTask

func FormatRecognizerTask(t RecognizerTask) string

FormatRecognizerTask renders a RecognizerTask as the structured task message the agent's system prompt is trained to read. Format matches `.claude/agents/recognizer.md` § "Inputs".

PayloadJSON is emitted as an indented YAML literal block so the daemon's escaping is symmetric with how the planner's REQUEST block is structured — the agent strips one level of indentation before passing to a JSON parser.

ValidatorContext is intentionally NOT emitted onto the wire: it is a daemon-side hint the validator reads after the agent submits its result.

Window fields render as `none` when both `From` and `To` are 0 — the SOR-1178 reflective signal for "no event window" (the trend-summary payload carries no raw events). The reactive path continues to set the fields to the trigger event's id/ts.

func FormatReviewerTask

func FormatReviewerTask(t ReviewerTask) string

FormatReviewerTask renders a ReviewerTask as the structured task message. Format follows docs/agents.md § "Role: reviewer".

func FormatSpecDrafterTask

func FormatSpecDrafterTask(t SpecDrafterTask) string

FormatSpecDrafterTask renders a SpecDrafterTask as the structured task message the spec_drafter system prompt reads. Deterministic: the same input always produces the same output (prompt-cache stability).

func FormatSpecReviewerTask

func FormatSpecReviewerTask(t SpecReviewerTask) string

FormatSpecReviewerTask renders a SpecReviewerTask as the structured task message the spec_reviewer system prompt reads. Deterministic: the same input always produces the same output (prompt-cache stability).

func FormatStewardTask

func FormatStewardTask(t StewardTask) string

FormatStewardTask renders the steward dispatch envelope: the wake trigger plus the daemon-precomputed brief, with each of the four brief components in its own labeled slot (R2). The envelope carries ONLY the precomputed brief and a directive to read it — never an instruction to scan raw event tables or query a data store (R3). An empty brief component renders as "(none)" so the envelope always carries all four labeled slots.

func FormatTriagerTask

func FormatTriagerTask(t TriagerTask) string

FormatTriagerTask renders a TriagerTask as the structured task message the agent's system prompt reads. Format matches `.claude/agents/triager.md` § "Inputs". PayloadJSON is emitted as an indented YAML literal block so the daemon's escaping is symmetric with how FormatRecognizerTask and the planner's REQUEST block are structured — the agent strips one level of indentation before passing to a JSON parser. ValidatorContext is intentionally NOT emitted onto the wire: it is a daemon-side hint the validator reads after the agent submits its result.

func FormatWedgeInvestigatorTask

func FormatWedgeInvestigatorTask(t WedgeInvestigatorTask) string

FormatWedgeInvestigatorTask renders a WedgeInvestigatorTask as the structured task message the agent's system prompt is trained to read. Format matches .claude/agents/wedge_investigator.md § "Inputs": the TASK header, the per-wedge KEY lines, and an indented DOSSIER literal block (the dedupe PAYLOAD-block idiom). The payload is self-contained; the agent reads from it and from the live issuestore / worktree / codebase via its read tools.

func ImplementerSubmitFields

func ImplementerSubmitFields() []string

ImplementerSubmitFields returns the envelope field names the implementer role's submit_result validator (ParseImplementerResult) always requires: the terminal `marker`. The clauderunner SubmitResultContracts registry sources the role's typed-schema `required` list from here so the schema's declared properties and the validator's required-field set share ONE source of truth (the DiscoverySubmitFields precedent).

func ImplementerSubmitSchema

func ImplementerSubmitSchema() map[string]any

ImplementerSubmitSchema is the JSON Schema for the implementer's submit_result input. The Conversation passes this to the API so Anthropic enforces the shape at tool-use time. Validation against ParseImplementerResult catches anything the API permits but the daemon rejects.

func MarshalDedupeVerdict

func MarshalDedupeVerdict(v DedupeVerdict) (map[string]any, error)

MarshalDedupeVerdict is a small helper used by the tests' fake dispatcher path: it lets a test write a JSON-shaped payload map that ParseDedupeVerdict will accept. Production code uses the runner's parseTerminal pipeline.

func MaybeUnwrapJSONString

func MaybeUnwrapJSONString(raw any) any

MaybeUnwrapJSONString is the bare-detector sibling of NormalizeMaybeJSONString: it returns the JSON-decoded value when raw is a string whose trimmed contents start with `[` or `{` and parse via json.Unmarshal, otherwise raw is returned unchanged.

Use this where the downstream is a strict-shape type-assertion (e.g. `raw.([]any)`) rather than a normalizer with its own error message — the unwrap removes the JSON-string layer so the assert sees the structured value the agent intended. The two liberal helpers in normalize.go (NormalizeArrayOrKeyedObject and NormalizeStringList) call this internally; raw-cast callsites in other Parse*Result functions call it explicitly.

Decode anchoring matches NormalizeMaybeJSONString: only strings whose trimmed first byte is `[` or `{` are attempted. Bare scalars (`"42"`, `"true"`, `"null"`, `"\"foo\""`) and prose strings (`"see notes"`, `"n/a"`) pass through unchanged so downstream callers can produce their existing error messages on those classes.

func MergeResolverSubmitFields

func MergeResolverSubmitFields() []string

MergeResolverSubmitFields returns the envelope field names the merge_resolver role's submit_result validator (ParseMergeResolverResult) always requires: `status` (the resolved/gave_up discriminant the terminal-shape checks branch on). The clauderunner SubmitResultContracts registry sources the role's typed-schema `required` list from here so the schema's declared properties and the validator's required-field set share ONE source of truth (the DiscoverySubmitFields precedent).

func MergeResolverSubmitSchema

func MergeResolverSubmitSchema() map[string]any

MergeResolverSubmitSchema is the JSON Schema for the merge_resolver's submit_result input.

func NewAssemblyFixerDispatcher

func NewAssemblyFixerDispatcher(runner ConversationRunner, effort, model string) func(context.Context, AssemblyFixerTask) (AssemblyFixerResult, []string, error)

NewAssemblyFixerDispatcher returns a function that runs one plan-branch cumulative-gate fix-up task on the `assembly_fixer` role and returns the typed result. Mirrors NewMergeResolverDispatcher's shape: nil-runner / nil-payload fall out as typed errors; the payload is parsed via ParseAssemblyFixerResult.

AddDirs carries the daemon's project root so the agent can `read_file <ARTIFACT_PATH>` (the artifact lives under `<projectRoot>/.sorcerer/plan-assemble/<plan-key>/`). Empty is permitted in tests; production wiring always supplies the root.

SubmitResultValidatorCtxJSON threads the parked-children set + the failing assembly's repo allowlist into the MCP-side validator so the same rejection rules (unknown child_key, unknown repo) fire at the tool-call boundary, matching the post-exit ParseAssemblyFixerResult behavior. SOR-1378 boundary; SOR-1443 Fix 4.

func NewDedupeDispatcher

func NewDedupeDispatcher(runner ConversationRunner, effort, model string) func(context.Context, DedupeTask) (DedupeVerdict, error)

NewDedupeDispatcher returns a function that runs one dedupe task on the `dedupe` role and returns the typed verdict. The dedupe gate reads no project files (allowed_tools restricted to submit_result against the daemon-supplied context payload only), so AddDirs is empty.

func NewImplementerDispatcher

func NewImplementerDispatcher(runner ConversationRunner, effort, model string) func(context.Context, ImplementerTask) (ImplementerResult, error)

NewImplementerDispatcher returns a function that satisfies daemon.ImplementerDispatcher. It formats the task, runs it on the "implementer" conversation, and parses the result.

Errors are folded back to the supervisor verbatim — the supervisor converts them to escalations.

func NewMergeResolverDispatcher

func NewMergeResolverDispatcher(runner ConversationRunner, effort, model string) func(context.Context, MergeResolverTask) (MergeResolverResult, error)

NewMergeResolverDispatcher returns a function that runs one squash-conflict-resolution task on the `merge_resolver` role and returns the typed result. Mirrors NewReviewerDispatcher / NewPlanReviewerDispatcher's shape: nil-runner / nil-payload fall out as typed errors; the payload is parsed via ParseMergeResolverResult.

RunOptions.AddDirs is the single-entry list `[task.WorktreePath]` so the runner adds the assembler's ephemeral worktree to the subprocess's allowed-dirs (the resolver reads + writes inside that worktree).

func NewPlanReviewerDispatcher

func NewPlanReviewerDispatcher(runner ConversationRunner, effort, model string) func(context.Context, PlanReviewerTask) (PlanReviewerVerdict, error)

NewPlanReviewerDispatcher returns a function that runs one plan-review task on the plan_reviewer role and returns the typed verdict. The reviewer reads the proposed-state children via the daemon's read CLIs, then calls `sorcererd plan review` with its approve/reject decision; the post-CLI verdict lands in review.json for the daemon's boot-replay path. The proposal_id surfaces in both the marker detail and the verdict body — we plumb either through.

func NewPlannerDispatcher

func NewPlannerDispatcher(runner ConversationRunner, effort, model string) func(context.Context, PlannerTask) (PlannerResult, error)

NewPlannerDispatcher returns a function that satisfies the planner dispatcher signature. Under the v2 proposals lifecycle the planner has no side-effect file; the marker's detail line carries the proposal id (e.g. "submitted proposal 42 with 3 issue(s)"), which we lift into the typed result for the supervisor to project.

func NewRecognizerDispatcher

func NewRecognizerDispatcher(runner ConversationRunner, effort, model string) func(context.Context, RecognizerTask) (RecognizerResult, error)

NewRecognizerDispatcher returns a function that runs one recognizer task on the `recognizer` role and returns the typed result. The recognizer reads no project files (allowed_tools restricted to read_file + submit_result against the daemon- supplied context payload only), so AddDirs is empty.

func NewReviewerDispatcher

func NewReviewerDispatcher(runner ConversationRunner, effort, model string) func(context.Context, ReviewerTask) (ReviewerVerdict, error)

NewReviewerDispatcher returns a function that satisfies daemon.ReviewerDispatcher.

func NewSpecDrafterDispatcher

func NewSpecDrafterDispatcher(runner ConversationRunner, effort, model string) func(context.Context, SpecDrafterTask) (SpecDrafterResult, error)

NewSpecDrafterDispatcher returns a function that runs one spec_drafter task (TASK: draft / amend) on the long-lived spec_drafter conversation and returns the typed result. Mirrors NewPlannerDispatcher's shape: the drafter submits its canonical spec via submit_result (persisted to the spec_draft.json side-effect file), then prints the SPEC_OK terminal marker; the runner merges the marker onto the payload and this dispatcher validates it via ParseSpecDrafterResult.

AddDirs exposes the per-(role, session, repo) worktrees so the drafter can read CLAUDE.md / docs for domain context. The semantic verification pipeline runs daemon-side after this returns — the dispatcher only surfaces the SHAPE-validated draft.

func NewSpecReviewerDispatcher

func NewSpecReviewerDispatcher(runner ConversationRunner, effort, model string) func(context.Context, SpecReviewerTask) (SpecReviewerVerdict, error)

NewSpecReviewerDispatcher returns a function that runs one spec_review task on the `spec_reviewer` role and returns the typed coherence verdict. The spec_reviewer is the read-only prose<->formal coherence judge (the formal-spec analog of how reviewer mirrors implementer): it reads the spec YAML + originating request from the task envelope and reads no project files, so AddDirs is empty — mirroring NewDedupeDispatcher / NewRecognizerDispatcher rather than NewSpecDrafterDispatcher.

func NewStewardDispatcher

func NewStewardDispatcher(runner ConversationRunner, effort, model string) func(context.Context, StewardTask) (StewardResult, error)

NewStewardDispatcher returns a function that runs one steward wake on the long-lived, resumable steward conversation and returns the typed result. Mirrors NewSpecDrafterDispatcher's long-lived-conversation contract: it formats the task, runs it on the "steward" role, and parses the payload.

R1 — resumption across restarts: opts.ResumeConversationID is set unconditionally to task.ConversationHandle. On the first dispatch the handle is empty (no `--resume` flag, fresh conversation); on every subsequent wake it carries the conversation id read from the persisted state.Conversations["steward"], so the runner threads `--resume <id>` and the subprocess continues the same conversation — surviving a daemon restart because the handle is durable state. The result's ConversationHandle (parsed from conversation_id) is what the daemon persists back.

The dispatcher parses the payload directly via ParseStewardResult rather than through lookupDispatchContract — the long-lived-conversation dispatcher shape; the boundary SubmitResultContract entry validates the same shape at submit.

func NewTriagerDispatcher

func NewTriagerDispatcher(runner ConversationRunner, effort, model string) func(context.Context, TriagerTask) (TriagerResult, error)

NewTriagerDispatcher returns a function that runs one triager task on the `triager` role and returns the typed result. The triager reads no worktree files (its allowed_tools are read_file against the daemon-supplied payload + gh for read-only PR / commit verification), so AddDirs is empty. The role's typed output lands in the per-session actions.json side-effect file that runner.parseTerminal reads back via sideEffectPath("triager"); the terminal marker on stdout is TRIAGER_OK / TRIAGER_FAILED. Parallel to NewRecognizerDispatcher.

func NewWedgeInvestigatorDispatcher

func NewWedgeInvestigatorDispatcher(runner ConversationRunner, effort, model string) func(context.Context, WedgeInvestigatorTask) (WedgeInvestigatorResult, error)

NewWedgeInvestigatorDispatcher returns a function that runs one wedge_investigator dispatch on the `wedge_investigator` role and returns the typed result. The investigator reads the live issuestore / worktree / git / daemon source / gate output via its read tools, so the daemon-side wiring (a sibling child) threads the project root through AddDirs; this dispatcher passes only the per-call effort / model — mirroring NewDedupeDispatcher's single-shot shape. nil-runner / nil-payload fall out as typed errors (no panic); the payload is parsed through the SAME post-exit dispatch contract the boundary keyed on (lookupDispatchContract), as the SOR-2512 contract-routed extraction requires.

func NormalizeArrayOrKeyedObject

func NormalizeArrayOrKeyedObject(raw any, keyField string) ([]map[string]any, error)

NormalizeArrayOrKeyedObject accepts either an array of objects or an object keyed by a natural string identifier, and returns a normalized slice of objects where each element carries the key promoted into the keyField property.

The helper exists because Claude reliably emits "items keyed by natural identifier" fields as `{"<key>": {...}, "<key2>": {...}}` instead of the spec'd `[{<keyField>: "<key>", ...}, ...]` array shape, even when the prompt screams that an array is required — four production reviewer crashes between 2026-05-22 and 2026-05-24 (archers SOR-1258 / SOR-1263, b/sorcerer SOR-1382 / SOR-1387) were the proximate motivation. Prompt sharpening (PR #643 the morning of 2026-05-24) explicitly documented "must be a JSON array" with GOOD/BAD examples; SOR-1387 crashed within 30 minutes of the merge. Parsers at the role boundary are now liberal in what they accept and conservative in what they produce (the canonical Go shape, fed downstream as today).

Inputs:

  • raw: the value from a submit_result payload field (the result of decoding JSON into a Go any). Expected shapes: []any of map[string]any (the array form) map[string]any of map[string]any (the object-keyed form)
  • keyField: the property name into which the object form's key should be promoted (e.g. "criterion" for per_criterion, "key" for amended_issues).

Returns the normalized []map[string]any. If raw is nil, returns (nil, nil) without error.

Order: array form preserves the input order. Object form's order is Go map iteration order (NOT insertion order — Go randomizes iteration to discourage relying on it). Callers that need insertion order for the object form should preserve it upstream (e.g. by decoding the raw JSON bytes via json.Decoder.Token before this helper). Most current callers do not require stable order across the object form because the keyField itself disambiguates entries.

Error cases:

  • raw is neither an array nor an object: returns an error citing the actual Go type.
  • array element is not an object: returns an error citing the index and Go type.
  • object value is not an object: returns an error citing the key and Go type.
  • object form conflict: a key in the outer object already has a non-empty value at keyField inside the value object that differs from the outer key. Returns an error so silent data-shape contradiction can't slip through (e.g. the planner would never intentionally emit `{"SOR-1": {"key": "SOR-2", ...}}`).

func NormalizeMaybeJSONString

func NormalizeMaybeJSONString(raw any, inner func(any) (any, error)) (any, error)

NormalizeMaybeJSONString routes a payload field through inner, first absorbing the double-JSON-encoded-string variant where the agent emitted the structured value as a JSON-encoded string instead of the structured shape. Three cases:

  1. raw is not a string — inner(raw) is called unchanged.
  2. raw is a string whose contents successfully decode as a JSON array or object via json.Unmarshal — inner(decoded) is called with the decoded value, as if the agent had emitted the structured shape directly.
  3. raw is a string that doesn't look like or doesn't parse as JSON — inner(raw) is called with the original string. The downstream's existing strict-shape rejection (e.g. NormalizeArrayOrKeyedObject's `must be an array or object (got string)` error) fires unchanged, so prose strings like "see notes" or "n/a" produce the same error they always would.

The helper exists because the reviewer occasionally double-JSON- encodes a structured field — e.g. `{"per_criterion": "[{\"criterion\": \"...\",...}]"}` instead of `{"per_criterion": [{"criterion": "...", ...}]}`. Archers SOR-1274 (2026-05-24 08:34) and b/sorcerer SOR-1419 (2026-05-24 10:10) both wedged on this exact shape; the in-conversation N=2 retry could not coax the agent off the string shape. Promoting the parser to absorb the double-encoding lets these reviews complete without operator intervention.

Typical callsite:

perCriterionRows, err := NormalizeMaybeJSONString(perCriterionRaw, func(v any) (any, error) {
    return NormalizeArrayOrKeyedObject(v, "criterion")
})

The inner-routing shape (vs a bare detector that returns just the decoded value) keeps the unwrap-then-normalize compose contract inside the helper, so callers can't accidentally skip the inner step on the non-string branch. It also keeps the prose-string path explicit: the inner sees the raw string and returns its existing error, rather than the helper silently swallowing the unwrap miss.

Decode anchoring. The helper only attempts json.Unmarshal when the trimmed string starts with `[` or `{`. Bare scalars like `"42"`, `"true"`, `"null"`, or a quoted-string `"\"foo\""` are not unwrapped — those would surface as scalar Go values that wouldn't satisfy a shape-expecting inner anyway, and the prose-string fall-through path is the cleaner error surface. The reviewer's observed failure mode is always an array-as-string or object-as-string, so anchoring on those prefixes is sufficient to absorb the production regression class.

func NormalizeStringList

func NormalizeStringList(raw any, valueField string) ([]string, error)

NormalizeStringList accepts multiple natural shapes for a string-list field and returns a normalized []string. It is the string-array peer of NormalizeArrayOrKeyedObject: same liberal-in / canonical-out discipline at the role boundary, applied to fields where each item is itself a string rather than an object.

The helper exists because the agent reliably reaches for shapes that aren't the spec'd "array of strings" even when the prompt demands one. Fields like merge_resolver.resolved_paths, plan_reviewer.edits_made / concerns_unfixed, and implementer.offending_acs are nominally `[]string` but observed in production as array-of-objects (each carrying the string under a property like "path" or "description") or object-keyed-by-the-string. Strict parsers at the role boundary that reject these variants wedge the dispatch (SOR-1379 assembly-crash class). Parsers at the role boundary stay liberal in what they accept and conservative in what they produce (the canonical Go []string downstream).

Inputs:

  • raw: the value from a submit_result payload field (the result of decoding JSON into a Go any). Expected shapes: []any of string (the canonical array form) []any of map[string]any with valueField present (array-of-objects form) map[string]any (object-keyed form; keys ARE the strings, values ignored)
  • valueField: the property name to extract from object elements in the array-of-objects form. For merge_resolver.resolved_paths use "path"; for plan_reviewer.edits_made use "description"; for plan_reviewer.concerns_unfixed use "concern"; for implementer.offending_acs use "text". An empty valueField is legal but disables the array-of-objects form — object elements then reject with a typed error.

Returns the normalized []string. If raw is nil, returns (nil, nil) without error.

Order: array forms (strings or objects) preserve input order. Object form is Go map iteration order (NOT insertion order — Go randomizes iteration to discourage relying on it). Callers that need stable order across the object form should preserve it upstream (e.g. by decoding the raw JSON bytes via json.Decoder.Token before this helper). Most current callers do not require stable order across the object form because each returned string disambiguates itself.

Error cases:

  • raw is neither an array nor an object: returns an error citing the actual Go type.
  • array element is neither a string nor an object: returns an error citing the index and Go type.
  • array element is an object but valueField is the empty string: returns an error citing the index (the caller disabled the object-element form).
  • array element is an object missing valueField, or has valueField bound to a non-string, or has valueField bound to an empty string: returns an error citing the index and (where applicable) the Go type.
  • object form contains an empty key: returns an error (an empty string is not a meaningful path / description / criterion / anything else this helper carries).

func OpenFindingIDSet

func OpenFindingIDSet(ids []string) map[string]bool

OpenFindingIDSet projects a finding-id slice onto the membership set the revise output validator (drafter.ParseReviseOutput) takes. Shared by the dispatcher's post-exit validation and the MCP-boundary validator context decode so both check against the same set.

func PlanReviewerSubmitFields

func PlanReviewerSubmitFields() []string

PlanReviewerSubmitFields returns the envelope field names the plan_reviewer role's submit_result validator (ParsePlanReviewerVerdict) always requires: `decision` (approve|reject). The clauderunner SubmitResultContracts registry sources the role's typed-schema `required` list from here so the schema's declared properties and the validator's required-field set share ONE source of truth (the DiscoverySubmitFields precedent).

func PlannerSubmitFields

func PlannerSubmitFields() []string

PlannerSubmitFields returns the envelope field names the planner role's typed submit_result schema guides the agent toward. The planner is MARKERLESS — ParsePlannerResult enforces no terminal marker; it requires only that the payload carry at least one of `proposal_id`, `issues`, or `amended_issues`. `issues` is the normal-path structural field the prompt instructs the agent to emit, so it is the single field the schema marks required. The clauderunner SubmitResultContracts registry sources the planner's typed-schema `required` list from here (and flags the contract Markerless) so the schema's declared properties and the validator's required-field set share ONE source of truth (the DiscoverySubmitFields precedent).

func PlannerSubmitSchema

func PlannerSubmitSchema() map[string]any

PlannerSubmitSchema is the JSON Schema for the planner's submit_result.

func RecognizerSubmitFields

func RecognizerSubmitFields() []string

RecognizerSubmitFields returns the envelope field names the recognizer role's submit_result validator (ParseRecognizerResult) always requires: the terminal `marker`. The clauderunner SubmitResultContracts registry sources the role's typed-schema `required` list from here so the schema's declared properties and the validator's required-field set share ONE source of truth (the DiscoverySubmitFields precedent).

func RenderDiscoveryMarkdown

func RenderDiscoveryMarkdown(r ImplementerDiscoveryResult) string

RenderDiscoveryMarkdown derives the human-readable discovery artifact from the typed result (SOR-2357 / spec R8): a `##`-headed markdown document with the six canonical sections in authoring order, each body verbatim under its heading. The typed fields are the authoritative source of truth; this rendered markdown is the projection the rest of the daemon threads as the implementer's `DISCOVERY:` block and seats on the issue's DiscoveryMarkdown column for operator display / telemetry — it is never parsed back as the source of truth.

func RenderReviewDiscoveryMarkdown

func RenderReviewDiscoveryMarkdown(r ReviewerDiscoveryResult) string

RenderReviewDiscoveryMarkdown derives the human-readable review-discovery artifact from the typed result (SOR-2360 / spec R8): a `##`-headed markdown document with the five canonical sections in authoring order, each body verbatim under its heading. The typed fields are the authoritative source of truth; this rendered markdown is the projection the dispatcher seats on the verdict and the supervisor persists onto the issue's ReviewDiscoveryMarkdown column — threaded as the judgment phase's REVIEW_DISCOVERY block and used for operator display / telemetry. It is never parsed back as the source of truth.

func ResolveDispatchContractIdentity

func ResolveDispatchContractIdentity(roleName string, ctxJSON []byte) (role, task string, ok bool)

ResolveDispatchContractIdentity returns the (role, task) identity of the post-exit dispatch contract resolved for a (roleName, ctxJSON) dispatch, plus ok=false when no contract covers the role. It is the exported probe the R9 property test compares against clauderunner's boundary-side ResolveContractIdentity: a drift in which contract the two registries select for the same (role, ctx) surfaces as an identity mismatch.

func ReviewerDetailField

func ReviewerDetailField(verifies []string) string

ReviewerDetailField returns the verdict-detail field name a reviewer dispatch requires given its spec requirement-ID (verifies) list — the dispatch-class discriminator. A non-empty verifies list is the spec-driven class (per_requirement_verdict); an empty / nil list is the legacy class (per_criterion). This is the single source of truth both the structured submit_result schema's `required` selection and ParseReviewerVerdict's detail-field branch read, so the schema and the validator agree on which detail field a given dispatch must carry.

func ReviewerDiscoverySubmitFields

func ReviewerDiscoverySubmitFields() []string

ReviewerDiscoverySubmitFields returns the five typed submit_result field names the reviewer_discovery dispatch requires, in canonical order. The clauderunner TypedSchemaDispatches registry sources its RequiredFields from here so the typed schema's declared `properties` and the validator's required-field set share ONE source of truth — the submit-result-schema- completeness invariant then holds for the reviewer-discovery entry by construction.

func ReviewerSubmitFields

func ReviewerSubmitFields() []string

ReviewerSubmitFields returns the envelope field names the reviewer role's submit_result validator (ParseReviewerVerdict) always requires UNCONDITIONALLY, in the order the parser enforces them: `decision` then `rationale`. The conditional verdict-detail field (per_criterion on a legacy dispatch, per_requirement_verdict on a spec-driven one) is NOT listed here — it is dispatch-class-dependent and single-sourced via ReviewerDetailField. The clauderunner SubmitResultContracts registry sources the role's typed-schema `required` list from here (plus the class detail field) so the schema's declared properties and the validator's required-field set share ONE source of truth (the DiscoverySubmitFields precedent).

func ReviewerSubmitSchema

func ReviewerSubmitSchema() map[string]any

ReviewerSubmitSchema is the JSON Schema for the reviewer's submit_result input.

func SpecReviewerSubmitFields

func SpecReviewerSubmitFields() []string

SpecReviewerSubmitFields returns the envelope field names the spec_reviewer role's submit_result validator (ParseSpecReviewerVerdict) always requires, in the order the parser enforces them: `marker` (the SPEC_REVIEW_OK terminal gate) then `coherent` (the load-bearing judgment bool). The clauderunner SubmitResultContracts registry sources the role's typed-schema `required` list from here so the schema's declared properties and the validator's required-field set share ONE source of truth (the DiscoverySubmitFields precedent).

func StewardSubmitFields

func StewardSubmitFields() []string

StewardSubmitFields returns the envelope field names the steward role's typed submit_result validator (ParseStewardResult) always requires: the terminal `marker` (STEWARD_OK). The clauderunner SubmitResultContracts registry sources the role's typed-schema `required` list from here so the schema's declared properties and the validator's required-field set share ONE source of truth (the TriagerSubmitFields / RecognizerSubmitFields precedent).

func SummarizeDiffStat

func SummarizeDiffStat(files []DiffStatLine, maxLines int) string

SummarizeDiffStat formats per-file diff stats as a multi-line digest suitable for ExistingIssue.DiffSummary.

Each emitted line has the shape `<status> <path> +<add>/-<del>`. The total number of lines is capped at maxLines; when len(files) > maxLines, the lowest-impact entries (smallest additions+deletions combined) are dropped first and a single trailing line `... +<M> more file(s)` accounts for them. The kept entries are emitted in impact-descending order so the planner sees the biggest surfaces first.

Empty input returns the empty string. maxLines <= 0 returns the empty string (callers should pass DiffSummaryMaxLines).

func SynthesizeDeferSignature

func SynthesizeDeferSignature(reason string, evidence []ActionEvidence) string

SynthesizeDeferSignature deterministically derives a non-empty pattern_signature for a defer action that omitted one (SOR-3080). It is the SOLE producer of a synthesized signature: validateAction calls it at parse time and writes the result into args, so every downstream reader observes one value. The derivation input is the action's own reason plus its evidence discriminators (EventID / IssueKey); the derived keys are sorted before hashing so two defer actions sharing the same (reason, evidence keys) — regardless of evidence ordering — synthesize an identical signature, and the value is stable across runs (no map iteration order, time, or randomness). The "synth:" prefix marks the signature as daemon-synthesized in the RECOGNIZER_DEFERRED audit event.

func SynthesizeEscalation

func SynthesizeEscalation(roleName string, parserError error, rawPayload string) (any, error)

SynthesizeEscalation builds a role-shaped escalation result the daemon can route through the role's normal verdict-handling path. Returns the typed result struct as `any` (the dispatcher type-asserts to its declared return type) plus a non-nil error iff no synthesizer is registered for roleName — the caller (the dispatcher) must fall back to the legacy crash path for an unsynthesized role rather than dropping the work silently.

parserError carries the final Parse*Result rejection text; it lands verbatim in the role's primary diagnostic field (ReviewerVerdict.Rationale, MergeResolverResult.FailureReason, etc.). rawPayload is the rejected payload's JSON-string snippet; informational only — the dispatcher embeds it in the audit-event message but does not surface it through the typed result struct.

New roles register their synthesizer by adding one map entry in defaultSynthesizerMap. No per-role wiring elsewhere on this surface.

func SynthesizedRoles

func SynthesizedRoles() []string

SynthesizedRoles returns the deterministic-order list of roles with a registered escalation synthesizer. Used by the daemon's role-contract probe + the synthesis dispatch-map unit test to assert every production role is wired.

func TriagerSubmitFields

func TriagerSubmitFields() []string

TriagerSubmitFields returns the envelope field names the triager role's submit_result validator (ParseTriagerResult) always requires: the terminal `marker`. The clauderunner SubmitResultContracts registry sources the role's typed-schema `required` list from here so the schema's declared properties and the validator's required-field set share ONE source of truth (the DiscoverySubmitFields precedent).

func TruncateForAudit

func TruncateForAudit(s string) string

TruncateForAudit returns s truncated to SynthesizedRawPayloadCap bytes on a UTF-8-safe boundary (rune-aligned). Exported so the daemon-side audit-event emitter and tests share one truncation policy.

func WedgeInvestigatorSubmitFields

func WedgeInvestigatorSubmitFields() []string

WedgeInvestigatorSubmitFields returns the envelope field names the wedge_investigator role's submit_result validator (ParseWedgeInvestigatorResult) always requires, in the order the parser enforces them: the terminal `marker` then the three load-bearing diagnosis fields `root_diagnosis`, `recovery_recommendation`, and `fileable_structural_root`. The clauderunner SubmitResultContracts registry sources the role's typed-schema `required` list from here so the schema's declared properties and the validator's required-field set share ONE source of truth (the DedupeSubmitFields precedent).

Types

type Action

type Action struct {
	Kind      string           `json:"kind"`
	Evidence  []ActionEvidence `json:"evidence"`
	Rationale string           `json:"rationale"`
	Args      map[string]any   `json:"args"`
}

Action is the parsed view of one row the recognizer emits in its output.json. Each action carries the kind (one of ActionKind*), ≥2 evidence entries grounding the pattern, a non-empty rationale the operator reads when auditing, and kind-specific args the daemon's Tick path dispatches through the matching mutation surface. Validation against the schema is done in ParseRecognizerResult; rows that fail validation are dropped + the reason surfaced via the RejectedReasons slice so the batch survives.

type ActionEvidence

type ActionEvidence struct {
	EventID  int64  `json:"event_id,omitempty"`
	IssueKey string `json:"issue_key,omitempty"`
	TS       int64  `json:"ts,omitempty"`
	Message  string `json:"message,omitempty"`
}

ActionEvidence is one citation entry on a recognizer Action. Either EventID or IssueKey must be non-zero; both may be set when the LLM links a session event to its owning issue.

EventID and IssueKey are the only fields the LLM emits — they are the discriminators the daemon needs. TS and Message are daemon- derived, NOT part of the emit contract (SOR-1151): validateAction hard-rejects any raw evidence row that includes `ts` or `message`, and enrichEvidence populates TS + Message from the daemon's per-tick event map keyed by EventID. An issue-key-only evidence row (no event_id) has no event to enrich from, so its TS stays 0 and its Message stays "" — downstream consumers handle the empty case.

type AmendTarget

type AmendTarget struct {
	Key         string
	CurrentBody string
}

AmendTarget is one issue the scoped AC-amend cycle (SOR-214) asks the planner to re-emit a corrected body for. CurrentBody is the body in place at dispatch time; the planner edits the acceptance criteria in place and returns the full corrected body. Set only on TriggerPlanDefectScopedAmend tasks.

type AmendedIssue

type AmendedIssue struct {
	Key             string `json:"key"`
	NewBodyMarkdown string `json:"new_body_markdown"`
}

AmendedIssue is one issue's re-emitted corrected body produced by a scoped AC-amend planner cycle (SOR-214). The daemon applies NewBodyMarkdown to Key via the audited amend capability (SOR-208); it never creates, cancels, or re-decomposes issues from this shape.

type AssemblyFixerAction

type AssemblyFixerAction struct {
	Kind      string `json:"kind"`
	Rationale string `json:"rationale"`

	// file_implementation_issue args.
	Title        string `json:"title,omitempty"`
	BodyMarkdown string `json:"body_markdown,omitempty"`
	Repo         string `json:"repo,omitempty"`
	Priority     int    `json:"priority,omitempty"`
	// DependsOn is the optional list of sibling-plan-child keys this
	// fix-up must wait for before dispatch. It MUST NOT include the
	// parked child this fix-up was filed to unblock (its triggering
	// parked child): that child cannot pass the cumulative gate until
	// the fix-up ships, so the fix-up must be schedulable BEFORE it, not
	// after it. A reverse edge deadlocks the plan — the daemon can't
	// dispatch the fix-up until the parked child merges, and the parked
	// child can't merge until the fix-up lands. The defense-in-depth
	// primary guard is the apply-path strip in plan_assembly_fixer.go;
	// this constraint is also rendered into the agent-visible task via
	// FormatAssemblyFixerTask's FIXUP_RULE line.
	DependsOn []string `json:"depends_on,omitempty"`

	// refer_back_child args.
	ChildKey string                 `json:"child_key,omitempty"`
	Concerns []AssemblyFixerConcern `json:"concerns,omitempty"`

	// escalate_capability_gap args.
	SorcererIssueTitle        string `json:"sorcerer_issue_title,omitempty"`
	SorcererIssueBodyMarkdown string `json:"sorcerer_issue_body_markdown,omitempty"`

	// retry_gate_transient_flake args.
	FlakyTestNames []string `json:"flaky_test_names,omitempty"`
	UnownedReason  string   `json:"unowned_reason,omitempty"`

	// file_main_side_fix args. The kind shares Title / BodyMarkdown / Repo
	// with file_implementation_issue (no new struct fields for those);
	// FailingTestNames + PreexistingReason carry the pre-existing-defect
	// evidence (the failing test name(s) plus why no parked child owns them).
	FailingTestNames  []string `json:"failing_test_names,omitempty"`
	PreexistingReason string   `json:"preexisting_reason,omitempty"`
}

AssemblyFixerAction is the parsed view of one row the assembly_fixer emits in its actions array. Kind is one of AssemblyFixerActionKind*; Args carries the kind-specific payload (Title / BodyMarkdown / Repo / Priority / DependsOn for file_implementation_issue, ChildKey / Concerns for refer_back_child, SorcererIssueTitle / SorcererIssueBodyMarkdown for escalate_capability_gap, FlakyTestNames / UnownedReason for retry_gate_transient_flake, Title / BodyMarkdown / Repo / FailingTestNames / PreexistingReason for file_main_side_fix).

The struct flattens the per-kind args into one set of fields rather than carrying a separate map[string]any. Validation rejects any action whose required-for-kind fields are missing or empty after TrimSpace; unknown kinds are also rejected.

type AssemblyFixerConcern

type AssemblyFixerConcern struct {
	Severity string `json:"severity"`
	Comment  string `json:"comment"`
	File     string `json:"file,omitempty"`
	Line     int    `json:"line,omitempty"`
}

AssemblyFixerConcern is one entry in a refer_back_child action's concerns list. Mirrors the existing reviewer's per-concern shape so applyReviewerVerdict's per-concern path can absorb the row without reshaping.

type AssemblyFixerParkedChild

type AssemblyFixerParkedChild struct {
	Key       string   `json:"key"`
	Title     string   `json:"title"`
	Branch    string   `json:"branch"`
	Repos     []string `json:"repos"`
	DependsOn []string `json:"depends_on"`
}

AssemblyFixerParkedChild is one child's envelope-level summary the agent reads to decide whether a refer_back_child action is viable.

type AssemblyFixerParseError

type AssemblyFixerParseError struct {
	Err        error
	RawPayload json.RawMessage
}

AssemblyFixerParseError wraps the parser's rejection error with the raw JSON payload the agent submitted. The dispatcher emits this typed error on the validator-reject path so the daemon's audit-event surface can record the offending JSON shape for post-hoc diagnosis without re-instrumenting the role. RawPayload is the bytes of the rejected payload AS-RECEIVED (a re-marshal of the decoded map); it is empty on transport-only errors that produced no payload to inspect.

applyAssemblyFixerResult unwraps via errors.As before emitting the secondary assembly_fixer_payload_rejected audit event.

func (*AssemblyFixerParseError) Error

func (e *AssemblyFixerParseError) Error() string

func (*AssemblyFixerParseError) Unwrap

func (e *AssemblyFixerParseError) Unwrap() error

type AssemblyFixerResult

type AssemblyFixerResult struct {
	Status        AssemblyFixerStatus   `json:"-"`
	FailureReason string                `json:"failure_reason,omitempty"`
	Actions       []AssemblyFixerAction `json:"actions,omitempty"`
	Coercions     []string              `json:"-"`
}

AssemblyFixerResult is the typed view of one assembly_fixer submit envelope. Status drives the supervisor branch (ok ⇒ apply each action; failed ⇒ emit assembly_fixer_failed event and route the plan's outer-retry counter).

Coercions is a transient (non-persisted) parse-time signal mirroring MergeResolverResult.Coercions: each entry names one liberal-parser coercion ParseAssemblyFixerResult applied to the raw payload (e.g. `field=actions from_shape=object to_shape=[]object`). The daemon-side closure reads it after the dispatcher returns and emits one `role_input_coerced` audit event per entry. Always nil on disk (json:"-") and nil when the input was already the canonical shape.

func ParseAssemblyFixerResult

func ParseAssemblyFixerResult(payload map[string]any, vctx AssemblyFixerValidatorContext) (AssemblyFixerResult, error)

ParseAssemblyFixerResult decodes the payload the runner returns from an assembly_fixer subprocess. Validates each `actions[i]` row independently against the schema AND the supplied validator context (vctx); rows that violate schema or vctx are rejected wholesale because the supervisor cannot apply a partially-valid action.

The parser is LIBERAL in what it accepts for several call-site misshapes (consistent with the SOR-1411 / SOR-1429 cross-role pattern):

  • `actions` accepts a bare object as well as an array (coerced to a one-element array, descriptor: `field=actions from_shape=object to_shape=[]object`).
  • per-action `depends_on` accepts a bare string OR an array (normalized via NormalizeStringList; descriptor: `field=actions[i].depends_on from_shape=<X> to_shape=[]string`).
  • per-action `concerns` accepts a bare object OR an array (coerced; descriptor: `field=actions[i].concerns from_shape= object to_shape=[]object`).

On any required-field failure the parser returns an error the dispatcher surfaces back to the supervisor; the supervisor's SubmitResult validator-context boundary then folds the error to a tool_result so the agent revises in-conversation (SOR-1378 boundary), with the standard N=2 in-conversation retry budget.

type AssemblyFixerStatus

type AssemblyFixerStatus int

AssemblyFixerStatus is the parsed terminal status. ok = the agent produced ≥1 action; failed = the input was malformed and the agent could not diagnose.

const (
	AssemblyFixerStatusUnknown AssemblyFixerStatus = iota
	AssemblyFixerStatusOK
	AssemblyFixerStatusFailed
)

type AssemblyFixerTask

type AssemblyFixerTask struct {
	// PlanKey is the planning issue whose cumulative dry-run gate
	// failed (e.g. "SOR-350").
	PlanKey string
	// PlanBody is the planning issue's description body. Read by the
	// agent to align fix-up actions with the original plan goal.
	PlanBody string
	// Repo is the failing repo (assembly short-circuits on first
	// failure; exactly one repo per dispatch).
	Repo string
	// ArtifactPath is the absolute path to the captured pre-push gate
	// output (`<projectRoot>/.sorcerer/plan-assemble/<plan-key>/
	// cargo-output-<unix-ts>.log`). The agent reads it with read_file.
	ArtifactPath string
	// ArtifactSummary is the daemon-computed first-FAIL-line +
	// N-line-context (or last-200-lines tail) of the artifact. A
	// pre-digest the agent can read before opening the full artifact.
	ArtifactSummary string
	// ResolverDiagnosis is the formatted merge-resolver verdict for a
	// conflict-triggered dispatch (a merge_resolver gave up on this plan
	// branch). The daemon loads it from the durable
	// merge_resolver_diagnostics row the capture seam wrote and renders it
	// into the agent's prompt as the primary architecture inventory for
	// corrective-child scoping + refer-back classification — the conflict
	// signature names the files, the verdict prose explains which side
	// introduced what. Empty on gate-only failures (no resolver ran), where
	// FormatAssemblyFixerTask omits the RESOLVER_DIAGNOSIS block entirely.
	ResolverDiagnosis string
	// ParkedChildren is the cohort that squashed onto the plan branch
	// in dependency order. Each entry is one possible refer_back_child
	// target.
	ParkedChildren []AssemblyFixerParkedChild
	// Cycle is the 1-indexed outer cycle (counts up on assembly_fixer
	// re-dispatch after a previous cycle's fixes still left the gate
	// failing). The agent uses it to avoid repeating a strategy that
	// already failed.
	Cycle int
	// MMax is the outer-retry budget (default 2). Surfaced so the
	// agent knows when synthetic escalation will fire on exhaustion.
	MMax int
	// RetryPreamble is the SOR-227 narration-retry notice the daemon
	// constructs when this dispatch follows a narration-class failure.
	// The dispatcher copies it onto RunOptions.RetryPreamble; the
	// runner prepends it to the per-call prompt body. Empty on every
	// dispatch that follows a non-narration outcome.
	RetryPreamble string
}

AssemblyFixerTask is the typed view of one per-dispatch message handed to the assembly_fixer conversation. The dispatcher renders it via FormatAssemblyFixerTask before handing the string to the runner.

type AssemblyFixerValidatorContext

type AssemblyFixerValidatorContext struct {
	// ParkedChildren is the set of parked-child keys a
	// refer_back_child action may cite. A `child_key` outside this set
	// is rejected with "unknown child key" so the action does not fire
	// against an unrelated issue.
	ParkedChildren map[string]bool
	// AllowedRepos is the set of repo slugs the failing assembly
	// touched. A file_implementation_issue's `repo` arg outside this
	// set is rejected so the action does not file a child against a
	// repo the plan does not own. Empty disables the check (tests
	// that don't seed the map).
	AllowedRepos map[string]bool
}

AssemblyFixerValidatorContext is the daemon-supplied side-channel the validator reads to enforce per-action acceptability rules that depend on runtime state (the parked-child set + the per-plan repo allowlist). FormatAssemblyFixerTask never emits these fields onto the wire — the agent doesn't see them — but the dispatcher threads them into ParseAssemblyFixerResult on the way back.

func DecodeAssemblyFixerValidatorCtx

func DecodeAssemblyFixerValidatorCtx(ctxJSON []byte) AssemblyFixerValidatorContext

DecodeAssemblyFixerValidatorCtx decodes the assembly_fixer validator context from the wire bytes. An empty or malformed context yields the zero value (an empty AllowedRepos disables the repo-allowlist check, matching the prior behavior).

type BenchmarkTargetObligation

type BenchmarkTargetObligation struct {
	RID             string
	BenchmarkPath   string
	BenchmarkSymbol string
	BoundText       string
}

BenchmarkTargetObligation is one entry in the required-benchmark-targets set for a spec-driven implementation issue that verifies a benchmark-mode requirement. It is the single ground-truth shape (spec SPEC-SOR-3095-v1 R4) the downstream sites — the implementing-dispatch instruction, the predicted footprint, and the dependent child's absent-benchmark authoring refer-back — read so they cannot disagree about which benchmark targets an issue owes. It is the direct benchmark analog of PropTestHelperObligation.

  • RID is the requirement ID (e.g. "R4").
  • BenchmarkPath is the worktree-relative path for the implementer-authored per-requirement benchmark file (<specDir>/bench/r<N>_bench_test.go), derived in RequiredBenchmarkTargets from the spec's stub directory.
  • BenchmarkSymbol is the benchmark function name (e.g. "BenchmarkR4"), derived from the language profile's benchmark HelperSymbolPattern with "{R}" substituted by the sanitized R-ID — so no language assumption is hard-coded in package daemon.
  • BoundText is the requirement's declared performance bound, surfaced from its EARS statement so the implementer (and the dependent child's absent-benchmark refer-back) can name the threshold the authored benchmark must assert against.

The type lives in internal/role (not internal/daemon) so a dispatch task can carry it without pulling daemon dependencies into the role package; it is produced by RequiredBenchmarkTargets in internal/daemon/benchmark_required_targets.go.

type CheckRollup

type CheckRollup struct {
	Name   string `json:"name"`
	Status string `json:"status"` // success | failure | pending | etc.
}

CheckRollup is the per-PR CI check status reported into a review task.

type ChildSquashRecord

type ChildSquashRecord struct {
	Key   string
	Title string
	SHA   string
}

ChildSquashRecord is one entry in the prior-squashed-siblings list the merge_resolver envelope carries: the children whose squashes already landed on the plan branch ahead of this child, in dependency order. The resolver reads them to disambiguate which "HEAD" content came from which sibling when picking a registry-style resolution.

type ConversationRunner

type ConversationRunner interface {
	// Run sends one task message to the named role and returns the
	// parsed final-payload (typically the submit_result body). opts
	// carries per-call info (worktree dirs to expose, JSON schema for
	// the agent's reply, etc.).
	//
	// Returning a nil payload with a nil error is illegal — callers
	// treat that as a malformed-result failure.
	Run(ctx context.Context, role string, taskMessage string, opts RunOptions) (map[string]any, error)
}

ConversationRunner is the abstraction the role-dispatcher factories use to talk to long-lived agent conversations without importing the concrete backing implementation. The daemon's wiring picks the implementation: agentcli.Runner (subprocess `claude -p`, the only path that works against Pro/Max OAuth tokens) in production, fakes in unit tests.

One ConversationRunner per project. The runner owns per-role session state (UUIDs for --resume, etc.).

type CoverageSummary

type CoverageSummary struct {
	CoveredInvariants          []string
	UncoveredSensitiveSurfaces []string
}

CoverageSummary is the optional invariant-coverage telemetry threaded onto the reviewer task envelope (the invariant-coverage pipeline's PR-review gate). The daemon loads it from the issue's newest issue_body_features event at dispatch time and packs it onto ReviewerTask.CoverageSummary only when at least one slice is non-empty; both empty (docs-only / test-only / non-sensitive change) and a legacy issue with no telemetry row leave the pointer nil, and FormatReviewerTask omits the COVERAGE_SUMMARY: block entirely. The reviewer interprets absence as "coverage check not applicable."

CoveredInvariants carries invariant NAMES (not surface globs — the event payload persists names only; each name maps to internal/invariants/<name>_test.go, which is what the coverage check reads). UncoveredSensitiveSurfaces carries repo-relative paths the create-time analyzer flagged as sensitive-but-uncovered. Both arrive already de-duplicated and sorted at emit time, so rendering in slice order is deterministic.

type CriterionCheck

type CriterionCheck struct {
	Criterion string `json:"criterion"`
	Status    string `json:"status"` // verified | not_verified | not_applicable
	Evidence  string `json:"evidence,omitempty"`
}

CriterionCheck is one entry in the verdict's per_criterion list.

type CycleMetrics

type CycleMetrics struct {
	// OutputTokens is the result frame's usage.output_tokens.
	OutputTokens int `json:"output_tokens,omitempty"`
	// InputTokens is the result frame's usage.input_tokens.
	InputTokens int `json:"input_tokens,omitempty"`
	// CacheCreationTokens is usage.cache_creation_input_tokens.
	CacheCreationTokens int `json:"cache_creation_tokens,omitempty"`
	// CacheReadTokens is usage.cache_read_input_tokens.
	CacheReadTokens int `json:"cache_read_tokens,omitempty"`
	// TotalCostUSD is the result frame's total_cost_usd.
	TotalCostUSD float64 `json:"total_cost_usd,omitempty"`
	// NumTurns is the result frame's num_turns.
	NumTurns int `json:"num_turns,omitempty"`
	// DurationAPIMs is the result frame's duration_api_ms.
	DurationAPIMs int `json:"duration_api_ms,omitempty"`
}

CycleMetrics is the per-cycle resource-usage view lifted from the claude subprocess's stream-json `result` frame (the final frame claude emits carrying the session's aggregate usage / cost / timing). agentcli.parseTerminal populates it on the implementer's non- failure marker paths; the daemon's implementer_cycle_summary event records it so the feedback-loop analysis can weigh per-cycle cache fill and token cost against the terminal outcome. Every field is zero when the result frame was absent (crash before emission).

type DedupeDuplicate

type DedupeDuplicate struct {
	Key              string `json:"key"`
	OverlapParagraph string `json:"overlap_paragraph"`
}

DedupeDuplicate is one entry in the verdict's duplicates array. Key MUST appear in the inflight digest the daemon supplied; the parser rejects rows that don't validate, so callers don't see synthesized keys.

type DedupeStatus

type DedupeStatus int

DedupeStatus is the parsed terminal marker tag. Callers branch on this to decide whether the LLM call itself succeeded; the verdict's is_duplicate field decides whether the create is refused.

const (
	DedupeStatusUnknown DedupeStatus = iota
	DedupeStatusOK
	DedupeStatusFailed
)

type DedupeTask

type DedupeTask struct {
	ProspectiveTitle string
	PayloadJSON      string
}

DedupeTask is the typed view of one per-task message handed to the dedupe conversation. PayloadJSON is the wrapped {prospective, inflight} JSON object the agent parses out of the indented PAYLOAD: block; ProspectiveTitle is also surfaced as its own KEY line so the agent's prompt cache stays stable across runs against the same prospective issue.

type DedupeVerdict

type DedupeVerdict struct {
	Status      DedupeStatus
	Detail      string
	SessionID   string
	IsDuplicate bool
	Duplicates  []DedupeDuplicate
}

DedupeVerdict is the typed view of the dedupe role's verdict.json. IsDuplicate is true iff Duplicates is non-empty; ParseDedupeVerdict rejects malformed shapes where the two fields disagree. SessionID is the clauderunner-minted subprocess id (threaded through payload["session_id"] by parseTerminal) so the daemon's audit event can point back at the source subprocess.

func ParseDedupeVerdict

func ParseDedupeVerdict(payload map[string]any) (DedupeVerdict, error)

ParseDedupeVerdict decodes the payload the runner returns from a dedupe subprocess. The marker tag is read from payload["marker"]; a missing or non-DEDUPE_* marker is a hard failure. On OK the runner attaches verdict.json's parsed JSON to the same map under the canonical keys ("is_duplicate", "duplicates").

Validation rules. Structural violations reject the verdict as a whole; the two field-level overlap_paragraph cases are normalized in place rather than rejected. See docs/agents.md § "Recoverable vs structural validator violations".

Structural (return an error):

  • is_duplicate missing or not a bool.
  • duplicates not an array.
  • duplicates[i] not an object.
  • duplicates[i].key empty or missing.
  • a non-DEDUPE_* marker.

Recoverable (normalized, no error):

  • duplicates[i].overlap_paragraph longer than 500 chars is truncated to its first 500 chars.
  • duplicates[i].overlap_paragraph empty after strings.TrimSpace drops that entry. The is_duplicate iff len(duplicates) > 0 invariant is enforced AFTER the drop, so a verdict whose only evidence row was dropped normalizes to a clean IsDuplicate=false verdict rather than erroring.

On a structural failure the verdict is rejected as a whole — a partial accept would let the gate refuse with malformed data. The caller treats a parse error as a soft failure and proceeds with the create per the no-fallbacks discipline (the audit event still fires).

type DescopedAC

type DescopedAC struct {
	ACRef     int    `json:"ac_ref,omitempty"`
	Rationale string `json:"rationale"`
	FollowUp  string `json:"follow_up"`
}

DescopedAC is one entry in the planner's proposal-level explicit-descope list. It declares a parent planning-issue acceptance criterion the plan deliberately defers. ACRef is a 1-based positional index into the parent's ExtractAcceptanceCriteria ordering (0 = unspecified). Rationale and FollowUp are required non-empty; ParsePlannerResult returns a clear per-field parse error when either is missing. The field is inert until a later wiring change consumes it in the validator's AC-coverage gate.

type DiffStatLine

type DiffStatLine struct {
	Status    string
	Path      string
	Additions int
	Deletions int
}

DiffStatLine is one file's entry in a PR's diff-stat output, used by the supervisor to build a planner-task ExistingIssue.DiffSummary. The status indicator follows GitHub's pulls/<n>/files API ("added" → "A", "modified" → "M", "removed" → "D", "renamed" → "R", "copied" → "C"); the supervisor maps from the API verbatim, and an empty Status defaults to "M" at format time.

type DiscoveryLegacyMarkerError

type DiscoveryLegacyMarkerError struct {
	// MarkerSummary is the one-line suffix the agent emitted after the
	// IMPLEMENT_PLAN_DEFECT marker (the free-text observation that was never
	// a structured plan_defect body). Carried through so the handler can put
	// the agent's actual concern on the audit surface.
	MarkerSummary string
}

DiscoveryLegacyMarkerError is the typed error ParseImplementerResult returns when an agent emits the legacy IMPLEMENT_PLAN_DEFECT marker without the structured body (the `Defect type:` / `Offending ACs:` headers) the post-discovery plan_defect submission machinery requires. The read-only discovery phase was never asked to compose that body, so a discovery LLM that reaches for IMPLEMENT_PLAN_DEFECT (e.g. from cached context predating the IMPLEMENT_DISCOVERY_BLOCKED rename) trips the `Defect type:` validation. Returning a TYPED error — rather than the raw "missing Defect type: header" validator error — lets the daemon's discovering-state session-error handler detect this class via errors.As and route it to blocked_user with a hint pointing at the new IMPLEMENT_DISCOVERY_BLOCKED marker, emitting a discovery_legacy_marker_ rejected event, instead of surfacing a cryptic session crash.

func (*DiscoveryLegacyMarkerError) Error

type Disposition

type Disposition string

Disposition is the spec-domain (SPEC-SOR-3081-v1) handling each FailureCause routes to. cohort_remediation engages the cohort (refer_back_child / file_implementation_issue / escalate_capability_gap); retry_transient is the bounded gate re-run (retry_gate_transient_flake); durable_main_side_fix files a durable fix against the pre-existing test's owning repo main line (file_main_side_fix) without exhausting the flake-retry budget.

const (
	// DispositionCohortRemediation engages the cohort.
	DispositionCohortRemediation Disposition = "cohort_remediation"
	// DispositionRetryTransient is the bounded gate-retry disposition.
	DispositionRetryTransient Disposition = "retry_transient"
	// DispositionDurableMainSideFix files a durable main-side fix.
	DispositionDurableMainSideFix Disposition = "durable_main_side_fix"
)

type DroppedFabricatedAction

type DroppedFabricatedAction struct {
	Kind            string
	Signature       string
	Rationale       string
	MissingEventIDs []int64
}

DroppedFabricatedAction is one action the pre-emit gate filtered out because at least one of its evidence rows cited an event_id not present in the input payload. The daemon's Tick path emits a recognizer_dropped_fabricated_evidence event per entry so a follow-up "how often is this firing" SELECT is one query away.

Signature is a short human-readable label (kind + the action's per-kind identity arg, e.g. "cancel_issue SOR-200") suitable for the event_subject column. Rationale carries the LLM's reasoning so the audit trail preserves it. MissingEventIDs is the closed set of fabricated event_ids cited on this action's evidence rows.

type EnrichmentError

type EnrichmentError struct {
	EventID int64
}

EnrichmentError is one evidence event_id the enrichment step could not resolve to a (ts, message) record despite the id being a real, non-fabricated event in the daemon's per-tick EventDaemons map.

type EventRecord

type EventRecord struct {
	TS      int64
	Message string
}

EventRecord is the daemon-canonical (ts, message) pair for one event row, keyed by event_id in RecognizerValidatorContext.EventRecords. The validator's enrichment step copies these onto each evidence row the recognizer cites, so the LLM never reconstructs (or hallucinates) data the daemon's events table already holds.

type ExistingIssue

type ExistingIssue struct {
	Key       string
	Title     string
	StateType string   // backlog | unstarted | started | completed | canceled
	DependsOn []string // already-declared deps from the body
	// PRURL is the URL of the first OPEN PR for this issue, if any. Empty
	// when the issue has no open PR (the common case). Populated by the
	// supervisor from d.latestPRSnapshots.
	PRURL string
	// DiffSummary is a one-line-per-file digest of the PR's diff (as
	// emitted by `gh pr diff --stat`), capped at 40 lines per issue.
	// Empty when no open PR or when the diff query failed. The shape of
	// each line is `<status> <path> +<add>/-<del>`; when truncation
	// drops files, the final line is `... +<M> more file(s)`.
	DiffSummary string
}

ExistingIssue is the daemon's compact view of one issue when handed to the planner. Includes only what the planner needs to make decisions: key, title, current state, declared deps, and (when an open PR exists) a digest of the in-flight diff so the planner can spot scope overlap.

type FailureCause

type FailureCause string

FailureCause is the spec-domain (SPEC-SOR-3081-v1) classification of a gate failure the assembly_fixer diagnoses. The three causes partition every classifiable gate failure: failing code in a cohort-touched file (cohort_attributable), one persistently-failing test in a cohort-untouched file owned by no parked child (preexisting_unowned_defect), and different tests failing across re-runs under load with no cohort owner (transient_unowned_flake).

const (
	// FailureCauseCohortAttributable: the failing test or code lies in a
	// file touched by some in-flight cohort child's diff.
	FailureCauseCohortAttributable FailureCause = "cohort_attributable"
	// FailureCauseTransientUnownedFlake: different tests fail across the
	// gate re-runs (load/scheduling noise), no cohort child owns them.
	FailureCauseTransientUnownedFlake FailureCause = "transient_unowned_flake"
	// FailureCausePreexistingUnownedDefect: one persistently-failing test
	// in a cohort-untouched file, owned by no parked child.
	FailureCausePreexistingUnownedDefect FailureCause = "preexisting_unowned_defect"
)

type Finding

type Finding struct {
	PatternSignature  string   `json:"pattern_signature"`
	ProseDescription  string   `json:"prose_description"`
	EvidenceEventIDs  []int64  `json:"evidence_event_ids"`
	EvidenceIssueKeys []string `json:"evidence_issue_keys"`
	SuggestedAction   string   `json:"suggested_action"`
	Confidence        string   `json:"confidence"`
	ParseError        string   `json:"-"`
}

Finding is the parsed view of one row the recognizer emits in the `findings` array of its output.json (SOR-1180). Findings are prose pattern observations the LLM records on every tick; they exist ALONGSIDE — not instead of — the strictly-validated `actions`.

The findings parse deliberately DEVIATES from the actions path's strict per-row validation (validateAction): a finding row is never rejected and never dropped. A row missing fields takes zero-value defaults; a row that does not decode to a JSON object is preserved with a non-empty ParseError (and its raw text in ProseDescription) so the observation the LLM could not fit to the schema still reaches the operator. Rationale: actions mutate the world and keep strict validation; findings only inform the operator, and a finding the LLM could not shape to the schema is exactly the observation the operator most wants to see.

ParseError is daemon-set, never emitted by the LLM: it is non-empty only when the row could not be decoded to the expected shape.

type ImplementerDiagnosis

type ImplementerDiagnosis struct {
	// Kind names the operator-side defect the implementer identified
	// (one of the validDiagnosisKinds constants). Always non-empty in
	// a populated diagnosis — the parser normalizes unrecognized inputs
	// to DiagnosisKindUnknown rather than rejecting them, so the audit
	// surface still fires.
	Kind string `json:"kind"`

	// Evidence is the implementer's concrete justification: file paths
	// that don't exist on main, grep hits that returned nothing, failing
	// build lines, etc. Order is preserved verbatim. Empty list is
	// allowed (the audit message still emits with `"evidence":[]`) but
	// strongly discouraged — the prompt instructs the agent to populate it.
	Evidence []string `json:"evidence,omitempty"`

	// SuggestedAction is the implementer's recommendation for what the
	// operator should do (one of the validDiagnosisActions constants).
	// Always non-empty in a populated diagnosis (parser normalizes
	// unrecognized inputs to DiagnosisActionUnknown).
	SuggestedAction string `json:"suggested_action"`
}

ImplementerDiagnosis is the typed view of the OPTIONAL structured diagnosis the implementer may pair with any non-failure terminal marker (SOR-1240). The agent writes a YAML file to `<state_dir>/implementer_diagnosis.yaml`; the clauderunner reads it into raw["diagnosis"] for ParseImplementerResult to project here.

Diagnosis is supplementary metadata, not a replacement for the marker's primary payload. The supervisor emits `implementer_diagnosed_<kind>` as a separate audit event after the existing session-result handling so the operator-facing surface (audit grep + sorcerer-ui SidePanel callout) carries the diagnosis directly.

type ImplementerDiscoveryResult

type ImplementerDiscoveryResult struct {
	// ScopeSummary states what the issue changes and why (one paragraph).
	ScopeSummary string
	// FilesToTouch lists the file paths the implementer will change/create.
	FilesToTouch string
	// ExistingDependencies lists cross-references to existing symbols / files
	// / packages the change builds on or must stay consistent with.
	ExistingDependencies string
	// TestSurface lists the test files to extend and any new tests.
	TestSurface string
	// Approach is the prose design: the shape of the change, the order to make
	// it in, and the conventions to follow.
	Approach string
	// EdgeCasesRisks lists edge cases, failure modes, and risks.
	EdgeCasesRisks string

	// OneLineSummary is the one-line suffix of the agent's
	// IMPLEMENT_DISCOVERY_OK terminal marker — the discovery's headline.
	OneLineSummary string

	// PredictedFootprint is the discovery agent's structured prediction of
	// the files this issue will touch/create, parsed from the OPTIONAL
	// `touched_files` / `created_files` STRUCTURED arrays the agent submits
	// directly on the typed submit_result payload (spec R6) — a field of the
	// typed result, not a parse of the `## Files to touch` markdown section and
	// not a read of a side predicted_footprint.json file. Non-nil only when the
	// agent declared at least one touched/created entry; the supervisor seats it
	// on the issue's PredictedFootprint (Exploration cleared) atomically with the
	// discovering → implementing transition, so an `--exploration` issue enters
	// implementing with a known footprint instead of waiting for the in_review
	// backfill. Nil when the agent produced no footprint (both arrays absent /
	// empty), which leaves the issue's existing planner-declared or
	// exploration-sentinel footprint untouched.
	PredictedFootprint *footprint.PredictedFootprint
}

ImplementerDiscoveryResult is the typed result of one implementer-discovery dispatch — the read-only phase that maps the implementation surface for an issue into a structured plan the downstream implementer consumes via its `DISCOVERY:` envelope block.

SOR-2357: the discovery agent submits the six section fields as a TYPED submit_result payload (one field per section), validated at the submit boundary. The human-readable discovery artifact is DERIVED from these typed fields via RenderDiscoveryMarkdown — the typed fields are the source of truth, the markdown is the projection.

func ParseImplementerDiscoveryResult

func ParseImplementerDiscoveryResult(payload map[string]any) (ImplementerDiscoveryResult, error)

ParseImplementerDiscoveryResult validates the discovery agent's typed submit_result payload and returns a typed result.

Inputs (SOR-2357):

  • The six section fields — `scope_summary`, `files_to_touch`, `existing_dependencies`, `test_surface`, `approach`, `edge_cases_risks` — each a markdown string the agent submits via submit_result.
  • `marker_summary` / `detail` / `summary` — the verbatim one-line suffix of the terminal marker, surfaced under different keys depending on whether the parser is handed the raw parseTerminal payload, the merged marker payload, or the submitted envelope. We read them in that order so OneLineSummary is populated on every path.
  • `touched_files` / `created_files` — the OPTIONAL structured predicted footprint arrays the agent submits directly on the typed payload (spec R6); each element is a {path, anchors} object or a bare path string.

Validation: every one of the six required section fields must be present and non-empty (after trimming). A payload missing any of them is rejected with a typed *MissingDiscoveryFieldsError so the submit boundary can re-prompt the agent with the exact missing field names.

type ImplementerResult

type ImplementerResult struct {
	// Success path: PRs opened, branch on remote. The branch name and the
	// resolved PR URLs are NOT part of the LLM emit contract — both are
	// daemon-derived (SOR-1148). The branch is dispatched WITH the task
	// (FormatImplementerTask's BRANCH: header, so iss.Branch is
	// authoritative); the PR URLs are discovered after IMPLEMENT_OK via
	// FindPRByBranch (one lookup per repo against iss.Branch). The only
	// load-bearing semantic field on the success path is Summary.
	Summary string `json:"summary,omitempty"`

	// Status field (autonomous-action path):
	//   "noop"                         — work isn't needed; supervisor abandons issue.
	//   "discovered-prereq-new"        — free-text prereq; supervisor spawns planner.
	//   "discovered-prereq-existing"   — cited existing key; supervisor links the dep directly.
	// Details is the human-readable explanation. All paths are
	// non-failure terminals.
	Status  string `json:"status,omitempty"`
	Details string `json:"details,omitempty"`

	// PrereqKind is required when Status == StatusDiscoveredPrereqNew.
	// One of PrereqBlocking / PrereqFollowup. Unused for the
	// existing-key shape.
	PrereqKind string `json:"prereq_kind,omitempty"`

	// ExistingKey is required when Status == StatusDiscoveredPrereqExisting.
	// Carries the cited Linear issue key (e.g. "SOR-856") that the
	// daemon will append to origin's DependsOn instead of filing a
	// Planning Issue.
	ExistingKey string `json:"existing_key,omitempty"`

	// Plan-defect fields (SOR-209). Populated when Status == StatusPlanDefect.
	//
	//   - DefectType  — one of the validDefectTypes constants.
	//   - OffendingACs — verbatim text of the AC bullets the defect
	//     attaches to (1-based / unkeyed; the supervisor surfaces them
	//     in the Linear comment so operators can map back to the issue
	//     description without grepping).
	//   - Evidence    — concrete justification: file:line citations,
	//     command output, links to sibling-issue gates that prove the
	//     duplication, etc.
	//   - SuggestedCorrection — the implementer's proposal for how the
	//     issue should be re-decomposed (split, descope, re-direct,
	//     cancel). The downstream plan-defect dispatch seam may consume
	//     this on later issues in the plan; the seam in this issue
	//     records it verbatim and falls through to the existing
	//     operator-escalation path.
	DefectType          string   `json:"defect_type,omitempty"`
	OffendingACs        []string `json:"offending_acs,omitempty"`
	Evidence            string   `json:"evidence,omitempty"`
	SuggestedCorrection string   `json:"suggested_correction,omitempty"`

	// Spec-defect fields (spec-driven phase D). Populated when
	// Status == StatusSpecDefect.
	//
	//   - SpecDefectRIDs — the cited spec requirement IDs the defect
	//     attaches to (e.g. ["R-1", "R-2"]), parsed from the
	//     IMPLEMENT_SPEC_DEFECT one-line summary's `R-1, R-2: <prose>`
	//     prefix or the structured `spec_defect_rids` field. The downstream
	//     amendment-dispatch handler routes the spec_drafter to a
	//     narrowly-scoped amendment of exactly these requirements.
	//   - SpecDefectProse — the prose description of why the cited
	//     requirement(s) are wrong (the summary's remainder, or the
	//     structured `spec_defect_prose` field). Distinct from Details,
	//     which carries the full body of any paired spec_defect.md file.
	SpecDefectRIDs  []string `json:"spec_defect_rids,omitempty"`
	SpecDefectProse string   `json:"spec_defect_prose,omitempty"`

	// MarkerSummary is the one-line text immediately after `:` on the
	// terminal-marker stdout line (e.g. `IMPLEMENT_PLAN_DEFECT:
	// <summary>`). The clauderunner surfaces this on every non-failure
	// marker so the supervisor has a short operator-facing string to
	// populate BlockedReason / transition reason fields. Distinct from
	// Details, which carries the full body of any paired markdown file
	// (discovered_prereq.md / plan_defect.md) — Details is fine for
	// Linear comments and triager payload assembly, but is too long for
	// a one-line summary field.
	MarkerSummary string `json:"marker_summary,omitempty"`

	// Diagnosis is the OPTIONAL structured diagnosis (SOR-1240) the
	// implementer may pair with any non-failure terminal marker. Non-nil
	// when the agent wrote `<state_dir>/implementer_diagnosis.yaml`
	// alongside the marker; the supervisor's applyImplementerResult emits
	// `implementer_diagnosed_<kind>` as a separate audit event so
	// operators can grep the structured diagnosis on the audit surface
	// instead of spelunking the session's JSON files.
	Diagnosis *ImplementerDiagnosis `json:"diagnosis,omitempty"`

	// Marker is the raw terminal-marker tag the clauderunner read off the
	// agent's terminal stdout line (e.g. "IMPLEMENT_OK", "FEEDBACK_NOOP",
	// "IMPLEMENT_PLAN_DEFECT"). Surfaced verbatim so the supervisor's
	// implementer_cycle_summary telemetry can record exactly which marker
	// fired without re-deriving it from Status. Empty on the *_FAILED
	// path (that never reaches this struct — the runner returns an error)
	// and on any boot-recovery replay that reconstructs the result from
	// the on-disk mirror (which persists only summary/status/details).
	Marker string `json:"marker,omitempty"`

	// CycleMetrics carries the claude result frame's usage / cost / turn
	// counters for the cycle, surfaced by agentcli.parseTerminal on
	// every non-failure marker path. Zero-valued when no result frame was
	// emitted (the subprocess crashed before its result frame) — the
	// supervisor's implementer_cycle_summary telemetry records the zero
	// values in that case. Inert for runtime behavior; telemetry only.
	CycleMetrics CycleMetrics `json:"cycle_metrics,omitempty"`

	// DiscoveryMarkdown carries the read-only discovery phase's human-readable
	// artifact (phased dispatch). Non-empty only on the IMPLEMENT_DISCOVERY_OK
	// marker path: ParseImplementerResult DERIVES it from the typed discovery
	// result via role.RenderDiscoveryMarkdown (SOR-2357 — the typed section
	// fields are the source of truth, this markdown is the projection). The
	// supervisor's discovery result handler seats it on the issue row's
	// DiscoveryMarkdown column for operator display / telemetry +
	// transitioning discovering → implementing. Empty for every other marker
	// (the implementation / feedback / rebase cycles produce PRs, not a
	// discovery artifact).
	DiscoveryMarkdown string `json:"discovery_markdown,omitempty"`

	// DiscoveryResult carries the read-only discovery phase's TYPED result —
	// the six section fields the agent submitted via submit_result, plus the
	// OPTIONAL parsed predicted footprint. Non-nil for every well-formed
	// discovery dispatch: the implementer_discovery dispatch contract
	// (dispatchContracts, internal/role/dispatch_contract.go) routes
	// typed-result extraction on the contract identity (SPEC-SOR-2785 R2/R3,
	// SOR-2811) — it calls role.ParseImplementerDiscoveryResult UNCONDITIONALLY,
	// so the typed result is seated regardless of the stdout terminal marker
	// (the prior `_DISCOVERY_OK`-suffix gate in ParseImplementerResult is no
	// longer the population path for the discovery contract). The supervisor's
	// discovery result handler consumes it directly — seating
	// DiscoveryResult.PredictedFootprint on the issue (Exploration cleared)
	// atomically with the discovering → implementing transition. Nil on the base
	// implementer arm (implementation / feedback / rebase cycles produce PRs,
	// not a discovery artifact).
	DiscoveryResult *ImplementerDiscoveryResult `json:"-"`

	// DispatchMarkerMismatch is ADVISORY telemetry (SPEC-SOR-2785 R2, SOR-2811):
	// NewImplementerDispatcher sets it for the discovery arm when the session's
	// stdout terminal marker does not begin with the resolved dispatch
	// contract's requiredMarker (IMPLEMENT_DISCOVERY_OK). It is NEVER a routing
	// decision — typed-result extraction has already routed on the dispatch
	// contract before this comparison runs, so a mismatch only records that the
	// agent printed an off-phase stdout marker for a well-formed discovery
	// submission. Empty on every dispatch whose stdout marker matches its
	// contract (and on the base implementer arm, where the stdout marker
	// legitimately differs from requiredMarker across feedback/rebase cycles, so
	// the check is scoped to the discovery role). Consumers may surface it on the
	// audit-event surface; it has no behavioral effect.
	DispatchMarkerMismatch string `json:"dispatch_marker_mismatch,omitempty"`

	// RequirementTraceJSON carries the implementer's OPTIONAL structured
	// requirement→code trace for spec-driven dispatches (SOR-2260), read from
	// `<state_dir>/requirement_trace.json` — the analog of the discovery
	// phase's typed result, but emitted at the END of an implementing
	// cycle on the success marker path (IMPLEMENT_OK / FEEDBACK_OK /
	// REBASE_OK). agentcli.parseTerminal reads the file onto the payload
	// under `requirement_trace_json` (absent → omitted, present-but-unreadable
	// → hard error) and ParseImplementerResult passes it through verbatim here.
	// The consumer decodes it into a *spectrace.RequirementTrace
	// (internal/spectrace); this layer only threads the raw string. Empty on
	// non-spec-driven dispatches and any cycle that emitted no trace.
	RequirementTraceJSON string `json:"requirement_trace_json,omitempty"`
}

ImplementerResult is the typed view of the implementer's submit_result payload. Exactly one of the (success) or (status) field groups is populated.

func ParseImplementerResult

func ParseImplementerResult(raw map[string]any) (ImplementerResult, error)

ParseImplementerResult validates the agent's payload and returns a typed result. Inputs:

  • A `marker` field set by clauderunner from the agent's terminal stdout line: IMPLEMENT_OK / FEEDBACK_OK / REBASE_OK on success; IMPLEMENT_NOOP / IMPLEMENT_DISCOVERED_PREREQ on the autonomous- action path; *_FAILED never reaches here (runner returns error).
  • A `detail` field with the marker's free-text suffix (or, for DISCOVERED_PREREQ, the body of <state_dir>/discovered_prereq.md when present — see agentcli.parseTerminal). On the success path the detail line carries the one-line summary.
  • Optional `status` + `details` + `prereq_kind` + `existing_key` if the agent emitted JSON shapes directly (legacy / structured-output path).

SOR-1148: the success-path emit contract is the IMPLEMENT_OK marker + the semantic `summary` field only. `branch` and `pr_urls` are daemon-derived (branch from the dispatched BRANCH: header, PR URLs from FindPRByBranch after the marker) and are hard-rejected if present in raw — see rejectDaemonDerivedFields.

Rules:

  • marker=*_NOOP → Status="noop", Details from detail.
  • marker=*_DISCOVERED_PREREQ → Status discriminates on the parsed `Shape:` header in detail. Missing shape header AND no `## Classification` heading is a hard error; missing shape header WITH a `## Classification` heading is treated as Shape: new (legacy back-compat for in-flight reports authored before SOR-939).
  • marker=*_OK → success path: Summary from `summary` field or detail.
  • status=<X> field overrides marker-derived status when explicitly set.

type ImplementerTask

type ImplementerTask struct {
	Issue              string
	LinearID           string
	Title              string
	Body               string
	AcceptanceCriteria []string
	Repos              []string
	WorktreePaths      map[string]string
	Branch             string
	DependsOn          []string
	MergeOrder         []string
	Cycle              string // CycleInitial | CycleRebase | "feedback-2" etc.
	// BranchModel selects the implementer's push-and-PR contract
	// (SOR-1137). Three values:
	//   - "per_child" (and the empty back-compat sentinel): legacy
	//     shape — push the feature branch and open a PR per repo.
	//   - "plan_branch" (legacy deferred integration): push the child
	//     working branch ONLY and stop — the daemon's one-shot assembler
	//     squashes the cohort onto a fresh plan branch at end-of-plan.
	//     The implementer must NOT run `gh pr create`.
	//   - "continuous_plan_branch" (CIPB Phase B): identical implementer
	//     contract to plan_branch — push the child working branch and
	//     stop. The difference is downstream: the daemon's per-child
	//     squash handler lands the branch on the LIVE plan branch as
	//     soon as the per-child reviewer approves.
	// Rendered into the envelope as `BRANCH_MODEL:`.
	BranchModel    string
	PriorPRURLs    map[string]string
	ReviewFeedback []ReviewConcern
	RebaseReason   string
	// PlanBranchConflict (SOR-1319) carries the structured signal the
	// implementer needs to resolve a plan-branch integration conflict
	// surfaced by the daemon's pre-squash or squash mechanism. Populated
	// by dispatchImplementer on a feedback cycle dispatched in response
	// to plan_branch_pre_squash_rebase_conflict or
	// plan_branch_squash_conflict; nil on every other dispatch path
	// (initial, rebase, reviewer-concern feedback). When non-nil, the
	// implementer's feedback-cycle handling switches from "re-validate
	// against current state" to "fetch the plan tip, merge it into the
	// child working branch, resolve the listed paths' textual
	// conflicts, commit + push."
	PlanBranchConflict *PlanBranchConflictFeedback
	// PlanPRReferBack (SOR-1328) carries the structured signal an
	// implementer plan-PR-fix dispatch needs to address a final
	// reviewer's refer-back on the cumulative diff of a plan PR. The
	// daemon populates it (in child #3's dispatch wiring) when a final
	// reviewer cycle issues a refer-back verdict on the plan PR; it hands
	// the implementer a worktree checked out on the plan branch directly
	// (per repo) and the implementer commits + pushes to the plan branch,
	// NOT to a per-child working branch. Pointer so the back-compat
	// default is nil for every non-fix dispatch (initial, rebase,
	// reviewer-concern feedback, plan-branch-conflict feedback). When
	// non-nil, FormatImplementerTask renders the PLAN_PR_REFER_BACK
	// envelope block documented in .claude/agents/implementer.md.
	PlanPRReferBack *PlanPRReferBackFeedback
	// RetryPreamble is the SOR-227 narration-retry notice the daemon
	// constructs (carrying the live N-th of M figures) when this
	// dispatch follows a narration-class failure on the same issue. The
	// dispatcher copies it onto RunOptions.RetryPreamble; the runner
	// prepends it to the per-call prompt body. Empty for cycle 1 of any
	// issue and for every dispatch that follows a non-narration outcome.
	RetryPreamble string
	// DispatchRole is the agent-spec role name the dispatcher runs this
	// task on (phased dispatch). The implementer family spans the read-only
	// discovery agent and the base implementer: the supervisor picks the
	// name from the issue's dispatch state (`discovering` →
	// "implementer_discovery", every other implementer-cycle state →
	// "implementer") and threads it here. Empty is the back-compat sentinel
	// for the base "implementer" role, so a task constructed without this
	// field still dispatches the implementer.
	DispatchRole string
	// DiscoveryMarkdown carries the read-only discovery phase's artifact
	// into the implementation phase (phased dispatch). When non-empty,
	// FormatImplementerTask appends a fenced `DISCOVERY:` block holding the
	// markdown verbatim — the authoritative implementation plan the
	// implementer reads before any work (see the Input contract section of
	// .claude/agents/implementer.md). Empty for the discovery dispatch
	// itself (discovery PRODUCES the artifact, it does not consume one) and
	// for every legacy dispatch that ran before the discovery phase existed.
	DiscoveryMarkdown string
	// SpecExcerpt carries the spec-requirement context for a spec-driven
	// child — each cited R-ID's prose statement + textual formal, plus the
	// spec glossary. The daemon resolves it once (resolveSpecExcerpt) from
	// the child's spec_id + verifies and populates it at the single
	// ImplementerTask literal, so both the discovery and implementation
	// dispatches carry it. Non-nil only for a spec-driven child;
	// FormatImplementerTask renders the SPEC_EXCERPT: block and the
	// VERIFIES: line when set and omits both when nil — the same strict
	// nil-gate the reviewer's CoverageSummary block follows.
	SpecExcerpt *SpecExcerpt
	// Verifies is the spec requirement-ID list this issue verifies, rendered
	// as the VERIFIES: line alongside SpecExcerpt. Populated together with
	// SpecExcerpt; empty for a legacy (non-spec-driven) child.
	Verifies []string
	// GeneratedTestStubs carries daemon-generated test-stub source (Phase D)
	// the implementer must commit unchanged and back with the cited
	// generator / predicate helpers. When non-empty, FormatImplementerTask
	// renders it as a fenced GENERATED_TEST_STUBS block beside the
	// SpecExcerpt / Verifies rendering; empty for every child that carries no
	// generated stubs (the back-compat default). The block uses the same
	// backtick-fence sizing as the DISCOVERY block so embedded Go code fences
	// cannot prematurely close it.
	GeneratedTestStubs string
	// GeneratedTestStubsDir is the worktree-relative directory the daemon wrote
	// the generated stub file into — the deterministic per-spec directory
	// (stubname.Directory keyed on the issue's spec id) when the dispatch is
	// spec-scoped, empty for the legacy worktree-root placement. When non-empty,
	// FormatImplementerTask announces `stub_path: <dir>/<basename>` so the agent
	// edits the stub the daemon actually wrote; GeneratedTestStubsBasename
	// supplies the basename. Empty for every child that carries no generated
	// stubs or whose stub landed at the worktree root.
	GeneratedTestStubsDir string
	// GeneratedTestStubsBasename is the bare filename of the generated prop_test
	// stub the daemon writes by construction (e.g. "proptest_stubs_test.go" for
	// Go, "proptest_stubs.ts" for TypeScript), threaded at dispatch time from the
	// spec's language profile (langprofile.Profile.PropTest.StubFilename) — never
	// a package-level constant, so the announced basename follows the project's
	// language. FormatImplementerTask joins it onto GeneratedTestStubsDir for the
	// `stub_path:` annotation; a caller that leaves it empty falls back to the
	// Go-default profile basename so the announced path is never a bare directory.
	GeneratedTestStubsBasename string
	// RequiredPropTestHelpers is the binding list of per-requirement gen helper
	// obligations for a spec-driven implementing dispatch whose verifies set
	// includes at least one prop_test-mode requirement (spec SPEC-SOR-2946-v1 R1).
	// Each entry names the implementer-authored helper file path and the
	// func Property<RID> it must declare. Populated on the implementing dispatch
	// ONLY — never the read-only discovery dispatch — by RequiredPropTestHelpers
	// in internal/daemon/proptest_required_helpers.go. Nil/empty when no
	// prop_test R-ID is cited; FormatImplementerTask renders the
	// REQUIRED_PROPTEST_HELPERS block if and only if at least one obligation is
	// present. Distinct from GeneratedTestStubs: this names required OUTPUT (the
	// helper files the implementer must author), not the advisory reference stub.
	RequiredPropTestHelpers []PropTestHelperObligation
	// RequiredBenchmarkTargets is the binding list of per-requirement benchmark
	// obligations for a spec-driven implementing dispatch whose verifies set
	// includes at least one benchmark-mode requirement (spec SPEC-SOR-3095-v1 R4).
	// Each entry names the implementer-authored benchmark file path, the benchmark
	// func it must declare, and the requirement's bound text. Populated on the
	// implementer write cycles (implementing / feedback / rebasing) — never the
	// read-only discovery dispatch — by RequiredBenchmarkTargets in
	// internal/daemon/benchmark_required_targets.go. Nil/empty when no benchmark
	// R-ID is cited; FormatImplementerTask renders the REQUIRED_BENCHMARK_TARGETS
	// block if and only if at least one obligation is present. The benchmark
	// analog of RequiredPropTestHelpers.
	RequiredBenchmarkTargets []BenchmarkTargetObligation
	// ModelOverride / EffortOverride carry the per-dispatch model-tier +
	// reasoning-effort the supervisor resolved for a phased-discovery
	// dispatch (per-project config knob, else the role spec frontmatter
	// default). The dispatcher closure prefers them over the family-effort
	// it captured at construction time: a non-empty EffortOverride replaces
	// the family effort, and ModelOverride lands as RunOptions.Model
	// (empty → no --model flag). The supervisor sets BOTH only when
	// dispatching the discovery role (DispatchRole == "implementer_discovery");
	// every post-discovery dispatch leaves them empty so its existing
	// family-effort + subscription-default-model behavior is unchanged.
	ModelOverride  string
	EffortOverride string
	// RuntimeSelector carries the per-project implementer dispatch runtime
	// (config runtime.implementer) the supervisor stamps in
	// dispatchImplementerSession: "container" routes the claude subprocess
	// through the ContainerRunner, "native" or empty keeps it on the host.
	// The dispatcher copies it onto RunOptions.RuntimeSelector; the runner
	// consults it in runOnce to choose the dispatch runtime. Both phases
	// (discovery + implementation) carry the same selector — the operator's
	// runtime choice applies to the read-only discovery run and the full
	// implementation run alike.
	RuntimeSelector string
}

ImplementerTask is the typed view of one per-task message handed to the implementer conversation. The fields that apply depend on Cycle:

  • initial: Issue, Repos, WorktreePaths, Branch, AcceptanceCriteria, DependsOn, MergeOrder. PriorPRURLs / ReviewFeedback / RebaseReason are unused.
  • feedback-N: same as initial PLUS PriorPRURLs and ReviewFeedback.
  • rebase: same as initial PLUS PriorPRURLs and RebaseReason.
  • plan-pr-fix: an autonomous plan-PR refer-back fix (SOR-1328). The daemon checks out the plan branch directly in each repo's worktree and dispatches with Issue, Title, Body, Repos, WorktreePaths, Branch=plan branch, BranchModel=plan_branch, PriorPRURLs=plan PR URLs, and PlanPRReferBack carrying the final reviewer's concerns + fix-cycle context. The implementer addresses the concerns on the plan branch and pushes it directly (no per-child branch, no new PR).

type InFlightImplFootprint

type InFlightImplFootprint struct {
	Key          string
	Title        string
	RepoSlug     string
	TouchedFiles []PredictedTouchedFile
	CreatedFiles []PredictedCreatedFile
}

InFlightImplFootprint is the daemon's compact view of one non-terminal implementation issue's predicted_footprint for inclusion in the planner envelope's IN_FLIGHT_IMPL_FOOTPRINTS block (CIPB Phase E cross-impl footprint awareness). The planner reads the block to detect file-level overlap between a proposed child and work already in flight, and reconciles it (re-scope, depends_on, or PROPOSAL_REJECTED). Only issues with a non-empty, non-exploration footprint are surfaced.

RepoSlug is the first repo in the issue's Repos list — a heuristic prefix on every file line for multi-repo clarity. For a multi-repo impl whose files span repos the prefix is informational only; the deterministic enforcement (plan_reviewer + scheduler gate) is repo-accurate. Empty Repos renders bare paths without a prefix.

type MergeResolverResult

type MergeResolverResult struct {
	// Resolved is true when the resolver finalized the squash commit
	// (status=resolved); false on give-up (status=gave_up).
	Resolved bool `json:"resolved"`
	// FailureReason is a one-line summary of the give-up reason.
	// Required when Resolved=false; empty when Resolved=true.
	FailureReason string `json:"failure_reason,omitempty"`
	// ResolvedPaths lists the repo-relative paths the resolver edited
	// to clear conflict markers. Required (non-empty) when
	// Resolved=true; empty when Resolved=false.
	ResolvedPaths []string `json:"resolved_paths,omitempty"`
	// CommitSHA is the SHA of the squash commit the resolver produced
	// via `git commit`. Required when Resolved=true; empty when
	// Resolved=false. The daemon verifies the commit and pushes it.
	CommitSHA string `json:"commit_sha,omitempty"`

	// Coercions is a transient (non-persisted) parse-time signal
	// (SOR-1401): each entry names one liberal-parser coercion that
	// ParseMergeResolverResult applied to the raw payload (e.g.
	// `field=resolved_paths from_shape=string to_shape=[]string`). The
	// daemon-side resolver closure reads it after the dispatcher returns
	// and emits one `role_input_coerced` audit event per entry so
	// operators can grep for how often each misshape fires in production.
	// Always nil on disk (json:"-") and nil when the input was already
	// the canonical shape.
	Coercions []string `json:"-"`
}

MergeResolverResult is the typed view of the merge_resolver's submit_result payload. Schema validation runs in ParseMergeResolverResult so malformed shapes become tool-level errors the resolver can self-correct.

func ParseMergeResolverResult

func ParseMergeResolverResult(raw map[string]any) (MergeResolverResult, error)

ParseMergeResolverResult validates and parses a submit_result payload from the merge_resolver. Mirrors ParseReviewerVerdict's structure: missing required fields, unknown status values, and shape mismatches become typed errors the daemon surfaces back to the resolver as a tool_result so it self-corrects in-conversation.

SOR-1401 / SOR-1432: the parser is LIBERAL in what it accepts for the production misshapes that burned the resolver's N=2 retry budget on schema-shape errors rather than real conflicts. `resolved_paths` normalization is now delegated to the shared NormalizeStringList helper (sibling to NormalizeArrayOrKeyedObject), so all liberal-string-list parser sites share one mechanism:

  • `resolved_paths` accepts (a) the canonical array of strings, (b) a single string (the call site wraps it into a one-element array before invoking the shared helper), (c) an array of objects each carrying a `path` string field (the `file` alias is normalized to `path` at the call site for backward compat with the bespoke pre-SOR-1432 helper), or (d) an object keyed by path (the outer key IS the path; values are ignored — the fourth shape the shared helper accepts, intentionally widened in SOR-1432).
  • `status` is INFERRED when omitted: presence of a non-empty `commit_sha` AND non-empty `resolved_paths` infers `resolved`; presence of a non-empty `failure_reason` and no `commit_sha` infers `gave_up`. If neither inference fires, the current “ `status` is required “ error stands so the resolver still self-corrects in-conversation.

Each coercion appends one descriptor to MergeResolverResult.Coercions at the canonical `field=<name> from_shape=<X> to_shape=[]string` format (load-bearing for the recognizer's per-misshape trend signal). The daemon-side closure (buildMergeResolverClosure) reads the slice after the dispatcher returns and emits one `role_input_coerced` audit event per entry. Downstream Go consumers see the canonical shape only.

On any validation failure the daemon should append the error message as a tool_result so the resolver revises in-conversation.

type MergeResolverTask

type MergeResolverTask struct {
	// PlanKey is the planning issue that owns the plan branch this
	// child's squash targets (e.g. "SOR-350"). Surfaces in the envelope
	// so the resolver can name the originating plan.
	PlanKey string
	// ChildKey is the child issue whose squash conflicted. Appears in the
	// success-marker line only
	// (`MERGE_RESOLVER_OK: resolved <N> path(s) on <ChildKey>`), which is
	// audit/stdout — never committed. The finalization commit subject is
	// CommitSubject, which carries no per-daemon key.
	ChildKey string
	// ChildTitle is the child issue's title; informational context for
	// the resolver. It is NOT concatenated into the commit subject — the
	// daemon encodes the (key-free) subject in CommitSubject instead.
	ChildTitle string
	// CommitSubject is the daemon-supplied squash commit subject the
	// resolver uses verbatim in the finalization `git commit -m`. The
	// daemon computes it via the same github.AssembleCommitSubject logic
	// the non-conflict assembly path uses, so both squash-subject paths
	// share one source of truth and the per-daemon routing key never
	// reaches a committed artifact.
	CommitSubject string
	// ChildBranch is the child's short-lived working branch (e.g.
	// "sorcerer/sor-355"). The assembler ran `git merge --squash
	// <ChildBranch>` into the plan branch; the merge's conflict markers
	// are what the resolver resolves. Informational — the resolver does
	// not re-run the merge.
	ChildBranch string
	// ChildAcceptanceCriteria is the child issue's acceptance-criteria
	// bullets in filed order (text only, checkbox prefix stripped).
	// These are the resolver's primary signal for what the child
	// intended; preserved content must satisfy a criterion or the
	// no-silent-drop invariant fires.
	ChildAcceptanceCriteria []string
	// ChildBody is the child issue's full markdown body. Read for
	// context when the criteria don't unambiguously pin the intent.
	ChildBody string
	// PriorSquashedChildren is the list of children whose squashes
	// already landed on the plan branch ahead of this one, in
	// dependency order. The resolver reads them to identify which prior
	// sibling laid down the HEAD content in a registry-style conflict.
	PriorSquashedChildren []ChildSquashRecord
	// PlanGoalBody is the planning issue's high-level goal body. Use
	// to disambiguate when the child's content and a prior sibling's
	// content overlap.
	PlanGoalBody string
	// DefaultBranch is the project's default branch (typically "main").
	// Informational; the resolver does not check it out.
	DefaultBranch string
	// RepoSlug is the slug ("owner/repo") the WorktreePath belongs to.
	RepoSlug string
	// WorktreePath is the absolute path to the ephemeral worktree the
	// daemon prepared for this dispatch. Already on the plan branch
	// with the conflicting squash merge in progress (the assembler ran
	// `git merge --squash` that produced the conflict before
	// dispatching). The dispatcher projects this single path onto
	// RunOptions.AddDirs so the runner adds it to the subprocess's
	// allowed-dirs.
	WorktreePath string
	// ConflictingPaths is the list of repo-relative paths `git status`
	// reports as conflicted at dispatch time. Rendered as the
	// CONFLICTING_PATHS: checklist in the envelope so the resolver
	// knows the authoritative file set to walk.
	ConflictingPaths []string
	// Cycle is the 1-indexed dispatch cycle. The daemon may re-dispatch
	// after a transient_error or boot_recovery, in which case this
	// counts up; the resolver uses it to avoid repeating a strategy
	// that already failed.
	Cycle int
	// RetryPreamble is the SOR-227 narration-retry notice the daemon
	// constructs (carrying the live N-th of M figures) when this
	// dispatch follows a narration-class failure. The dispatcher copies
	// it onto RunOptions.RetryPreamble; the runner prepends it to the
	// per-call prompt body. Empty on every dispatch that follows a
	// non-narration outcome.
	RetryPreamble string
}

MergeResolverTask is the typed view of one per-task message handed to the merge_resolver conversation. The dispatcher renders it via FormatMergeResolverTask before handing the string to the runner.

The resolver runs against ONE child's squash conflict in ONE repo per dispatch — multi-repo plans dispatch one resolver per conflicted (child, repo) pair.

type MissingDiscoveryFieldsError

type MissingDiscoveryFieldsError struct {
	Missing []string
}

MissingDiscoveryFieldsError is the typed rejection ParseImplementerDiscoveryResult returns when the typed discovery payload is missing one or more of the six required section fields (absent or empty/whitespace). Missing carries the absent field names in canonical order so the in-conversation re-prompt names exactly what the agent must add. The validator runs at the submit boundary, so a missing field is a retryable rejection bounded by MaxSubmitResultAttempts — never the downstream discovery_validation_failed (blocked_user) path.

func (*MissingDiscoveryFieldsError) Error

type MissingReviewerDiscoveryFieldsError

type MissingReviewerDiscoveryFieldsError struct {
	Missing []string
}

MissingReviewerDiscoveryFieldsError is the typed rejection ParseReviewerDiscoveryResult returns when the typed reviewer-discovery payload is missing one or more of the five required section fields (absent or empty/whitespace). Missing carries the absent field names in canonical order so the in-conversation re-prompt names exactly what the agent must add. The validator runs at the submit boundary, so a missing field is a retryable rejection bounded by MaxSubmitResultAttempts — never the downstream review_discovery_validation_failed (blocked_user) path.

func (*MissingReviewerDiscoveryFieldsError) Error

type PRForReview

type PRForReview struct {
	Repo         string        // "owner/repo"
	URL          string        // PR URL
	Mergeable    string        // "MERGEABLE" | "CONFLICTING" | "UNKNOWN"
	Base         string        // base branch (typically main)
	Head         string        // head branch
	Checks       []CheckRollup // CI rollup
	BotFindings  []string      // free-form bot review summaries
	FilesChanged int
	Additions    int
	Deletions    int
	Diff         string // unified diff
}

PRForReview is one PR's relevant state, supplied to the reviewer.

type PlanBranchConflictFeedback

type PlanBranchConflictFeedback struct {
	// Reason is the conflict-event kind, e.g.
	// "plan_branch_pre_squash_rebase_conflict" (a SOR-1296 merge of the
	// plan tip into the child branch failed) or
	// "plan_branch_squash_conflict" (the final `merge --squash` from the
	// child onto the plan branch failed).
	Reason string
	// PlanBranch is the long-lived branch name (e.g. sorcerer/plan/sor-350)
	// the implementer needs to fetch + merge into the child working
	// branch.
	PlanBranch string
	// Paths is the file list git reported as unmerged. The implementer
	// edits these to resolve the conflict markers.
	Paths []string
}

PlanBranchConflictFeedback carries the structured signal an implementer feedback cycle needs to resolve a plan-branch integration conflict (SOR-1319). The daemon populates it from the most-recent plan_branch_*_rebase_conflict / plan_branch_squash_conflict event for the issue; the implementer reads it from the rendered PLAN_BRANCH_CONFLICT envelope block and follows the resolution procedure documented in .claude/agents/implementer.md.

type PlanPRReferBackFeedback

type PlanPRReferBackFeedback struct {
	// PlanKey is the parent plan issue's key (e.g. SOR-350).
	PlanKey string
	// PlanBranch is the long-lived plan branch name (e.g.
	// sorcerer/plan/sor-350) the daemon checked out in each repo's
	// worktree; the implementer commits + pushes to it directly.
	PlanBranch string
	// PlanPRURLs maps each repo slug to its open plan PR URL. The plan
	// PR's head is the plan branch, so advancing the branch auto-updates
	// the PR.
	PlanPRURLs map[string]string
	// Concerns are the final reviewer's concerns from the refer-back
	// verdict on the cumulative plan diff. Rendered in input order.
	Concerns []ReviewConcern
	// Rationale is the reviewer's free-text rationale for the refer-back.
	Rationale string
	// Cycle is the 1-indexed plan-PR fix cycle (the current
	// iss.PlanPRFixCycle post-increment).
	Cycle int
	// Budget is the project's MaxRefererBackCycles cap on plan-PR fix
	// cycles.
	Budget int
}

PlanPRReferBackFeedback carries the structured signal an implementer plan-PR-fix dispatch needs to address a final reviewer's refer-back on the cumulative diff of a plan PR (SOR-1328). The daemon populates it from the final reviewer cycle's refer-back verdict; the implementer reads it from the rendered PLAN_PR_REFER_BACK envelope block and follows the resolution procedure documented in .claude/agents/implementer.md. Mirrors PlanBranchConflictFeedback's shape but carries fix-cycle context (concerns + budget) rather than conflict-resolution context (merge paths).

type PlanReviewerTask

type PlanReviewerTask struct {
	ProposalID      int64
	Request         string
	Repos           []string
	ExplorableRepos []string
	ExistingIssues  []ExistingIssue
	// WorktreePaths is the per-repo role-worktree paths the supervisor
	// prepared for this dispatch. The dispatcher projects this map to
	// the deterministic sorted RunOptions.AddDirs slice (parallel to
	// ImplementerTask.WorktreePaths). Replaces the prior
	// ExposeProjectRoot=true mode that pointed the plan_reviewer at the
	// operator's raw clone — see SOR-287 / SOR-288.
	WorktreePaths map[string]string
}

PlanReviewerTask is the typed input for the plan_reviewer role. It arrives between the planner's submission and the daemon's Linear writes: the planner subprocess POSTed an envelope to the daemon and got back a proposal id; the supervisor calls the plan-reviewer with that id, and the reviewer queries the daemon for the proposed-state children, optionally mutates fields via the issue CLIs, and submits `sorcererd plan review --decision=approve|reject` before any Linear issues are filed by the bridge writer.

type PlanReviewerVerdict

type PlanReviewerVerdict struct {
	Decision        string   `json:"decision"`
	ProposalID      int64    `json:"proposal_id,omitempty"`
	Summary         string   `json:"summary,omitempty"`
	EditsMade       []string `json:"edits_made,omitempty"`
	ConcernsUnfixed []string `json:"concerns_unfixed,omitempty"`
}

PlanReviewerVerdict is the typed output the plan_reviewer emits. `Decision` is one of "approve" or "reject"; `ProposalID` carries the proposal id the reviewer just decided on so the supervisor can project the children from the issuestore (the HTTP-side approve path has already flipped them to `waiting`).

func ParsePlanReviewerVerdict

func ParsePlanReviewerVerdict(raw map[string]any) (PlanReviewerVerdict, error)

ParsePlanReviewerVerdict validates the agent's review.json payload.

Rules:

  • `decision` is required and must be "approve" or "reject".
  • On "reject", `concerns_unfixed` should be non-empty (so the escalation has actionable text); empty is allowed but warned via a synthetic concern of "no detail".
  • `edits_made` is optional; informational only.

type PlannerACAnnotation

type PlannerACAnnotation struct {
	Klass string               `json:"klass,omitempty"`
	Probe *dsl.ActivationProbe `json:"probe,omitempty"`
}

PlannerACAnnotation is one child acceptance criterion's activation classification, emitted by the planner as typed submit_result data (SOR-2506). Klass is the criterion's class — `activation` (provable only by executing the merged system, carrying an executable evidence probe) or `pre_merge` / "" (provable from the PR set at review time, the unchanged default). Probe is the typed, daemon-evaluable evidence check an activation criterion carries (nil for a pre_merge criterion). Entries are positional: ACAnnotations[i] annotates the i-th criterion in the child body's `## Acceptance criteria` ordering.

type PlannerIssue

type PlannerIssue struct {
	Title        string   `json:"title"`
	BodyMarkdown string   `json:"body_markdown"`
	DependsOn    []string `json:"depends_on,omitempty"`
	Repos        []string `json:"repos"`
	// MergeOrder is daemon-owned. The LLM is not supposed to emit it;
	// the validator hard-rejects non-empty LLM-emitted values. Kept on
	// the struct so the wire-envelope adapter can hand the parsed value
	// to the validator for detection (SOR-1147).
	MergeOrder []string `json:"merge_order,omitempty"`
	// LabelGroup is daemon-owned. The LLM is not supposed to emit it;
	// the validator hard-rejects non-empty LLM-emitted values. Kept on
	// the struct so the wire-envelope adapter can hand the parsed value
	// to the validator for detection (SOR-1147).
	LabelGroup string `json:"label_group,omitempty"`
	// Priority is daemon-derived. The LLM is not supposed to emit it; the
	// validator hard-rejects non-zero LLM-emitted values. Kept on the
	// struct so the submit handler can fill it in deterministically
	// (parent's priority or PriorityOverride.Value) before persisting.
	Priority int `json:"priority,omitempty"`
	// MaxAgeSeconds is daemon-derived. The LLM is not supposed to emit
	// it; the validator hard-rejects non-zero LLM-emitted values. Kept on
	// the struct so persistence-side callers can read the daemon-resolved
	// value (zero means "use project default" — the prior storage-layer
	// semantic is preserved).
	MaxAgeSeconds int `json:"max_age_seconds,omitempty"`
	// PriorityOverride is the explicit per-issue priority deviation the
	// planner may emit when a child genuinely needs a different priority
	// than the parent's. Omit (nil) to inherit the parent's priority via
	// ApplyChildPriority — the right default for load-bearing children.
	// Less-urgent overrides (PriorityOverride.Value > parent.Priority)
	// require a non-empty Reason; the validator rejects unjustified
	// downgrades.
	PriorityOverride *PriorityOverride `json:"priority_override,omitempty"`
	// PredictedFootprint is the planner's declaration of every file
	// region this implementation child intends to touch. Phase A of
	// CIPB (docs/proposals/continuous-integration-architecture.md):
	// the planner emits the prediction, the validator rejects
	// proposals whose siblings would race on the same files, and the
	// scheduler serializes ready children whose declared footprints
	// overlap an in-flight sibling's. ParsePlannerResult is liberal —
	// a missing or empty footprint parses clean (defaults to a
	// zero-value PredictedFootprint); the validator REJECTS the plan
	// when the footprint is empty on an implementation child so the
	// planner is forced to enumerate the surfaces it intends to
	// touch.
	PredictedFootprint PredictedFootprint `json:"predicted_footprint,omitempty"`
	// Verifies is the spec requirement-ID list this child implements,
	// emitted ONLY on spec-driven dispatches (the planner task carried a
	// SPEC: block). Each entry is an R-ID, optionally qualified
	// (e.g. "R2.case_a"); the proposal-time validator resolves a
	// qualifier to its base R-ID by prefix-strip at the first '.' for the
	// coverage and reference-closure checks. Legacy (spec-unaware)
	// dispatches omit it — the parser is LIBERAL (absence parses clean),
	// and the validator enforces the coverage / closure contract only
	// when the planning issue's spec R-ID set is threaded through
	// ValidationContext.RequirementIDs.
	Verifies []string `json:"verifies,omitempty"`
	// SatisfiesParentACs is the free-prose analog of Verifies, emitted on
	// non-spec-driven dispatches. Each entry is a 1-based positional index
	// into the parent planning issue's ExtractAcceptanceCriteria ordering,
	// declaring which parent ACs this child satisfies. The parser is
	// LIBERAL exactly like Verifies — a missing or empty value parses
	// clean. The fields are inert until a later wiring change threads the
	// union of all children's declared indices into the validator's
	// AC-coverage gate.
	SatisfiesParentACs []int `json:"satisfies_parent_acs,omitempty"`
	// ACAnnotations carries the per-AC activation classification the planner
	// emits as typed submit_result data (SOR-2506): each entry annotates the
	// positionally-corresponding acceptance criterion in this child's body
	// `## Acceptance criteria` ordering with its class (`pre_merge` /
	// `activation`) and, for an activation criterion, the typed evidence Probe
	// the daemon fires in the plan_activating phase. The parser is LIBERAL —
	// a missing or empty value parses clean, and an entry may carry a nil Probe
	// (a pre_merge criterion). It is the typed carrier the merge-gating
	// placement gate reads (DetectActivationACInMergeGatingSet, via
	// ProposedChild.ACKlasses) so an activation-classified criterion can never
	// enter the final plan-PR reviewer's merge verdict (spec R3).
	ACAnnotations []PlannerACAnnotation `json:"ac_annotations,omitempty"`
}

PlannerIssue is one issue the planner proposes. Mirrors the on-the- wire schema defined in agents.md "Role: planner" → "Required output".

Five fields the planner used to emit are now daemon-owned and not part of the LLM contract. Each is kept on the struct so the validator can detect "LLM emitted a non-zero/non-empty value" and hard-reject; the documented schema in `.claude/agents/planner.md` no longer mentions any of them:

  • Priority is inherited from the parent planning issue automatically (SOR-1145). A non-zero LLM-emitted Priority is hard-rejected by the validator. Explicit deviation flows through the structured PriorityOverride field instead.
  • MaxAgeSeconds is the project default at submit time (SOR-1145). A non-zero LLM-emitted MaxAgeSeconds is hard-rejected. Per-issue overrides are not available on the LLM emit contract; split the issue if it needs more wall clock.
  • origin_issue (envelope-level, not per-child) is resolved from the daemon's session→issue map every submission (SOR-1145).
  • MergeOrder is not currently persisted from planner submissions (SOR-1147). A non-empty LLM-emitted MergeOrder is hard-rejected; multi-repo merge ordering is declared in body_markdown's `## Merge order` prose section.
  • LabelGroup is not currently persisted from planner submissions (SOR-1147). A non-empty LLM-emitted LabelGroup is hard-rejected; label groupings are not part of the v2 issuestore schema.

type PlannerResult

type PlannerResult struct {
	ProposalID int64          `json:"proposal_id,omitempty"`
	Issues     []PlannerIssue `json:"issues,omitempty"`
	Rationale  string         `json:"rationale,omitempty"`
	// OverlapConsidered is the planner's explicit statement about how
	// the proposed children's scope relates to in-flight open PRs the
	// daemon surfaced via the existing-issues digest. The deterministic
	// validator requires this to be non-empty (and at least 40 trimmed
	// characters) when the digest contains at least one entry with a
	// non-empty PRURL.
	OverlapConsidered string `json:"overlap_considered,omitempty"`
	// DescopedACs is the planner's proposal-level explicit-descope list:
	// each entry declares a parent planning-issue acceptance criterion the
	// plan deliberately defers, with a required non-empty rationale and a
	// tracked follow-up reference. ParsePlannerResult parses it liberally
	// at the envelope level (a missing/null value parses clean) but
	// returns a hard per-field parse error for a malformed entry (missing
	// rationale or follow_up). Inert until a later wiring change consumes
	// it alongside the per-child SatisfiesParentACs in the AC-coverage
	// gate.
	DescopedACs []DescopedAC `json:"descoped_acs,omitempty"`
	// AmendedIssues carries the planner's re-emitted corrected bodies on
	// a scoped AC-amend cycle (TriggerPlanDefectScopedAmend, SOR-214).
	// Empty on every other cycle; the production scoped dispatcher
	// populates it from the planner's submit_result payload and the
	// daemon applies each entry via the audited amend capability.
	AmendedIssues []AmendedIssue `json:"amended_issues,omitempty"`
	// NoAmendments signals that a scoped AC-amend cycle parsed cleanly
	// but the planner returned no corrected bodies — `amended_issues`
	// was null, missing, or an empty array (SOR-300). It lets the
	// supervisor distinguish "planner ran fine but judged no amendment
	// needed (or dropped the field)" from "planner errored" so the
	// no-amendment outcome can emit its own audit event and continue
	// the dispatch sequence instead of escalating. Only set by the
	// scoped-amend dispatch path; every other cycle leaves it false.
	NoAmendments bool `json:"no_amendments,omitempty"`
}

PlannerResult is the typed view of the planner's submit outcome. With the proposals-lifecycle cutover, the planner subprocess submits its envelope to the daemon via `sorcererd plan submit` and the daemon mints + stores the children; the supervisor receives ProposalID and reads the children back from the issuestore. Issues / Rationale are kept for in-process callers (validators, boot replays, fakes) but the production path leaves them empty.

func ParsePlannerResult

func ParsePlannerResult(raw map[string]any) (PlannerResult, error)

ParsePlannerResult validates the planner's submit_result payload and returns a typed result. Shape errors are returned with clear per-field messages so the daemon can prompt the planner to revise in-conversation.

Semantic validation (file-existence, dep cycles, allowlist, etc.) is in internal/plan.ValidatePlannerResult, which runs after this parse.

type PlannerTask

type PlannerTask struct {
	Trigger string
	// SessionID is the daemon-issued sessions.id (e.g. "p-<hex>") the
	// supervisor opened for this planner dispatch. The dispatcher
	// threads it into RunOptions.SessionID so the clauderunner uses it
	// as the subprocess's session identity and exports it as
	// SORCERER_SESSION_ID in the subprocess env. The planner prompt's
	// `sorcererd plan submit --planner-session=$SORCERER_SESSION_ID`
	// call site depends on this round-trip: SubmitProposal's
	// PlanningKeyForPlannerSession looks the id up in sessions.id and
	// reads issue_key to resolve the planning parent's priority for
	// ApplyChildPriority. SOR-1330: pre-fix the runner generated a
	// fresh in-memory id disconnected from the daemon's sessions
	// table, so the lookup always missed and every planner-emitted
	// child landed at priority=0.
	SessionID         string
	Request           string // initial: user request markdown
	OriginIssue       string // replan / scoped-amend: the issue that escalated
	PrereqDescription string // replan: the prereq the implementer needs
	// PlanDefectReport is the rendered implementer plan_defect report
	// (defect type, offending ACs, evidence, suggested correction). Set
	// only on TriggerPlanDefectScopedAmend.
	PlanDefectReport string
	// AmendTargets names the issues whose acceptance criteria the scoped
	// AC-amend cycle must re-emit, each with the body in place at
	// dispatch time. Set only on TriggerPlanDefectScopedAmend.
	AmendTargets []AmendTarget
	// Concerns carries the verbatim feedback the planner must address on
	// a revision cycle — either the plan_reviewer's `concerns_unfixed`
	// joined as a bulleted list, or the deterministic validator's error
	// string (with the `plan validation failed: ` prefix stripped). The
	// planner reads CONCERNS: identically regardless of source. Empty on
	// initial dispatch and on the first replan cycle; non-empty when the
	// supervisor dispatches a revising cycle. See SOR-119.
	Concerns string
	// ProposalRejectionFindings carries the programmatic-validator
	// findings from the most recent proposal-time lint rejection. When
	// non-empty, FormatPlannerTask renders them as a PROPOSAL_REJECTED:
	// block near the top of the task body so the planner can self-correct
	// the defective proposal before re-submitting. Empty on a clean
	// dispatch; populated by dispatchPlanningIssue on a lint-rejection
	// revising cycle (the lint-rejection analog of Concerns → CONCERNS:).
	ProposalRejectionFindings []ProposalRejectionFinding

	ExistingIssues []ExistingIssue
	// InFlightImplFootprints carries the predicted_footprint of every
	// non-terminal implementation issue with a materialized (non-empty,
	// non-exploration) footprint, so the planner can plan around
	// cross-impl file-level overlap at proposal time (CIPB Phase E).
	// FormatPlannerTask caps the rendered block at inFlightImplFootprintCap
	// entries with an overflow marker; the supervisor passes the full
	// collected set (sorted most-recently-active first).
	InFlightImplFootprints []InFlightImplFootprint
	ExplorableRepos        []string
	Repos                  []string
	TeamKey                string
	// WorktreePaths is the per-repo role-worktree paths the supervisor
	// prepared for this dispatch. The dispatcher projects this map to
	// the deterministic sorted RunOptions.AddDirs slice (parallel to
	// ImplementerTask.WorktreePaths). Replaces the prior
	// ExposeProjectRoot=true mode that pointed the planner at the
	// operator's raw clone — see SOR-287 / SOR-288.
	WorktreePaths map[string]string
	// Spec carries the approved spec on a spec-driven planning dispatch
	// (phase C handoff). Nil for non-spec planning issues; FormatPlannerTask
	// renders the SPEC: section from it and omits the section when nil.
	Spec *PlannerTaskSpec
}

PlannerTask is the typed view of one per-task message for the planner.

Four modes (state-machines.md §6, agents.md "Role: planner"):

  • Initial planning (Trigger == TriggerInitial): the user submitted a request via /sorcerer; Request holds the markdown body.
  • Replan (Trigger == TriggerDiscoveredPrereq): an implementer escalated; OriginIssue is the issue that hit the prereq, and PrereqDescription is what they reported.
  • Operator-revise (Trigger == TriggerRevising): the operator amended the planning issue's body via `sorcerer plan refactor`; OriginIssue carries the planning issue's own key and Request holds the amended body. Distinct from Replan: no implementer escalated.
  • Scoped AC-amend (Trigger == TriggerPlanDefectScopedAmend): an implementer reported an ambiguous plan-validity defect; OriginIssue is the issue it was reported on, PlanDefectReport is the rendered report, and AmendTargets names the issues whose acceptance criteria the planner must re-emit in place.

type PlannerTaskSpec

type PlannerTaskSpec struct {
	BodyYAML   string
	ParsedSpec *dsl.Spec
}

PlannerTaskSpec carries an approved spec onto the planner task envelope (spec-driven phase C handoff). It is set only when the planning issue is spec-driven; FormatPlannerTask renders the SPEC: section from it and omits the section entirely when Spec is nil. BodyYAML is the canonical YAML from the approved specs row; ParsedSpec is the AST obtained via dsl.Unmarshal at dispatch time so the planner need not re-parse the YAML (the glossary rides on ParsedSpec.Glossary).

type PredictedCreatedFile

type PredictedCreatedFile = footprint.PredictedCreatedFile

PredictedFootprint, PredictedTouchedFile, PredictedCreatedFile are re-exported from internal/footprint so callers reaching for the types via internal/role continue to compile. The canonical declaration lives in internal/footprint so internal/sm can carry the same shape on Issue without either package importing the other.

type PredictedFootprint

type PredictedFootprint = footprint.PredictedFootprint

PredictedFootprint, PredictedTouchedFile, PredictedCreatedFile are re-exported from internal/footprint so callers reaching for the types via internal/role continue to compile. The canonical declaration lives in internal/footprint so internal/sm can carry the same shape on Issue without either package importing the other.

type PredictedTouchedFile

type PredictedTouchedFile = footprint.PredictedTouchedFile

PredictedFootprint, PredictedTouchedFile, PredictedCreatedFile are re-exported from internal/footprint so callers reaching for the types via internal/role continue to compile. The canonical declaration lives in internal/footprint so internal/sm can carry the same shape on Issue without either package importing the other.

type PriorityOverride

type PriorityOverride struct {
	Value  int    `json:"value"`
	Reason string `json:"reason,omitempty"`
}

PriorityOverride is the structured per-issue priority deviation the planner may emit on a child object. Value follows Linear's convention (0=none, 1=urgent, 2=high, 3=medium, 4=low). Reason MUST be non-empty when Value is strictly less urgent than the parent's priority (Linear's "lower number = more urgent" convention means a less-urgent priority has a higher numeric value).

type ProbeEvidence

type ProbeEvidence struct {
	Kind          string `json:"kind"`
	Summary       string `json:"summary,omitempty"`
	RunID         int64  `json:"run_id,omitempty"`
	HeadSHA       string `json:"head_sha,omitempty"`
	ArtifactPath  string `json:"artifact_path,omitempty"`
	CommandOutput string `json:"command_output,omitempty"`
}

ProbeEvidence is the typed evidence pointer a recorded activation-probe result carries (SOR-2502 / spec R10). It names the kind of probe and the concrete artifact the daemon observed — a committed artifact path, a CI run id + head SHA, or truncated command output — so the generated traceability matrix and `sorcerer issue show` can cite WHY an activation requirement was marked satisfied or failed. Every field is omitempty so an evidence record encodes compactly. It is serialized as JSON inside RequirementVerdict.Evidence into the review_verdicts.per_requirement_verdict column — no DB schema change.

type PropTestHelperObligation

type PropTestHelperObligation struct {
	RID             string
	HelperPath      string
	PredicateSymbol string
}

PropTestHelperObligation is one entry in the required-helpers set for a spec-driven implementation issue that verifies a prop_test-mode requirement. It is the single ground-truth shape (spec SPEC-SOR-2946-v1 R4) the three downstream sites — the dispatch instruction (R1), the predicted-footprint augmentation (R2), and the absent-package authoring refer-back (R3) — read so they cannot disagree about which helpers an issue owes.

  • RID is the requirement ID (e.g. "R2").
  • HelperPath is the worktree-relative path for the implementer-authored per-requirement helper file (<specDir>/gen/r<N>.go), derived from stubname.HelperFilePath.
  • PredicateSymbol is the exported predicate function name (e.g. "PropertyR2"), derived from stubname.PredicateSymbol.

The type lives in internal/role (not internal/daemon) so a dispatch task can carry it without pulling daemon dependencies into the role package; it is produced by RequiredPropTestHelpers in internal/daemon/proptest_required_helpers.go.

type ProposalRejectionFinding

type ProposalRejectionFinding struct {
	// Lint is the validate.Lint* heuristic name that produced the finding
	// (e.g. "fuzzy_ac_duplicate").
	Lint string `json:"lint"`
	// ChildTitle is the offending proposed child's title.
	ChildTitle string `json:"child_title"`
	// ACExcerpt is a one-line excerpt of the offending acceptance
	// criterion / finding detail rendered on the block's `ac=` field.
	ACExcerpt string `json:"ac_excerpt"`
	// Rationale is the one-line operator-readable explanation rendered on
	// the block's `rationale:` line.
	Rationale string `json:"rationale"`
}

ProposalRejectionFinding is one programmatic-validator finding the planner-bridge surfaces back to the planner as a PROPOSAL_REJECTED: feedback-block entry after rejecting a proposal at submit time. It is the role-package projection of an internal/plan/validate.Finding (the gate's native type); the supervisor splits the validator's one-line detail into the offending-criterion excerpt and the remediation rationale when it builds these from the carried rejection.

type RecognizerResult

type RecognizerResult struct {
	Status            RecognizerStatus
	Detail            string
	SessionID         string
	ValidActions      []Action
	Findings          []Finding
	SelfReflection    SelfReflection `json:"self_reflection"`
	RejectedReasons   []string
	DroppedFabricated []DroppedFabricatedAction
	EnrichmentErrors  []EnrichmentError
	RawJSON           []byte
}

RecognizerResult is the typed result the dispatcher returns from one recognizer run. ValidActions is the slice of rows that survived schema validation AND the fabricated-evidence pre-emit gate; RejectedReasons is the slice of "<field>: <detail>" strings the daemon emits as recognizer_invalid_output events (genuine schema failures only); DroppedFabricated is the slice of actions the pre-emit gate filtered out because at least one evidence row cited an event_id not present in the input payload (the daemon emits recognizer_dropped_fabricated_evidence events for these — a distinct kind so the role-health watchdog's "_invalid_output" suffix doesn't pause the recognizer on a desync between the LLM's conversation memory and the per-turn payload, SOR-1087). RawJSON is the entire side-effect file's bytes — preserved for forensics. SessionID is the clauderunner-minted subprocess id (threaded through payload["session_id"] by parseTerminal) so audit events can attribute back to the source subprocess. EnrichmentErrors is the slice of evidence event_ids that survived the fabricated-evidence gate (i.e. are present in the daemon's per-tick EventDaemons map) yet had no enrichable (ts, message) record in EventRecords. Both maps are built from the same per-tick events slice, so a divergence should never happen — the daemon's Tick path emits a recognizer_evidence_enrichment_error event per entry as defense-in-depth observability.

func ParseRecognizerResult

func ParseRecognizerResult(payload map[string]any, vctx RecognizerValidatorContext) (RecognizerResult, error)

ParseRecognizerResult decodes the payload the runner returns from a recognizer subprocess. Validates each `actions[i]` row independently against the schema AND the daemon-supplied validator context (vctx); surviving rows go on ValidActions, rejected rows go on RejectedReasons. The marker tag is read from payload["marker"]; a missing or non-RECOGNIZER_* marker is a hard failure.

vctx may be zero — in that case per-row schema validation still runs, but the cross-row evidence existence check is skipped because there is no daemon-supplied event map to compare against.

type RecognizerStatus

type RecognizerStatus int

RecognizerStatus is the parsed terminal marker tag. The supervisor branches on this to decide what to record.

const (
	RecognizerStatusUnknown RecognizerStatus = iota
	RecognizerStatusOK
	RecognizerStatusFailed
)

type RecognizerTask

type RecognizerTask struct {
	WindowEventsFrom  int64
	WindowEventsTo    int64
	WindowEventIDFrom int64
	WindowEventIDTo   int64
	DispatchMode      string
	PayloadJSON       string
	ValidatorContext  RecognizerValidatorContext
}

RecognizerTask is the typed view of one per-task message handed to the recognizer conversation. PayloadJSON is the wrapped `{events, issues, recent_merges, triager_bridges}` JSON object the agent parses out of the indented PAYLOAD: block; WindowEventsFrom / WindowEventsTo bound the events slice's timestamp range for sanity-checking inside the agent.

WindowEventIDFrom / WindowEventIDTo bound the events slice's event_id range explicitly (SOR-1087): the daemon truncates the per-turn payload to fit the recognizer's token_cap, so an LLM that remembers higher / lower event_ids from prior turns can no longer cite them as evidence on the current turn. The agent reads these to enforce the "cite from this turn's payload only" rule in its system prompt; the daemon's pre-emit gate (see validateAction below) filters any action whose evidence event_id is absent from the per-turn EventDaemons map regardless, so the numbers in the envelope are an advisory frame for the LLM, not a load-bearing correctness gate.

ValidatorContext carries the daemon-side metadata the validator reads to enforce per-row rejection rules that depend on runtime state. FormatRecognizerTask never emits these fields onto the wire — the agent doesn't see them — but the dispatcher threads them into ParseRecognizerResult on the way back.

DispatchMode selects the per-task lens (SOR-1177): "reactive" for an event-write-triggered dispatch carrying a narrow per-trigger payload, "reflective" for the timer-driven dispatch carrying the wide raw-event payload. FormatRecognizerTask emits it as a DISPATCH_MODE line above the PAYLOAD block; an empty value renders as DispatchModeReflective for back-compat with call sites that predate the field.

type RecognizerValidatorContext

type RecognizerValidatorContext struct {
	AllowedDaemons map[string]bool
	EventDaemons   map[int64]string
	EventRecords   map[int64]EventRecord
}

RecognizerValidatorContext is the daemon-supplied side-channel the validator reads to enforce per-row rejection rules that depend on runtime state not visible from the JSON row alone.

AllowedDaemons is the set of daemon names a row's evidence may claim. The validator currently uses this as a sanity surface only for the multi-daemon assembly case; single-daemon mode (the LocalDaemonName fallback) is the common path.

EventDaemons maps event_id → daemon name as recorded by the assembly step. When non-nil, the validator confirms each evidence row's cited event_id exists in this map — i.e. that the LLM is citing real events from the input payload rather than fabricated ids. A nil EventDaemons disables the check (used by tests that don't seed the per-event map).

EventRecords maps event_id → the daemon-canonical (ts, message) for that event, as recorded by the assembly step from the same per-tick events slice EventDaemons is built from. The recognizer's emit contract no longer carries ts / message (SOR-1151): the daemon already holds both in its events table keyed by event_id, so the validator's enrichment step copies them onto each evidence row rather than trusting the LLM's reconstruction. A nil EventRecords map disables enrichment (tests that don't seed the per-tick map).

func DecodeRecognizerValidatorCtx

func DecodeRecognizerValidatorCtx(ctxJSON []byte) RecognizerValidatorContext

DecodeRecognizerValidatorCtx decodes the recognizer validator context from the wire bytes. An empty or malformed context yields the zero value (every per-row check disabled), matching the pre-threading behavior.

type RequirementVerdict

type RequirementVerdict struct {
	Verdict   string `json:"verdict"` // pass | fail
	Rationale string `json:"rationale,omitempty"`

	// ActivationStatus carries an activation criterion's probe outcome
	// (pending | satisfied | failed) for the post-merge plan_activating phase
	// (spec R10). It is distinct from Verdict (the pre-merge reviewer pass/fail):
	// an activation requirement is proven by executing the merged system, not by
	// the PR-set review, so its matrix cell is sourced from a recorded probe
	// result rather than a reviewer judgment. omitempty keeps pre-activation
	// reviewer rows byte-identical (empty ActivationStatus, nil Evidence).
	ActivationStatus string `json:"activation_status,omitempty"`
	// Evidence is the typed pointer to the probe evidence backing a recorded
	// activation result (the artifact path / CI run id / command output the
	// probe observed). nil on a pre-merge reviewer verdict and on a pending
	// activation result; non-nil once a terminal probe result is recorded.
	Evidence *ProbeEvidence `json:"evidence,omitempty"`
}

RequirementVerdict is one entry in the per_requirement_verdict map the reviewer emits for spec-driven dispatches. Verdict is "pass" or "fail"; Rationale is the reviewer's one-line justification. The activation fields below are empty/nil on a pre-merge reviewer verdict and populated only when the entry records a post-merge activation-probe result (SOR-2502 / spec R10).

func NewActivationVerdict

func NewActivationVerdict(status string, evidence *ProbeEvidence) RequirementVerdict

NewActivationVerdict builds the per-requirement verdict entry recording one terminal activation-probe result, carrying the activation status and the evidence pointer (spec R10: recorded "with evidence pointers"). The pre-merge Verdict field stays empty — an activation requirement's cell is the activation status, never the reviewer pass/fail.

type ReviewConcern

type ReviewConcern struct {
	PR       string `json:"pr"`
	File     string `json:"file,omitempty"`
	Line     int    `json:"line,omitempty"`
	Severity string `json:"severity"` // blocker | major | minor
	Comment  string `json:"comment"`
	// ConcernClass is the OPTIONAL coverage-check classifier the reviewer
	// stamps on a concern raised by the COVERAGE_SUMMARY check (one of
	// coverage_regression | coverage_gap | coverage_missing_invariant; see
	// ParseReviewerVerdict's closed-set validation). Empty for every
	// non-coverage concern. It rides the verdict-parse → feedback-envelope
	// → implementer-REVIEW_FEEDBACK pipeline verbatim — FormatImplementerTask
	// renders a `concern_class:` line when set, and feedback's
	// RenderImplementerFeedback projects ReviewerConcerns through untouched —
	// so the implementer's next refer-back cycle sees which coverage rule
	// fired without a new envelope shape.
	//
	// JSON omitempty keeps the on-disk row byte-identical for the common
	// case (non-coverage concerns) and old persisted rows decode cleanly
	// with ConcernClass empty.
	ConcernClass string `json:"concern_class,omitempty"`
	// FailedRequirementID is the OPTIONAL spec requirement R-ID this concern
	// is bound to, set only on spec-driven dispatches when the reviewer's
	// per_requirement_verdict map marks the R-ID `fail`. The supervisor's
	// applyReviewerVerdict synthesizes one such concern per failing R-ID in
	// its refer-back arm (mirroring projectVerifierFailuresToConcerns) so the
	// implementer's next feedback cycle's REVIEW_FEEDBACK block names which
	// requirement failed. It rides the verdict-parse → feedback-envelope →
	// implementer-REVIEW_FEEDBACK pipeline verbatim — FormatImplementerTask
	// renders a `requirement_id:` line when set, and feedback's
	// RenderImplementerFeedback projects ReviewerConcerns through untouched —
	// so no new envelope shape is required (the ConcernClass precedent).
	//
	// JSON omitempty keeps the on-disk row byte-identical for the common
	// case (legacy / non-spec-driven concerns) and old persisted rows decode
	// cleanly with FailedRequirementID empty.
	FailedRequirementID string `json:"failed_requirement_id,omitempty"`
}

ReviewConcern is one issue raised against a PR by the reviewer. It appears in implementer feedback messages and in reviewer verdicts.

PR is the repo slug (`owner/repo`) the concern attaches to — a member of the issue's repos allowlist, NOT a PR URL. The reviewer LLM no longer reconstructs the URL (which the daemon already owns via iss.PRURLs[repo]); it emits the slug and the daemon resolves the URL at verdict-apply time (SOR-1149).

func AssemblyFixerConcernsToReviewConcerns

func AssemblyFixerConcernsToReviewConcerns(in []AssemblyFixerConcern) []ReviewConcern

AssemblyFixerConcernsToReviewConcerns projects the assembly_fixer's per-action concern slice onto the implementer-feedback ReviewConcern shape. Field mapping is 1:1 EXCEPT for ReviewConcern.PR which is left empty — AssemblyFixerConcern carries no PR identifier because the assembly_fixer classifies a per-child squash failure rooted in the cargo-output artifact, not in a per-child PR review thread. The implementer's REVIEW_FEEDBACK rendering tolerates an empty PR (the `pr:` line is still emitted but downstream tools key on File/Line/Comment for actionability).

Lives in internal/role/ (alongside the two source types) so the feedback envelope renderer in internal/role/feedback/ can call it without forming an import cycle with internal/daemon/.

type ReviewerDiscoveryResult

type ReviewerDiscoveryResult struct {
	// DiffSummary is a paragraph summarizing what the change does.
	DiffSummary string
	// PerACMapping maps each acceptance criterion to the file:line range (or
	// `not satisfied`) in the diff that addresses it.
	PerACMapping string
	// PerCriterionThoughts is the preliminary pass | fail | unsure plus a
	// one-line rationale per acceptance criterion.
	PerCriterionThoughts string
	// IdentifiedConcerns lists concrete issues found in the change, or `none`.
	IdentifiedConcerns string
	// OverallDirection is the discovery's lean — one of lean merge / lean
	// feedback / lean rebase / lean escalate.
	OverallDirection string

	// OneLineSummary is the terse one-liner the discovery agent emitted after
	// the `:` on its REVIEW_DISCOVERY_OK marker line. Operator-facing summary
	// only; never load-bearing for the judgment phase.
	OneLineSummary string
}

ReviewerDiscoveryResult is the typed result of one reviewer-discovery dispatch — the read-only phase that maps the change (a PR set or one child branch under plan_branch) against the issue's acceptance criteria into a structured read the downstream judgment-phase reviewer consumes via its REVIEW_DISCOVERY envelope block.

SOR-2360: the discovery agent submits the five section fields as a TYPED submit_result payload (one field per section), validated at the submit boundary. The human-readable discovery artifact is DERIVED from these typed fields via RenderReviewDiscoveryMarkdown — the typed fields are the source of truth, the markdown is the projection.

func ParseReviewerDiscoveryResult

func ParseReviewerDiscoveryResult(payload map[string]any) (ReviewerDiscoveryResult, error)

ParseReviewerDiscoveryResult validates the discovery agent's typed submit_result payload and returns a typed result.

Inputs (SOR-2360):

  • The five section fields — `diff_summary`, `per_ac_mapping`, `per_criterion_thoughts`, `identified_concerns`, `overall_direction` — each a markdown string the agent submits via submit_result.
  • `marker_summary` / `detail` / `summary` — the verbatim one-line suffix of the terminal marker, surfaced under different keys depending on whether the parser is handed the raw parseTerminal payload, the merged marker payload, or the submitted envelope. We read them in that order so OneLineSummary is populated on every path.

Validation: every one of the five required section fields must be present and non-empty (after trimming). A payload missing any of them is rejected with a typed *MissingReviewerDiscoveryFieldsError so the submit boundary can re-prompt the agent with the exact missing field names.

type ReviewerTask

type ReviewerTask struct {
	Issue              string
	LinearID           string
	AcceptanceCriteria []string
	PRSet              []PRForReview
	// BranchModel discriminates the reviewer's input contract (SOR-1138).
	// "" / "per_child" → PR-driven review (PRSet is the review target,
	// rendered as PR_SET:). "plan_branch" → branch-diff review (the
	// deferred-integration model): there is no per-child PR and nothing
	// is integrated yet, so PRSet is empty and the child's short-lived
	// working branch + its base ref carry the review target instead. The
	// reviewer agent prompt branches on the rendered BRANCH_MODEL: line.
	BranchModel string
	// PlanKey is the planning issue that owns this child's deferred plan.
	// Set only when BranchModel == "plan_branch".
	PlanKey string
	// ChildBranch is the child's short-lived working branch (same name
	// across repos — ChildWorkingBranchName). The review target is
	// `git diff <base>..<ChildBranch>` per repo. Set only when
	// BranchModel == "plan_branch".
	ChildBranch string
	// BaseRef maps repo slug ("owner/repo") to the ref the child working
	// branch was carved off — origin's default branch when the child has
	// no sibling dependency, or the sibling dependency's working branch
	// when the child stacks on one. The reviewer diffs the child's branch
	// against this base (`git diff <base>..<ChildBranch>`). Per-repo
	// because the base differs per repo in a multi-repo plan. Set only
	// when BranchModel == "plan_branch".
	BaseRef map[string]string
	// FinalPlanReview discriminates the mandatory final plan-PR review
	// (SOR-1139). When true the review target is a plan PR — the single
	// PR per repo opened from a plan_branch planning issue's plan branch
	// to main, carrying the cumulative diff of every child's squash
	// commit. PRSet carries the plan PR(s); AcceptanceCriteria carries
	// the plan's high-level goal (typically the planning issue's
	// PromptBody), NOT the per-criterion ACs of individual children
	// (those were already reviewed per-commit). The reviewer agent
	// prompt branches on the rendered FINAL_PLAN_REVIEW line: it
	// re-validates the plan's high-level goal against the cumulative
	// diff. Mutually exclusive with BranchModel == "plan_branch" (that
	// selects per-child commit-driven review).
	FinalPlanReview bool
	Cycle           int      // review cycle number, 1-indexed
	PriorVerdicts   []string // free-form summary of prior verdicts on this issue
	// Second-opinion mode: when true, FirstVerdict and Instruction are set
	// and the reviewer is asked to argue against its prior verdict.
	SecondOpinion bool
	FirstVerdict  string
	Instruction   string
	// WorktreePaths is the per-repo role-worktree paths the supervisor
	// prepared for this dispatch. The dispatcher projects this map to
	// the deterministic sorted RunOptions.AddDirs slice (parallel to
	// ImplementerTask.WorktreePaths). Replaces the prior
	// ExposeProjectRoot=true mode that pointed the reviewer at the
	// operator's raw clone — see SOR-287 / SOR-288.
	WorktreePaths map[string]string
	// RetryPreamble is the SOR-227 narration-retry notice the daemon
	// constructs (carrying the live N-th of M figures) when this
	// dispatch follows a narration-class failure on the same issue. The
	// dispatcher copies it onto RunOptions.RetryPreamble; the runner
	// prepends it to the per-call prompt body. Empty on every dispatch
	// that follows a non-narration outcome.
	RetryPreamble string
	// DispatchRole is the agent-spec role name the dispatcher runs this
	// task on (phased review dispatch). The reviewer family spans the
	// read-only discovery agent and the base reviewer: the supervisor picks
	// the name from the issue's dispatch state (`review_discovering` →
	// "reviewer_discovery", `review_judging` → "reviewer") and threads it
	// here. Empty is the back-compat sentinel for the base "reviewer" role,
	// so a task constructed without this field still dispatches the reviewer.
	DispatchRole string
	// ReviewDiscoveryMarkdown carries the read-only review-discovery phase's
	// artifact into the judgment phase (phased review dispatch). When
	// non-empty, FormatReviewerTask appends a fenced `REVIEW_DISCOVERY:`
	// block holding the markdown verbatim — the structured read of the diff
	// the judgment-phase reviewer consumes. Empty for the discovery dispatch
	// itself (discovery PRODUCES the artifact, it does not consume one) and
	// for every legacy reviewer dispatch that ran before the discovery phase
	// existed.
	ReviewDiscoveryMarkdown string
	// CoverageSummary carries the issue's invariant-coverage telemetry into
	// the review. Non-nil only when the issue's newest issue_body_features
	// event listed at least one covered invariant or one uncovered
	// sensitive surface; FormatReviewerTask renders the COVERAGE_SUMMARY:
	// block when set and omits it entirely when nil (legacy / docs-only /
	// non-sensitive change). The reviewer's coverage check reads the block
	// to verify the PR preserves/extends each covered invariant and
	// justifies each uncovered sensitive surface; see
	// .claude/agents/reviewer.md § "Coverage check".
	CoverageSummary *CoverageSummary
	// SpecExcerpt carries the spec-requirement context for a spec-driven
	// child — each cited R-ID's prose statement + textual formal, plus the
	// spec glossary. The daemon resolves it once (resolveSpecExcerpt) from
	// the child's spec_id + verifies and populates it at the single
	// ReviewerTask literal, so both the review-discovery and review-judging
	// dispatches carry it. Non-nil only for a spec-driven child;
	// FormatReviewerTask renders the SPEC_EXCERPT: block and the VERIFIES:
	// line when set and omits both when nil — the same strict nil-gate the
	// CoverageSummary block follows.
	SpecExcerpt *SpecExcerpt
	// Verifies is the spec requirement-ID list this issue verifies, rendered
	// as the VERIFIES: line alongside SpecExcerpt. Populated together with
	// SpecExcerpt; empty for a legacy (non-spec-driven) child.
	Verifies []string
	// ModelOverride / EffortOverride carry the per-dispatch model-tier +
	// reasoning-effort the supervisor resolved for a phased review-discovery
	// dispatch (per-project config knob, else the role spec frontmatter
	// default). The dispatcher closure prefers them over the family-effort
	// captured at construction: a non-empty EffortOverride replaces the
	// family effort, and ModelOverride lands as RunOptions.Model (empty → no
	// --model flag). The supervisor sets BOTH only when dispatching the
	// discovery role (DispatchRole == "reviewer_discovery"); every
	// post-discovery (judgment) dispatch leaves them empty so its existing
	// family-effort + subscription-default-model behavior is unchanged.
	ModelOverride  string
	EffortOverride string
}

ReviewerTask is the typed view of one per-task message handed to the reviewer conversation.

type ReviewerVerdict

type ReviewerVerdict struct {
	Decision       string           `json:"decision"`
	Rationale      string           `json:"rationale"`
	PerCriterion   []CriterionCheck `json:"per_criterion"`
	Concerns       []ReviewConcern  `json:"concerns,omitempty"`
	EscalationRule string           `json:"escalation_rule,omitempty"`

	// PerRequirementVerdict maps each R-ID from the issue's verifies list to
	// a {verdict, rationale} entry. Non-nil only on spec-driven dispatches
	// (when ParseReviewerVerdict is called with a non-empty knownVerifies);
	// nil on the legacy per_criterion path. json:omitempty keeps on-disk rows
	// byte-identical for legacy verdicts. DeriveOverallVerdict reconciles this
	// map against the overall Decision; see reviewer_verdict.go.
	PerRequirementVerdict map[string]RequirementVerdict `json:"per_requirement_verdict,omitempty"`

	// Coercions is a transient (non-persisted) parse-time signal
	// (SOR-1433): each entry names one liberal-parser coercion that
	// ParseReviewerVerdict applied to the raw payload (e.g.
	// `field=per_criterion from_shape=json_string to_shape=array`). The
	// daemon-side reviewer-result handler reads it after the dispatcher
	// returns and emits one `role_input_coerced` audit event per entry so
	// operators can grep for how often each misshape fires in production.
	// Mirrors MergeResolverResult.Coercions (SOR-1401, PR #651). Always
	// nil on disk (json:"-") and nil when the input was already the
	// canonical shape.
	Coercions []string `json:"-"`

	// Marker is the raw terminal-marker tag the reviewer emitted
	// (REVIEW_OK on the clean path), lifted by ParseReviewerVerdict from
	// the in-process payload the runner's parseTerminal threads under
	// "marker". Feeds the reviewer_cycle_summary event's marker_emitted
	// field. Transient in-process hand-off — never persisted (json:"-")
	// and empty on the synthesized-escalate / boot-replay paths that
	// bypass parseTerminal.
	Marker string `json:"-"`

	// CycleMetrics carries the claude result frame's usage / cost / turn
	// counters for the reviewer_cycle_summary telemetry, lifted by
	// ParseReviewerVerdict from the in-process payload the runner's
	// parseTerminal stashes under "cycle_metrics" (the reviewer analog
	// of ImplementerResult.CycleMetrics, reusing the same extraction).
	// Zero when no result frame was emitted (the subprocess crashed
	// first). Transient in-process hand-off — never persisted (json:"-"),
	// so a boot-replayed verdict surfaces zero metrics.
	CycleMetrics CycleMetrics `json:"-"`

	// ReviewDiscoveryMarkdown carries the read-only review-discovery phase's
	// artifact back from the dispatcher to the supervisor's discovery-result
	// handler (phased review dispatch). SOR-2360: the reviewer-discovery dispatch
	// parses the typed submit_result fields (ParseReviewerDiscoveryResult) and
	// DERIVES this markdown via RenderReviewDiscoveryMarkdown instead of calling
	// ParseReviewerVerdict (a discovery payload carries no `decision`, which the
	// strict verdict parser hard-rejects); the supervisor's
	// applyReviewerDiscoveryResult seats this pre-rendered markdown directly onto
	// iss.ReviewDiscoveryMarkdown. The reviewer analog of
	// ImplementerResult.DiscoveryMarkdown. Transient in-process hand-off —
	// never persisted (json:"-") and empty on every judgment-phase verdict
	// (which carries a decision, not a discovery artifact). Distinct from
	// ReviewerTask.ReviewDiscoveryMarkdown — that is the judgment phase's
	// INPUT; this field is the discovery phase's OUTPUT.
	ReviewDiscoveryMarkdown string `json:"-"`

	// ReviewDiscoveryPerACMapping carries the reviewer-discovery phase's TYPED
	// per_ac_mapping field (ReviewerDiscoveryResult.PerACMapping) verbatim back
	// from the dispatcher to applyReviewerDiscoveryResult, which extracts its
	// cited path-set once and seats it onto the structured
	// iss.ReviewDiscoveryFootprint column at the review_discovering →
	// review_judging transition. The deviation telemetry then reads the
	// structured field rather than re-parsing the derived ReviewDiscoveryMarkdown
	// (spec R8). Distinct from ReviewDiscoveryMarkdown: this is the authoritative
	// typed section content, that is the human-readable projection. Transient
	// in-process hand-off — never persisted (json:"-") and empty on every
	// judgment-phase verdict (which carries a decision, not a discovery artifact).
	ReviewDiscoveryPerACMapping string `json:"-"`
}

ReviewerVerdict is the typed view of the reviewer's submit_result payload. Schema validation runs in ParseReviewerVerdict so malformed shapes (the v1 second-opinion bug class) become tool-level errors the reviewer can self-correct.

func ParseReviewerVerdict

func ParseReviewerVerdict(raw map[string]any, knownRepos []string, knownVerifies ...string) (ReviewerVerdict, error)

ParseReviewerVerdict validates and parses a submit_result payload from the reviewer. Catches every v1 second-opinion bug class structurally: missing decision field, malformed concern shape, unknown status values.

knownRepos is the issue's repos allowlist (the reviewer task's known repo slugs). Every `concerns[i].pr` must be a member: the field now carries a repo slug (`owner/repo`), not a PR URL — the daemon resolves the URL from iss.PRURLs[repo] at verdict-apply time (SOR-1149). When knownRepos is empty (a degenerate task with no repo set, or a replay against an issue whose Repos were never recorded) the membership check is skipped — the non-empty-slug requirement still holds.

knownVerifies is the issue's spec requirement-ID (R-ID) list. When non-empty the dispatch is spec-driven: the reviewer must emit a `per_requirement_verdict` map with exactly one entry per R-ID (every R-ID present, no extras), and the legacy `per_criterion` required check is skipped. When empty (the legacy, spec_id IS NULL path) the existing `per_criterion` validation runs unchanged. The variadic shape keeps every existing two-argument caller on the legacy path with no signature churn; the daemon wiring that supplies the issue's verifies list lands in a dependent change.

On any validation failure the daemon should append the error message as a tool_result so the reviewer revises in-conversation.

type RunOptions

type RunOptions struct {
	// SessionID is the daemon-issued session row id (sessions.id) the
	// supervisor created when it opened this dispatch. The clauderunner
	// uses it as the subprocess's session identity (cwd's session_dir
	// basename, env-var SORCERER_SESSION_ID, the live-tracker map key)
	// so the planner / implementer / reviewer prompts can expand
	// $SORCERER_SESSION_ID to a value that round-trips through the
	// daemon's sessions table. SOR-1330: pre-fix the runner generated a
	// fresh 8-char id per Run() call disconnected from the daemon's
	// sessions table, so `sorcererd plan submit --planner-session=$ID`
	// never resolved → ApplyChildPriority's parent lookup always
	// returned zero → every planner-emitted child landed at priority=0.
	// When empty, the runner falls back to its own newSessionID (test
	// wiring + the deprecated direct-Run path).
	SessionID string
	// AddDirs are absolute directories the agent's tools may read/write.
	// For the implementer this is the per-repo worktree paths; for the
	// reviewer, the same set so it can git-diff and gh pr view; for the
	// planner, the project's bare-clone tree where the planner reads
	// CLAUDE.md and docs/.
	AddDirs []string
	// RetryPreamble is the SOR-227 "BEGIN IMMEDIATELY" notice the
	// supervisor injects on re-dispatch from a narration-class failure.
	// Empty for cycle 1 of any issue; non-empty for any cycle whose
	// previous attempt exited with failure_class ∈ {narration_only,
	// deadline_expired_first_activity}. The runner prepends it (with a
	// blank line) to the per-call prompt body so the agent sees the
	// notice ABOVE the role's normal system prompt and the dispatched
	// task envelope. The daemon constructs the string carrying the
	// live `N-th retry of M` figures so the agent has accurate budget
	// context per cycle.
	RetryPreamble string
	// Effort routes through to the claude CLI subprocess as
	// `--effort <level>` (valid levels: low | medium | high | xhigh | max).
	// Resolved per-role at dispatcher-construction time from
	// cfg.Effort.<role>; the runner threads it onto the subprocess argv
	// before any --add-dir entries. Empty string means "emit no
	// --effort flag" — the runner falls back to claude's hardcoded
	// default and logs a warning. SOR-243.
	Effort string
	// Model routes through to the claude CLI subprocess as
	// `--model <id>`. Every role dispatch populates it from the role's
	// triple resolved verbatim (config.Roles.TripleFor(role)) — no default /
	// calibration substitution (SOR-3269 R4). The read-only discovery
	// sub-phase overrides it with the discovery role's own triple model via
	// task.ModelOverride, which wins over the family model. Empty string
	// means "emit no --model flag" (the claude CLI default takes over).
	Model string
	// RuntimeSelector selects the per-dispatch claude-subprocess runtime:
	// "container" routes the dispatch through the runner's
	// RuntimeRunnerFactory (the ContainerRunner, a rootless `podman run`);
	// "native" or empty uses the shared NativeRunner — today's in-process
	// host spawn, byte-identical to before the runtime seam existed. The
	// implementer dispatcher populates it from the per-project
	// runtime.implementer config (threaded onto ImplementerTask); every
	// other role leaves it empty so their dispatch stays native.
	RuntimeSelector string
	// SubmitResultValidatorCtxJSON is the daemon-supplied validator
	// context for the role's Parse* function, marshaled to JSON bytes
	// (SOR-1378). The runner writes these bytes to a per-session file
	// at dispatch time; the stdio MCP server hosting the submit_result
	// tool boundary reads them back at startup and threads them into
	// every Parse* invocation. Roles whose validator takes no extra
	// context (planner, implementer, merge_resolver, dedupe,
	// plan_reviewer) leave this empty; reviewer threads
	// {"known_repos": [...]} matching ParseReviewerVerdict's signature;
	// triager / recognizer thread their validator-context as a string-
	// keyed JSON shape the dispatch-map decoder reverses.
	SubmitResultValidatorCtxJSON []byte
	// ResumeConversationID, when non-empty, is threaded to the claude
	// subprocess as `--resume <id>` so the dispatch continues a prior
	// conversation instead of starting a fresh one. Used by the steward
	// dispatcher to resume its long-lived, durable conversation across
	// daemon restarts (the handle is read from the persisted
	// state.Conversations["steward"] and threaded back here). Empty for
	// every other dispatcher — the runner emits no `--resume` flag, the
	// fresh-spawn path.
	ResumeConversationID string
}

RunOptions are the per-call extras that don't fit the role/message shape. Populated by each role's dispatcher based on the typed task.

type SelfReflection

type SelfReflection struct {
	Blocker      string `json:"blocker"`
	Rationale    string `json:"rationale"`
	SuggestedFix string `json:"suggested_fix,omitempty"`
}

SelfReflection is the parsed view of the `self_reflection` object the recognizer emits ALONGSIDE its findings + actions on every tick (SOR-1169 Shift 4). It is informational, not load-bearing: the parse is lenient (see ParseRecognizerResult / parseSelfReflection) and the rollup-sweep child consumes the persisted rows out-of-band.

Blocker carries the recognizer's one-token self-classification of why this tick was (un)productive. The five legal values are:

  • `nothing` — productive tick: the recognizer found AND acted.
  • `payload_noise` — events too unfocused to surface a pattern.
  • `schema_too_strict` — pattern observed, no action-vocabulary fit.
  • `insufficient_context` — pattern seen, can't confirm from data.
  • `none_to_find` — daemon state genuinely had nothing pattern-worthy.

The vocabulary is informational: the prompt explicitly says future shifts may extend it, so the parse preserves unknown blockers verbatim rather than rejecting them.

type SpecDrafterResult

type SpecDrafterResult struct {
	SpecYAML  string
	Spec      *dsl.Spec
	Revise    *drafter.ReviseOutput
	Summary   string
	SessionID string
	// Patch is the RFC-6902 JSON-Patch document a TASK: scoped_amend
	// submission returns (set instead of SpecYAML / Spec). The daemon
	// applies it via the patcher's ApplyPatchAtomic seam. Empty on every
	// other task kind.
	Patch json.RawMessage
}

SpecDrafterResult is the typed view of the spec_drafter's validated submit_result payload. SpecYAML is the canonical spec serialized from the typed `spec` object and Spec is the parsed AST (a successful dsl.Unmarshal of the structured payload is the "well-formed spec" gate) — both set on a draft / amend submission. Revise is the SHAPE-validated per-finding resolution output and is set instead on a TASK: revise submission (Spec / SpecYAML stay empty). Summary is the one-line marker suffix. SessionID is the runner-minted subprocess id threaded through payload["session_id"].

func ParseSpecDrafterResult

func ParseSpecDrafterResult(payload map[string]any) (SpecDrafterResult, error)

ParseSpecDrafterResult is the spec_drafter output validator. It runs at the submit_result MCP boundary (so a malformed payload is routed back to the agent as a re-prompt before the subprocess exits) AND post-exit on the runner-merged payload. It rejects the malformed shapes with a typed error (docs/spec-driven.md § 9.2 output contract):

  • absent marker: payload["marker"] is missing or not the SPEC_OK terminal tag. (At the MCP boundary the agent supplies the marker in the payload; post-exit clauderunner injects it from stdout.)
  • missing / non-object spec: the `spec` field is absent or is not a structured object (e.g. a leftover fenced <spec-yaml> string).
  • malformed spec: the structured payload does not decode as a spec DSL document (dsl.Unmarshal fails).

The `spec` field is a typed structured object modeling dsl.Spec (the typed draft/amend submit_result schema, SOR-2515) — NOT a fenced YAML string. The object is round-tripped JSON → dsl.Unmarshal → dsl.Marshal so SpecYAML carries the canonical serialization. The agent-authored spec_id (if any) is DROPPED here so the agent can never author it: the daemon's stamp (internal/daemon/spec_drafter_dispatch.go) is the sole author of the persisted spec_id (R12).

On success it returns the canonical SpecYAML, the parsed *dsl.Spec, and the one-line summary. The semantic pipeline (reference closure, translation, SMT verification) runs in the daemon AFTER this validator accepts — this function gates only the output SHAPE, not the spec's logical validity.

func ParseSpecDrafterReviseResult

func ParseSpecDrafterReviseResult(payload map[string]any, openFindingIDs map[string]bool) (SpecDrafterResult, error)

ParseSpecDrafterReviseResult is the spec_drafter TASK: revise output validator. Like ParseSpecDrafterResult it runs at the submit_result MCP boundary (so a malformed payload routes back to the drafter as a re-prompt carrying the validator error before the subprocess exits) AND post-exit on the runner-merged payload. It rejects an absent / non- SPEC_OK marker, a missing `revise` field, and every malformed ReviseOutput shape (drafter.ParseReviseOutput's rejection classes: wrong type, missing required field, finding-Kind outside the enum, finding-ID not in openFindingIDs, a finding carrying neither / both a patch and a suppress rationale, malformed JSON-Patch op array).

openFindingIDs is the set of finding identifiers the revise envelope supplied; the validator rejects any echoed finding-ID outside it. On success the validated ReviseOutput rides SpecDrafterResult.Revise.

func ParseSpecDrafterScopedAmendResult

func ParseSpecDrafterScopedAmendResult(payload map[string]any) (SpecDrafterResult, error)

ParseSpecDrafterScopedAmendResult is the spec_drafter TASK: scoped_amend output validator (spec-driven phase D). Like the sibling validators it runs at the submit_result MCP boundary AND post-exit on the runner-merged payload. It rejects an absent / non-SPEC_OK marker and a `patch` field that is missing, empty, or not a JSON array of RFC-6902 operations. The full per-op structural validation (and the YAML-apply / re-translation gate) runs in the daemon's ApplyPatchAtomic seam; this validator gates the output SHAPE so a scalar / object / empty patch routes back as a re-prompt before the subprocess exits. On success the patch rides SpecDrafterResult.Patch.

type SpecDrafterTask

type SpecDrafterTask struct {
	// Task is one of SpecDrafterTaskDraft / SpecDrafterTaskAmend /
	// SpecDrafterTaskRevise / SpecDrafterTaskScopedAmend.
	Task string
	// PlanningKey is the planning issue the spec belongs to.
	PlanningKey string
	// Request is the /sorcerer prompt (draft) the drafter elicits a
	// domain + requirements from.
	Request string
	// Amendment is the operator's narrow-change instruction (amend) or the
	// implementer's spec-defect prose (scoped_amend).
	Amendment string
	// Scope is the local|structural amendment discriminator threaded onto
	// the amend / scoped_amend envelope. Empty on draft / revise. On the
	// operator amend it is an advisory hint; on scoped_amend the drafter
	// MUST confine its JSON-Patch to the CitedRIDs' content.
	Scope string
	// CitedRIDs is the set of spec requirement IDs the IMPLEMENT_SPEC_DEFECT
	// marker cited (scoped_amend only). The drafter's JSON-Patch must touch
	// only these requirements' formal / statement / prose; the daemon's
	// classifier reports an edit outside the set as structural.
	CitedRIDs []string
	// CurrentSpecYAML is the canonical YAML of the spec version being
	// amended (amend) or revised (revise). Empty on draft.
	CurrentSpecYAML string
	// SMTLib is the generated SMT-LIB script for the spec version under
	// revision, packed alongside the findings so the drafter sees the
	// solver encoding the unsat core / witness reference (revise only).
	// Empty on draft / amend.
	SMTLib string
	// OpenFindingsJSON is the JSON array of the spec's currently-open
	// findings the drafter must each produce one resolution for (revise
	// only). One revise dispatch covers all open findings.
	OpenFindingsJSON string
	// OpenFindingIDs is the set of finding identifiers OpenFindingsJSON
	// carries, projected onto the submit_result validator context so the
	// revise output validator rejects an echoed finding-ID the dispatch
	// did not supply (revise only).
	OpenFindingIDs []string
	// ValidationError carries the captured pre-verifier pipeline error
	// (schema / reference-closure / formal-parse / translation) the
	// dispatcher threads back on a re-prompt, mirroring the planner's
	// CONCERNS re-dispatch convention. Empty on the first dispatch.
	ValidationError string
	// PriorAttemptYAML is the spec YAML the drafter last submitted that
	// failed the pre-verifier pipeline, threaded back alongside
	// ValidationError so the drafter can correct it in place. Empty on
	// the first dispatch.
	PriorAttemptYAML string
	// SessionID is the daemon-issued session row id, threaded onto the
	// subprocess so $SORCERER_SESSION_ID resolves to a daemon-owned row.
	SessionID string
	// WorktreePaths maps repo slug → the per-(role, session, repo)
	// worktree the drafter may read for domain context.
	WorktreePaths map[string]string
}

SpecDrafterTask is the typed view of one per-task message handed to the long-lived spec_drafter conversation. Task selects the draft / amend flavor; the remaining fields populate the task envelope FormatSpecDrafterTask renders (matching .claude/agents/spec_drafter.md § "Inputs").

type SpecExcerpt

type SpecExcerpt struct {
	// Requirements are the cited R-IDs' excerpts in the spec's declared
	// order (the spec author's intended reading order — not sorted by ID).
	// Only the R-IDs named in the child's Verifies list that resolve in the
	// spec appear; a cited R-ID absent from the spec is silently skipped.
	Requirements []SpecRequirementExcerpt
	// Glossary is the full spec glossary (every term, regardless of which
	// R-IDs are cited) in declared order, so the agent has all domain terms.
	Glossary []SpecGlossaryEntry
}

SpecExcerpt is the spec-requirement context threaded onto a spec-driven child's implementer / reviewer task envelope: the cited requirements' prose statement + textual formal, plus the spec's glossary. The daemon resolves it once from the child's spec_id + verifies (see internal/daemon/spec_excerpt.go) and packs the same value onto both task structs, so every phase of each role (implementer discovery + execution, reviewer discovery + judging) carries it. Non-nil only for a spec-driven child; a legacy child leaves the pointer nil and the Format*Task functions omit the SPEC_EXCERPT: / VERIFIES: lines entirely (the same strict nil-gate CoverageSummary follows). Pure data: rendered by renderSpecExcerpt below.

type SpecGlossaryEntry

type SpecGlossaryEntry struct {
	Term     string
	RefersTo string
}

SpecGlossaryEntry maps a human term to the in-spec concept it refers to, mirroring dsl.GlossaryEntry without coupling the role package to the spec DSL.

type SpecRequirementExcerpt

type SpecRequirementExcerpt struct {
	ID        string
	Statement string
	Formal    string
}

SpecRequirementExcerpt is one cited requirement's excerpt: its R-ID, the EARS prose Statement, and the formal-mini-language Formal text (which may span multiple lines — rendered as a YAML block scalar).

type SpecReviewerFinding

type SpecReviewerFinding struct {
	RequirementIDs []string
	Explanation    string
	Kind           string
}

SpecReviewerFinding names one defect the spec_reviewer surfaced. Kind discriminates a prose<->formal coherence drift ("coherence", the default) from an adversarial intent counterexample ("intent_entailment"). RequirementIDs names the spec requirements the finding implicates (the routing key back to specific requirements); Explanation is the prose describing the gap. Both RequirementIDs and Explanation are load-bearing — ParseSpecReviewerVerdict rejects a finding missing either; Kind defaults to "coherence" when the agent omits it.

type SpecReviewerTask

type SpecReviewerTask struct {
	// SpecYAML is the canonical spec YAML the role judges for faithful
	// capture of the request.
	SpecYAML string
	// Request is the originating /sorcerer request prose the spec is
	// judged against.
	Request string
	// SessionID is the daemon-issued session row id, threaded onto the
	// subprocess so $SORCERER_SESSION_ID resolves to a daemon-owned row.
	SessionID string
}

SpecReviewerTask is the typed view of one per-task message handed to the spec_reviewer conversation. The spec_reviewer is the read-only coherence judge — the formal-spec analog of how reviewer mirrors implementer — that judges whether a formal spec faithfully captures the originating /sorcerer request (the prose<->formal coherence / risk-register R-3 "consistent-but-wrong" check). Both load-bearing inputs ride the envelope; the role reads no project files.

type SpecReviewerVerdict

type SpecReviewerVerdict struct {
	Coherent  bool
	Findings  []SpecReviewerFinding
	SessionID string
}

SpecReviewerVerdict is the typed view of the spec_reviewer's validated submit_result payload. Coherent is the top-level prose<->formal judgment (the spec faithfully captures the request); Findings carries the per-defect findings. A non-coherent verdict carries its coherence drift findings; a COHERENT verdict MAY still carry "intent_entailment" findings — the adversarial intent probe is orthogonal to coherence, so an intent counterexample rides alongside Coherent=true. SessionID is the runner-minted subprocess id threaded through payload["session_id"].

func ParseSpecReviewerVerdict

func ParseSpecReviewerVerdict(payload map[string]any) (SpecReviewerVerdict, error)

ParseSpecReviewerVerdict is the spec_reviewer output validator. It runs at the submit_result MCP boundary (so a malformed payload routes back to the agent as a re-prompt before the subprocess exits) AND post-exit on the runner-merged payload. It rejects:

  • a nil payload.
  • an absent / non-SPEC_REVIEW_OK terminal marker.
  • a missing or non-bool `coherent` flag (the load-bearing judgment).
  • a non-nil, non-array `findings` value (a scalar / object is structural). Absent or null `findings` normalizes to empty — valid on a coherent verdict where the agent omits the field.
  • a finding with an empty / absent `requirement_ids` or an empty `explanation` (both load-bearing for routing the drift back to specific requirements).
  • a finding with a `kind` outside {"coherence", "intent_entailment"}. An absent / empty `kind` normalizes to "coherence" (the back-compat default).

On success it returns the coherence flag plus the validated findings. A coherent verdict MAY carry "intent_entailment" findings (the orthogonal adversarial intent probe); the flag/findings agreement is the daemon's concern, not the parser's.

type StewardQuestion

type StewardQuestion struct {
	Subject string                  `json:"subject"`
	Text    string                  `json:"text"`
	Options []StewardQuestionOption `json:"options"`
}

StewardQuestion is the typed operator-question output (R16): the steward parks Subject safely and surfaces context (Text) plus proposed Options for an operator decision rather than acting unilaterally or dead-ending the subject. Subject is the issue key being parked; Text is the surfaced context; Options are the proposed answers (at least one, each carrying an Action).

type StewardQuestionAction

type StewardQuestionAction struct {
	Kind    string `json:"kind"`               // "transition" | "cancel"
	ToState string `json:"to_state,omitempty"` // target state for kind == "transition"
	Reason  string `json:"reason,omitempty"`   // audit reason for transition / cancel
}

StewardQuestionAction is the mutation an operator-question option applies when chosen (R17). Kind selects the normal daemon mutation bridge — "transition" drives ApplyOperatorTransition (ToState the legal SM target, Reason the audit reason), "cancel" drives ApplyOperatorCancel (Reason the cancel reason). No new writer path: every kind names an EXISTING operator mutation surface.

type StewardQuestionOption

type StewardQuestionOption struct {
	Label   string                `json:"label"`
	Context string                `json:"context,omitempty"`
	Action  StewardQuestionAction `json:"action"`
}

StewardQuestionOption is one proposed answer the operator may pick (R16's "proposed options"). Label is the human-facing choice the answer endpoint matches on (case-sensitive); Context explains the option; Action is what the daemon applies when the option is chosen.

type StewardResult

type StewardResult struct {
	ConversationHandle string
	Summary            string
	Marker             string
	// Actions is the optional typed action vocabulary the steward emits to
	// mutate daemon state this wake (SPEC-SOR-2652 R18–R21). Each entry is one
	// operator verb (amend / transition / priority / comment / cancel / blocks /
	// unblocks / spec verify / spec approve / plan re-arm / role-pause) or a
	// refusal sentinel; the daemon-side executor applies each through an
	// existing mutation surface. Empty (the common case) when the wake produced
	// no mutating action — a nil-safe zero-value slice.
	Actions []steward.StewardAction
	// OperatorQuestion is the optional typed operator-question output (R16 /
	// R17): when non-nil, the daemon parks the question's subject safely and
	// persists the question as a steward_ledger operator-question row. Nil on a
	// wake that asks nothing.
	OperatorQuestion *StewardQuestion
}

StewardResult is the typed result of one steward wake. ConversationHandle is the conversation id parsed from the submit_result payload's conversation_id field — the daemon persists it back into state.Conversations["steward"] so the next wake resumes the same long-lived conversation (R1). Summary is the one-line audit summary; Marker is the terminal marker (always StewardMarkerOK on success). OperatorQuestion is the optional typed operator-question output (R16 / R17): when non-nil, the daemon parks the question's subject safely and persists the question as a steward_ledger operator-question row. Absent (nil) on a wake that asks nothing.

func ParseStewardResult

func ParseStewardResult(payload map[string]any) (StewardResult, error)

ParseStewardResult extracts the typed StewardResult from a submit_result payload. The terminal marker MUST be StewardMarkerOK; conversation_id and summary are optional string fields. NewStewardDispatcher parses directly through this (rather than lookupDispatchContract) to mirror the long-lived- conversation dispatchers, even though the steward now also carries a boundary SubmitResultContract entry the runner validates at submit time.

type StewardTask

type StewardTask struct {
	WakeTrigger        StewardWakeTrigger
	Brief              StewardWakeBrief
	ConversationHandle string
}

StewardTask is the input envelope for one steward wake. WakeTrigger names why the daemon woke the steward; Brief is the precomputed context; ConversationHandle is the durable conversation id read from the persisted state.Conversations["steward"] — empty on the first dispatch, the prior conversation id on every subsequent wake (including post-restart), which NewStewardDispatcher threads as RunOptions.ResumeConversationID (R1).

type StewardWakeBrief

type StewardWakeBrief struct {
	FlowMetricDeltas       string
	EventDigest            string
	BlockedCohort          string
	SuppressedSignalRollup string
}

StewardWakeBrief is the daemon-precomputed wake brief (R2): the four components every steward wake envelope carries — the flow-metric deltas, the ledger-class-grouped event digest, the blocked cohort, and the suppressed-signal rollup. Each field is rendered into a labeled slot by FormatStewardTask. Populated in production by RenderStewardWakeBrief from SOR-2660's deterministic brief generator (steward.Generator.Compute); an empty field renders as "(none)" so the envelope always carries all four slots even on a wake whose store is unwired (a test wrapper).

func RenderStewardWakeBrief

func RenderStewardWakeBrief(b steward.StewardBrief) StewardWakeBrief

RenderStewardWakeBrief is the bridge from SOR-2660's deterministic, daemon-precomputed steward.StewardBrief (the structured output of steward.Generator.Compute) into the four labeled string slots FormatStewardTask renders (R2). The daemon invokes it on every wake so the envelope carries the populated brief — without this bridge the steward (sole owner of the blocked cohort after the triager retirement) would wake to an all-"(none)" envelope and treat every wake as a quiet cycle. Each component renders to a compact human-readable block; an empty component renders to "" so its labeled slot falls through to indentBriefComponent's "(none)". The flow-metric and suppressed-signal slots always render their counter lines (even at zero), so a populated brief is observable in those slots regardless of queue contents.

type StewardWakeTrigger

type StewardWakeTrigger string

StewardWakeTrigger enumerates the three reasons the daemon wakes the steward (R1's wake model): a debounced reactive wake (invariant violations / escalations / watch alerts), a periodic tick, and an operator summon. The trigger rides the dispatch envelope so the steward knows why it was woken.

const (
	// StewardWakeTriggerReactive is a wake coalesced over a debounce window
	// from invariant violations / escalations / watch alerts.
	StewardWakeTriggerReactive StewardWakeTrigger = "reactive"
	// StewardWakeTriggerPeriodic is the per-cycle tick wake.
	StewardWakeTriggerPeriodic StewardWakeTrigger = "periodic"
	// StewardWakeTriggerSummon is an operator-initiated wake.
	StewardWakeTriggerSummon StewardWakeTrigger = "summon"
)

type SubmitResultExhaustedError

type SubmitResultExhaustedError struct {
	Role               string
	FinalError         string
	FinalAttempt       int
	RawPayload         string
	Synthesized        any
	SynthesizedOutcome string
}

SubmitResultExhaustedError is the typed error the runner returns when the per-(session, tool-call) attempt counter exhausts on Parse*Result rejections (MaxSubmitResultAttempts consecutive failures). Lives in the role package so dispatchers can detect it via errors.As without taking a clauderunner import (clauderunner already depends on role); the runner imports role and constructs values here, and the per-role dispatchers in dispatcher.go inspect the typed shape to route the carried `Synthesized` result through their normal return path.

FinalError is the last validator error verbatim — preserved as the rationale / failure_reason field of the synthesized escalation verdict so the operator-visible diagnostic loses no fidelity.

RawPayload is the first SynthesizedRawPayloadCap bytes of the rejected JSON payload (defense-in-depth — the events table doesn't carry the full body) so the post-hoc audit-event surface preserves a forensic snippet without re-reading the per-session attempts log.

Synthesized is the typed role-specific escalation struct the runner built via SynthesizeEscalation. Per-role dispatchers extract it via a type assertion and return it as their normal typed result — the daemon then routes through applyReviewerVerdict / applyMergeResolverResult / etc. exactly as if the agent had emitted the escalation directly. SynthesizedOutcome is the short human-readable descriptor of the escalation (e.g. "decision=escalate" for the reviewer); it lands on the submit_result_validation_synthesized_escalation audit event so dashboards can filter without re-decoding the typed struct.

func (*SubmitResultExhaustedError) Error

type TriagerAction

type TriagerAction struct {
	Action           string         `json:"action"`
	Confidence       string         `json:"confidence"`
	Rationale        string         `json:"rationale"`
	Args             map[string]any `json:"args"`
	SeenIssueVersion int64          `json:"-"`
}

TriagerAction is one row the triager emits in its actions array. The daemon-side executor router (a follow-up issue) reads Action + Confidence + Args to decide whether to apply, defer for review, or drop.

SeenIssueVersion is the SOR-1067 stale-payload race shield: the triager driver decorates each parsed action with the row-version it observed for `args["issue_key"]` during assemblePayload (i.e. at Tick start, before the LLM was called). The executor copies this value onto TriagerActionApplyPayload.SeenIssueVersion; the main-loop apply handler refuses to fire on a Version mismatch. The field is NOT part of the LLM's emitted JSON — it is daemon-side metadata attached after parsing, never marshaled back to the agent.

type TriagerResult

type TriagerResult struct {
	Status          TriagerStatus
	Detail          string
	SessionID       string
	ValidActions    []TriagerAction
	RejectedReasons []string
	RawJSON         []byte
}

TriagerResult is the typed result the dispatcher returns from one triager run. ValidActions is the slice of rows that survived schema validation; RejectedReasons is the slice of "actions[i]: <field>: <detail>" strings the daemon emits as `triager_invalid_output` events. RawJSON is the entire side-effect file's bytes — preserved on schema failure for forensics. SessionID is the clauderunner-minted subprocess id (threaded through payload["session_id"]) so the trail back to the source subprocess survives independently of marker detail collisions.

func ParseTriagerResult

func ParseTriagerResult(payload map[string]any, vctx TriagerValidatorContext) (TriagerResult, error)

ParseTriagerResult decodes the payload the runner returns from a triager subprocess. Validates each `actions[i]` row independently against the schema AND the daemon-supplied validator context (vctx); surviving rows go on ValidActions, rejected rows go on RejectedReasons. The marker tag is read from payload["marker"]; a missing or non-TRIAGER_* marker is a hard failure.

vctx may be zero — in that case per-row schema validation still runs, but the daemon-owned existence checks (evidence_event_ids against the per-tick payload, proposal_id against the proposals table) are skipped because there is no daemon-supplied state to compare against. The production dispatch path always threads a populated context (SOR-1150).

On TRIAGER_FAILED the parsed result carries Status + Detail (+ SessionID when present) and an empty ValidActions slice. The supervisor never executes actions from a failed result.

type TriagerStatus

type TriagerStatus int

TriagerStatus is the parsed terminal marker tag. The supervisor branches on this to decide what to record.

const (
	TriagerStatusUnknown TriagerStatus = iota
	TriagerStatusOK
	TriagerStatusFailed
)

type TriagerTask

type TriagerTask struct {
	WindowEventsFrom int64
	WindowEventsTo   int64
	PayloadJSON      string
	ValidatorContext TriagerValidatorContext
}

TriagerTask is the typed view of one per-task message handed to the triager conversation. PayloadJSON is the wrapped JSON object the agent parses out of the indented PAYLOAD: block; WindowEventsFrom / WindowEventsTo bound the events slice for sanity-checking inside the agent (parallel to RecognizerTask).

ValidatorContext carries the daemon-side metadata the emit validator reads to enforce per-row rejection rules that depend on runtime state not visible from the action JSON alone. FormatTriagerTask never emits these fields onto the wire — the agent doesn't see them — but the dispatcher threads them into ParseTriagerResult on the way back. Mirrors RecognizerTask.ValidatorContext (SOR-1150).

type TriagerValidatorContext

type TriagerValidatorContext struct {
	EvidenceEventIDs  map[int64]bool
	OrphanProposalIDs map[int64]bool
}

TriagerValidatorContext is the daemon-supplied side-channel the triager's emit validator reads to hard-reject per-row args that reference daemon-owned state. Mirrors RecognizerValidatorContext (SOR-1087): the daemon assembles it from the same per-tick payload the agent reads, FormatTriagerTask never emits it onto the wire, and the dispatcher threads it into ParseTriagerResult on the way back.

EvidenceEventIDs is the closed set of event ids surfaced in this tick's payload `events` slice — the only ids an action may cite in escalate_to_recognizer's `evidence_event_ids` array. When non-nil the validator hard-rejects any cited id absent from the set (fabricated / hallucinated evidence, or an id recalled from a prior turn's payload). A nil map disables the check (tests that don't seed a per-tick context).

OrphanProposalIDs is the set of pending-outcome proposal ids the assemble step read from the proposals table — the canonical source cascade_abandon_orphan_proposal's `proposal_id` is verified against. When non-nil the validator hard-rejects a `proposal_id` absent from the set. A nil map disables the check.

A zero-value TriagerValidatorContext (both maps nil) disables every existence check; per-row schema validation still runs. The production dispatch path always threads a populated context.

func DecodeTriagerValidatorCtx

func DecodeTriagerValidatorCtx(ctxJSON []byte) TriagerValidatorContext

DecodeTriagerValidatorCtx decodes the triager validator context from the wire bytes. An empty or malformed context yields the zero value.

type WedgeInvestigatorResult

type WedgeInvestigatorResult struct {
	// Marker is the terminal marker (always WedgeInvestigatorMarkerOK on success).
	Marker string
	// SessionID is the subprocess id echoed from the task envelope.
	SessionID string
	// RootDiagnosis is the structured root cause of the wedge with evidence
	// cited from the dossier, live issuestore, worktree, or gate output.
	RootDiagnosis string
	// RecoveryRecommendation is the concrete operator action or daemon fix that
	// would unblock the subject.
	RecoveryRecommendation string
	// FileableStructuralRoot is true iff the root cause is a structural
	// daemon/product defect worth filing as a sorcerer-self planning or
	// implementation issue.
	FileableStructuralRoot bool
}

WedgeInvestigatorResult is the typed view of the wedge_investigator role's submit_result payload. All three diagnosis fields are required: a payload missing any of them is a structural parse error (there is no failed/ok status enum — the role either produces a complete diagnosis or the dispatch is a structural reject). SessionID is the clauderunner-minted subprocess id (threaded through payload["session_id"]) so the daemon's audit event can point back at the source subprocess.

func ParseWedgeInvestigatorResult

func ParseWedgeInvestigatorResult(payload map[string]any) (WedgeInvestigatorResult, error)

ParseWedgeInvestigatorResult decodes the payload the runner returns from a wedge_investigator subprocess. The marker tag is read from payload["marker"]; a missing or non-WEDGE_INVESTIGATOR_OK marker is a hard failure. The three diagnosis fields are each REQUIRED — a structural violation rejects the result as a whole rather than returning a partial verdict:

  • root_diagnosis missing or blank (whitespace-only) → error.
  • recovery_recommendation missing or blank → error.
  • fileable_structural_root missing (key absent) or not a bool → error. The key must be PRESENT and a bool; an absent key is distinct from `false` (the zero value), so a forgotten verdict cannot silently parse as a non-fileable one.

type WedgeInvestigatorTask

type WedgeInvestigatorTask struct {
	// IssueKey is the blocked-cohort issue the investigator deep-dives.
	IssueKey string
	// State is the issue's current (blocked) state.
	State string
	// HoursInState is how long the issue has been parked in State.
	HoursInState int
	// BlockedReason is the recorded blocked reason / escalation rule, when present.
	BlockedReason string
	// DossierText is the pre-rendered diagnostic dossier — the live facts root
	// diagnosis needs (captured verifier/gate output, body-spec_id-vs-DB state,
	// worktree footprint + file-placement, plan-branch tree delta, prior steward
	// recovery attempts). Rendered by the steward-side caller from the daemon's
	// steward.Dossier; emitted into the task envelope as an indented block.
	DossierText string
}

WedgeInvestigatorTask is the input envelope for one wedge_investigator dispatch. The fields name the novel wedge under investigation; DossierText is the pre-rendered per-wedge diagnostic dossier (the steward brief's renderDossierLines output for this row) the agent reads alongside the live issuestore / worktree / git state its read tools reach. The struct carries the dossier as a string rather than importing the steward package: the steward-side wiring renders steward.Dossier into these lines before constructing the task.

Directories

Path Synopsis
discoverytypedproptest
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the typed discovery submit boundaries (R1 / R3 / R4 / R5 / R8, plus SOR-2358's R6 — the implementer's structured predicted footprint).
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the typed discovery submit boundaries (R1 / R3 / R4 / R5 / R8, plus SOR-2358's R6 — the implementer's structured predicted footprint).
Package feedback defines the typed implementer-feedback envelope, the Source enum that names every refer-back originator, the per-source diagnostic payload types, and the slot constants that the Required matrix in required.go keys against.
Package feedback defines the typed implementer-feedback envelope, the Source enum that names every refer-back originator, the per-source diagnostic payload types, and the slot constants that the Required matrix in required.go keys against.
discoveryinputproptest/gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for SOR-2359 R7.
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for SOR-2359 R7.
lint
Package lint holds the standalone go/analysis analyzer that gates the feedback envelope's Source-to-slot Required matrix.
Package lint holds the standalone go/analysis analyzer that gates the feedback envelope's Source-to-slot Required matrix.
lint/cmd/feedbackmatrixlint command
Command feedbackmatrixlint is the standalone driver for the feedbackmatrix static analyzer.
Command feedbackmatrixlint is the standalone driver for the feedbackmatrix static analyzer.
specidauthorproptest
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (the worktree-root proptest_stubs_test.go, the daemon's per-issue profile-sourced prop-test stub basename) for SOR-2479 R12 — the typed spec_drafter draft/amend payload with a daemon-stamped spec_id.
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (the worktree-root proptest_stubs_test.go, the daemon's per-issue profile-sourced prop-test stub basename) for SOR-2479 R12 — the typed spec_drafter draft/amend payload with a daemon-stamped spec_id.
submitresultcompletenessproptest
gen
Package gen is the project-local generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for SOR-2521's cited requirements of the submit_result-contract spec (SOR-2479 umbrella): R4 / R5 / R6 over the agentcli.SubmitResultContracts registry, and R14 over the spec/dsl canonical serializer.
Package gen is the project-local generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for SOR-2521's cited requirements of the submit_result-contract spec (SOR-2479 umbrella): R4 / R5 / R6 over the agentcli.SubmitResultContracts registry, and R14 over the spec/dsl canonical serializer.
submitresultcontractproptest
gen
Package gen is the project-local generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for R9 of the submit_result-contract spec (SOR-2479 umbrella; the dispatcher seam is SOR-2512).
Package gen is the project-local generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for R9 of the submit_result-contract spec (SOR-2479 umbrella; the dispatcher seam is SOR-2512).
submitresultcontractrequiredproptest
gen
Package gen is the project-local generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for R11 of the submit_result-contract spec (SOR-2479 umbrella; the deny-by-default contract-required analyzer is SOR-2517).
Package gen is the project-local generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for R11 of the submit_result-contract spec (SOR-2479 umbrella; the deny-by-default contract-required analyzer is SOR-2517).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL