32 KiB
ADR-0231: First Candidate Operator Boundary
Status: Proposed
Date: 2026-06-23
Scope: Kernel diagnostics, candidate attempt construction, residual-gated practice loop
Depends on:
- ADR-0225 ContractResidual read model
- ADR-0226 Residual-Gated Practice Loop v1
- ADR-0227 ComputeBudgetPolicy envelope
- ADR-0228 GeometricSearchRun envelope
- ADR-0229 Contract/Proof Replay Adapter boundary
- ADR-0230 SealedPracticeTrace boundary
- PR #874 inert SealedPracticeTrace shell
1. Summary
This ADR defines the first deterministic candidate-operator boundary for the Residual-Gated Practice Loop. The boundary authorizes one narrow operator to produce one diagnostic candidate attempt from one eligible typed residual context after the already-ratified diagnostic chain has produced gate, budget, run, replay, and sealing evidence.
The current spine is:
ContractAssessment
-> ContractResidual
-> SearchGateDecision
-> ComputeBudgetDecision
-> GeometricSearchRun
-> Contract/Proof Replay Adapter
-> SealedPracticeTrace
The new boundary sits between an initialized GeometricSearchRun identity and
the Contract/Proof Replay Adapter:
GeometricSearchRun identity
-> CandidateOperator
-> CandidateAttempt-compatible candidate reconstruction
-> Contract/Proof Replay Adapter
-> SealedPracticeTrace
The boundary is intentionally not open-ended search. It permits:
one deterministic operator
one eligible residual context
one attempt index
one candidate reconstruction
one diagnostic result or refusal
It does not permit:
truth
answer production
repair
ranking
selection
serving
mutation
promotion
Workbench behavior
stochastic generation
open-ended search
The load-bearing invariant is:
Candidate operators propose candidate attempts only.
A candidate operator has no truth authority.
A candidate operator has no answer authority.
A candidate operator has no repair authority.
A candidate operator has no selection/ranking authority.
A candidate operator has no mutation/promotion authority.
Every produced candidate must be replay-classified and sealed before it can
become evidence for any future reviewed promotion or display.
This ADR is documentation only. It adds no candidate-operator implementation, no search execution, no replay execution, no sealed-trace execution, no Workbench behavior, no runtime/serving behavior, and no mutation path.
2. Why this exists
ADR-0228 deliberately left GeometricSearchRun inert. Its v1 implementation can
validate gate and budget evidence, record a closed operator-set identity, and
emit an empty exhausted_no_candidate run when the operator set is empty.
ADR-0229 and ADR-0230 then define how any future candidate must be replay-classified
and sealed.
The missing bridge is the first lawful forward operator:
typed residual obstruction
-> deterministic candidate reconstruction
-> replay and sealing obligations
Without a distinct candidate-operator boundary, a future implementation could collapse residual diagnosis into repair, treat budget as permission to search until success, let candidate construction masquerade as contract closure, or write a candidate directly into serving, Workbench, teaching, packs, reports, or policy.
The intrinsic state space is a typed residual field with one bounded forward operator and a required corrective conjugate. The operator may propose one candidate reconstruction. Contract/proof replay and sealing remain downstream corrective boundaries.
3. Architectural directions considered
3.1 Open operator search
A general search engine could traverse a table of operators, expand multiple candidates, adapt based on intermediate failures, and rank or select a best candidate. This would collapse the first operator boundary into an unbounded search policy and would make ordering, budget use, and authority difficult to audit. Rejected.
3.2 Repair-in-place role binding
The operator could patch the original ProblemFrame, original
ContractAssessment, residuals, gate, budget, or run. This would destroy
upstream immutability and make candidate construction indistinguishable from
repair authority. Rejected.
3.3 Replay-integrated candidate producer
The operator could construct a candidate and immediately run contract/proof replay, returning only replayed successes. This would combine proposal and correction in one module and allow candidate-generation failure to masquerade as semantic refusal. Rejected.
3.4 Immutable single-operator reconstruction boundary
The operator consumes already-produced residual, gate, budget, and run identities; validates a closed operator-set identity; emits either one candidate reconstruction/result or one refusal; and grants no downstream authority. Replay, sealing, display, promotion, serving, and mutation remain separate boundaries. Selected.
This selected shape makes illegal authority transitions visible in the output type and keeps the first candidate-producing PR small enough to test deterministically.
4. Decision
The v1 policy identity is conceptually:
CANDIDATE_OPERATOR_POLICY_VERSION = "candidate_operator.v1"
The v1 operator-set version is conceptually:
CANDIDATE_OPERATOR_SET_VERSION = "candidate_operators.v1"
The public conceptual outcome is:
CandidateOperatorOutcome = CandidateOperatorResult | CandidateOperatorRefusal
CandidateOperatorResult exists only when one deterministic candidate
reconstruction was lawfully produced from an eligible residual under the closed
operator policy, matching gate, matching budget, and matching run identity.
CandidateOperatorRefusal exists when the operator cannot lawfully produce a
candidate because input, policy, residual kind, residual code, gate, budget,
run, evidence-span, or operator-set validation failed. A refusal is not a
partial candidate and must not masquerade as a replay result.
The operator may decide only:
whether one deterministic candidate reconstruction can be produced from an
eligible residual under a bounded run/budget/operator policy
It may not decide:
truth
answerability
proof closure
serving
promotion
learning
global uniqueness
candidate selection
ranking
fallback repair
mutation
Candidate closure remains exclusively downstream:
CandidateAttempt
-> Contract/Proof Replay Adapter
-> SealedPracticeTrace
-> future read-only display / reviewed promotion path
5. Position in the loop
Dependency and authority remain one-way:
ContractAssessment
-> ContractResidual
-> SearchGateDecision
-> ComputeBudgetDecision
-> GeometricSearchRun
-> CandidateOperator
-> CandidateAttempt-compatible reconstruction
-> Contract/Proof Replay Adapter
-> SealedPracticeTrace
-> future Workbench read-only projection
-> future reviewed promotion path
There is no reverse dependency. In particular:
- the operator does not create, mutate, or replace a
GeometricSearchRun; - residual, gate, budget, run, replay-adapter, and sealed-trace modules do not gain imports from this operator boundary unless a later implementation PR explicitly authorizes that direction;
- the operator emits candidate-attempt evidence only, not replay evidence;
- the replay adapter validates the candidate independently before calling any contract/proof authority; and
- the sealed trace validates replay/run/attempt identity before any future evidence can be displayed or reviewed.
The fact that this ADR follows the sealed-trace PR sequence does not place the
runtime candidate operator after SealedPracticeTrace. It defines the next
architectural boundary after the sealed-trace shell exists, while the operator's
runtime position remains upstream of replay and sealing.
6. First operator selection
The first authorized operator is:
operator_family = "residual_missing_role"
operator_name = "missing_role_candidate"
operator_version = "missing_role_candidate.v1"
The operator is selected because the repo already has the following closed vocabulary:
ResidualKind.MISSING_ROLE -> "missing_role"
SearchGate reason_code -> "eligible_missing_role"
ComputeBudget reason_code -> "budget_allowed_missing_role"
V1 further narrows the operator to the smallest safe shell:
allowed_residual_kinds = ("missing_role",)
allowed_candidate_organs = ("unary_delta_transition",)
allowed_residual_codes = ("direction_unbound",)
max_attempts_per_run = 1
budget_charge = BudgetCharge(candidates=1, steps=1)
depth = 1
serial only
This is intentionally narrower than all missing-role cases. The selected
residual code, direction_unbound, can be constrained to an organ-specific
typed rule: the operator may emit a candidate only when the supplied immutable
ProblemFrame or validated projection already contains exactly one
GroundedUnaryDeltaCue whose typed direction value and exact cue span are
available and whose span is already justified by residual/problem-frame
evidence. The operator may then propose a role-binding delta from that existing
typed cue into the missing direction role.
If the typed cue is absent, ambiguous, not source-grounded, not already covered
by lawful evidence spans, or inconsistent with the residual context, the
operator returns CandidateOperatorRefusal. It must not read source text again,
infer a direction from prose, synthesize a cue, repair a relation, or search for
alternative bindings.
Other ResidualKind.MISSING_ROLE residual codes remain refusals until
separately authorized. Other candidate organs remain refusals until separately
authorized. This preserves the first implementation PR as a small deterministic
shell rather than a general role-filling engine.
7. Inputs
7.1 CandidateOperatorPolicy
The conceptual policy schema is:
@dataclass(frozen=True, slots=True)
class CandidateOperatorPolicy:
operator_policy_version: str
operator_family: str
operator_name: str
operator_version: str
allowed_residual_kinds: tuple[str, ...]
allowed_candidate_organs: tuple[str, ...]
max_attempts_per_run: int
determinism_requirements: tuple[str, ...]
forbidden_authority_paths: tuple[str, ...]
An implementation may add policy fields only if they are deterministic, static, and part of the canonical operator-set table. For the selected first operator, the closed policy should additionally carry the residual-code constraint and fixed budget charge:
allowed_residual_codes = ("direction_unbound",)
budget_charge = {"candidates": 1, "steps": 1}
depth = 1
max_parallelism = 1
7.2 CandidateOperatorInput
The conceptual operator input is:
@dataclass(frozen=True, slots=True)
class CandidateOperatorInput:
input_digest: str
operator_policy_version: str
operator_name: str
operator_version: str
problem_frame_digest: str
original_contract_assessment_id: str
residual_id: str
residual_kind: str
search_gate_decision_id: str
compute_budget_id: str
geometric_search_run_id: str
operator_set_id: str
operator_set_version: str
attempt_index: int
schema_versions: tuple[tuple[str, str], ...]
policy_versions: tuple[tuple[str, str], ...]
The input is accompanied by immutable values or validated identity-bearing projections for:
- the original
ProblemFrame; - the original refused
ContractAssessment; - the selected
ContractResidual; - the
SearchGateDecision; - the
ComputeBudgetDecision; - the
GeometricSearchRunor run identity required by the current shell; and - the closed operator-set table.
Identities alone are not enough when the operator must inspect typed state. Typed state alone is not enough when identities must bind the replay chain. The implementation must have enough immutable content to recompute the digests it depends on and enough typed content to apply the deterministic operator rule.
The input cannot contain an answer, preferred candidate, ranked list, repair command, Workbench action, mutation target, hidden source text, callable, model handle, path, plugin name, environment value, or runtime/session state.
7.3 Required preconditions
Before constructing a candidate, the operator must validate:
operator_policy_version == "candidate_operator.v1".operator_name == "missing_role_candidate".operator_version == "missing_role_candidate.v1".residual_kind == "missing_role".candidate_organ == "unary_delta_transition"on the residual/gate chain.residual_code == "direction_unbound"for v1.SearchGateDecision.status == SearchGateStatus.ELIGIBLE.SearchGateDecision.reason_code == "eligible_missing_role".ComputeBudgetDecision.status == ComputeBudgetStatus.BUDGET_ALLOWED.ComputeBudgetDecision.reason_code == "budget_allowed_missing_role".ComputeBudgetDecision.gate_decision_id == SearchGateDecision.decision_id.GeometricSearchRun.gate_decision_id == SearchGateDecision.decision_id.GeometricSearchRun.budget_id == ComputeBudgetDecision.budget_id.GeometricSearchRun.operator_set_id == operator_set_id.GeometricSearchRun.operator_set_version == operator_set_version.attempt_index >= 0.attempt_index < ComputeBudgetDecision.max_candidates.attempt_index < CandidateOperatorPolicy.max_attempts_per_run.- The fixed operator charge fits within
max_candidates,max_depth, andmax_steps. ComputeBudgetDecision.max_parallelism == 1.- Evidence spans are valid, exact, ordered, and already justified by the residual/problem-frame records.
schema_versionsandpolicy_versionscontain unique names in ascending lexical order.
Any failed validation returns CandidateOperatorRefusal; no partial candidate
record is emitted.
8. Outputs
8.1 CandidateReconstruction
The conceptual candidate reconstruction is:
@dataclass(frozen=True, slots=True)
class CandidateReconstruction:
candidate_digest: str
candidate_reconstruction_digest: str
candidate_organ: str
candidate_payload: object
evidence_spans: tuple[SourceSpan, ...]
operator_name: str
operator_version: str
operator_provenance: tuple[tuple[str, str], ...]
source_residual_id: str
problem_frame_digest: str
original_contract_assessment_id: str
For missing_role_candidate.v1, candidate_payload is an organ-specific,
deterministic reconstruction envelope. It may identify a single role-binding
delta such as:
kind = "role_binding_delta"
candidate_organ = "unary_delta_transition"
relation_type = "state_change.unary_delta"
role = "direction"
source = "GroundedUnaryDeltaCue.direction"
This example is a schema obligation, not permission to invent a new parser or
generic patch engine. The payload must be sufficient for downstream replay to
reconstruct one exact candidate value or to refuse. It must not mutate the
original ProblemFrame or rewrite source evidence.
8.2 CandidateOperatorResult
The conceptual operator result is:
@dataclass(frozen=True, slots=True)
class CandidateOperatorResult:
operator_result_id: str
operator_policy_version: str
input_digest: str
geometric_search_run_id: str
attempt_id: str
attempt_index: int
candidate_digest: str
candidate_reconstruction_digest: str
candidate_organ: str
operator_name: str
operator_version: str
reason_codes: tuple[str, ...]
evidence_spans: tuple[SourceSpan, ...]
explanation: str
The result is diagnostic evidence that candidate construction happened. It is not replay evidence, proof evidence, answer evidence, or promotion evidence.
8.3 CandidateOperatorRefusal
The conceptual refusal is:
@dataclass(frozen=True, slots=True)
class CandidateOperatorRefusal:
operator_refusal_id: str
operator_policy_version: str
input_digest: str | None
geometric_search_run_id: str | None
residual_id: str | None
operator_name: str
reason_codes: tuple[str, ...]
explanation: str
Malformed or unauthorized candidate construction must return a refusal, not a partial candidate. Optional identities are populated only when they can be validated without guessing.
8.4 Forbidden output fields
No candidate-operator output may contain:
answer
final_answer
served_output
proof
verdict
promotion
mutation
teaching_update
pack_update
policy_update
identity_update
workbench_state
runtime_effect
confidence
score
rank
priority
selected
selected_candidate
best
best_candidate
serving_allowed
runnable
Generated prose is explanatory only and excluded from identity.
9. Candidate identity and reconstruction identity
The identity split from ADR-0229 is preserved:
candidate_digest
-> canonical digest of candidate content to be replayed
candidate_reconstruction_digest
-> canonical digest of the full reconstruction envelope:
run_id
attempt_id
attempt_index
operator_set_id
operator_name
operator_version
residual identity
candidate_digest
evidence-span identity
schema/policy versions
candidate_digest hashes candidate content only. For v1 this includes, at
minimum:
original problem_frame_digest
candidate organ
organ-specific candidate payload
exact ordered evidence spans
candidate_digest excludes:
attempt_id
attempt_index
operator_set_id
operator_name
operator_version
explanation
runtime identity
candidate_reconstruction_digest self-seals the complete reconstruction
envelope with its own digest field blanked. It includes the validated
candidate_digest, run/attempt/operator provenance, residual identity, exact
evidence-span identity, and schema/policy versions. It excludes explanation.
The two digests must not be collapsed. A future implementation must include a test proving they differ for a valid candidate reconstruction.
10. Operator-set identity
operator_set_id is the canonical digest of the closed allowed operator table.
The table must include, for every operator row:
operator_name
operator_version
allowed_residual_kinds
allowed_candidate_organs
max_attempts_per_run
schema_versions
policy_versions
The selected v1 table contains exactly one row:
| operator_name | operator_version | allowed_residual_kinds | allowed_candidate_organs | max_attempts_per_run |
|---|---|---|---|---|
missing_role_candidate |
missing_role_candidate.v1 |
missing_role |
unary_delta_transition |
1 |
The table may carry additional static constraints such as:
allowed_residual_codes = ("direction_unbound",)
budget_charge = {"candidates": 1, "steps": 1}
depth = 1
max_parallelism = 1
Those constraints participate in operator_set_id if present in the
implementation. The table must be static, closed, deterministic, and versioned.
Forbidden operator availability mechanisms:
dynamic plugin discovery
filesystem scanning
entry-point enumeration
environment-variable configuration
model/runtime-dependent availability
session-memory availability
network discovery
Workbench configuration
Changing the table changes operator_set_id and therefore creates a distinct
run/reconstruction identity. No hidden fallback operator is permitted.
11. Budget and run constraints
The candidate operator obeys the already-assigned ComputeBudgetDecision. It
does not allocate budget.
The operator may execute only when:
ComputeBudgetDecision.status == BUDGET_ALLOWED
SearchGateDecision.status == ELIGIBLE
attempt_index < ComputeBudgetDecision.max_candidates
attempt_index < CandidateOperatorPolicy.max_attempts_per_run
fixed candidate charge fits max_candidates/max_depth/max_steps
ComputeBudgetDecision.max_parallelism == 1
GeometricSearchRun.gate_decision_id == SearchGateDecision.decision_id
GeometricSearchRun.budget_id == ComputeBudgetDecision.budget_id
GeometricSearchRun.operator_set_id == operator_set_id
Budget exhaustion never implies correctness. Operator exhaustion never implies
closure. A budget refusal, zero budget, unassessable budget, run mismatch, or
attempt index beyond the ceiling returns CandidateOperatorRefusal.
The operator is not open-ended search. It must not:
loop over arbitrary operators
recursively expand candidates
execute in parallel
adaptively choose operators
score operators
rank candidates
select candidates
mutate search trees
invoke fallback operators
retry until success
refund or inflate budget
Any future multi-operator, multi-attempt, recursive, parallel, or adaptive search requires a separate ADR.
12. Replay and sealing obligations
Every produced candidate must be routed through:
Contract/Proof Replay Adapter
-> SealedPracticeTrace
A CandidateOperatorResult alone is not evidence of correctness. It cannot be
displayed as a solution, promoted, served, or used for learning unless a later
reviewed path consumes a sealed trace that validates the full chain:
original refused ContractAssessment
-> ContractResidual
-> SearchGateDecision
-> ComputeBudgetDecision
-> GeometricSearchRun
-> CandidateAttempt-compatible record
-> CandidateReconstruction
-> ReplayAdapterResult / ReplayAdapterRefusal
-> SealedPracticeTrace
The operator may not run contract replay, proof replay, replay-adapter classification, or sealed-trace construction. It may only emit the diagnostic candidate attempt evidence that those downstream stages must validate.
13. Evidence-span preservation
The operator must:
preserve ordered evidence spans
preserve duplicate spans
use only source spans already justified by residual/problem-frame records
not synthesize spans
not dedupe spans
not sort spans independently
fail closed on malformed spans
For missing_role_candidate.v1, candidate evidence spans are copied from the
residual/problem-frame/cue evidence that authorizes the role-binding delta. If
the required span is absent or malformed, the operator refuses.
Evidence spans that participate in candidate_digest,
candidate_reconstruction_digest, attempt_id, or operator_result_id must
change those identities when reordered. Duplicate spans remain distinct entries
when upstream records preserved them as distinct entries.
The operator must not reread raw source text to recover missing provenance.
14. Determinism and canonical identity
All load-bearing identities use canonical JSON:
json.dumps(
payload,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
)
The serialized string is encoded as UTF-8 and hashed with SHA-256. The digest is the full lowercase hexadecimal encoding.
The following IDs are load-bearing:
operator_set_id
CandidateOperatorInput.input_digest
candidate_digest
candidate_reconstruction_digest
attempt_id
operator_result_id
operator_refusal_id
CandidateOperatorInput.input_digest hashes the structural input fields other
than input_digest itself:
operator_policy_version
operator_name
operator_version
problem_frame_digest
original_contract_assessment_id
residual_id
residual_kind
search_gate_decision_id
compute_budget_id
geometric_search_run_id
operator_set_id
operator_set_version
attempt_index
canonical schema_versions
canonical policy_versions
attempt_id, when a CandidateAttempt-compatible record is produced, hashes:
attempt_index
parent_attempt_id
operator_id / operator_name
operator_version
input_digest
candidate_digest
budget_charge
depth
step_index
replay_status
replay_blockers
exact ordered evidence_spans
It uses the existing CandidateAttempt vocabulary:
replay_status = CandidateReplayStatus.REPLAY_PENDING
replay_blockers = ()
budget_charge = BudgetCharge(candidates=1, steps=1)
IDs must exclude:
explanation
wall-clock time
timestamps
random values
UUIDs
environment variables
hostname
OS details
CI metadata
file paths
generated prose
memory addresses
thread/process identifiers
filesystem order
hash-map iteration order unless canonicalized
user identity
model identity
machine identity
Changing explanation must not change any ID.
15. Authority boundary
The candidate operator may decide only whether one deterministic candidate reconstruction can be produced under the selected operator policy.
| Concern | Authority owner | Candidate operator authority |
|---|---|---|
| Original organ runnable/refused state | Existing ContractAssessment organ |
None |
| Residual projection | ContractResidual |
None |
| Search eligibility | SearchGateDecision |
None |
| Compute allocation | ComputeBudgetDecision |
None |
| Run envelope identity | GeometricSearchRun |
None beyond consuming identity |
| Candidate reconstruction | This ADR's candidate operator | One deterministic proposal only |
| Contract/proof replay | Contract/Proof Replay Adapter | None |
| Trace integrity | SealedPracticeTrace |
None |
| Answer production/selection | Future separately authorized result stage | None |
| Durable promotion | Existing reviewed/certificate paths | None |
| Workbench | Future read-only projection | None |
The operator has no authority to:
decide truth
decide answerability
close contracts
close proofs
run proof replay
run contract replay
seal traces
select answers
rank candidates
repair source records
serve output
mutate artifacts
promote findings
edit packs
edit teaching data
edit policy
edit identity
edit eval reports
write Workbench state
No candidate-operator result changes durable epistemic standing. Source kind
(operator, search, replay, or practice) grants no promotion authority.
16. Forbidden imports/calls/effects
16.1 Allowed dependency surface
A future implementation may import only:
- standard-library immutable value, enum, canonical JSON, and SHA-256 facilities;
SourceSpanand existing identity-bearingProblemFramevalues or validated projections;ContractAssessmentas a value type;ContractResidualandResidualKindas value types;SearchGateDecisionandSearchGateStatusas value types;ComputeBudgetDecisionandComputeBudgetStatusas value types;GeometricSearchRun,CandidateAttempt,BudgetCharge, andCandidateReplayStatusas value types; and- local candidate-operator immutable value types and static operator-set manifests.
The implementation consumes upstream records for identity binding and deterministic candidate reconstruction only. It does not invoke upstream producers or downstream closure authorities.
16.2 Forbidden calls and effects
The future implementation must not import or call:
runtime
serving
Workbench
teaching/proposal mutation
pack/policy/identity mutation
eval/report mutation
Vault/recall mutation
filesystem writes
network I/O
subprocess
time/datetime/random/uuid/env/hostname/path identity
dynamic import/plugin discovery
external model/tool invocation
LLM generation
embedding/ANN/cosine/semantic-rank modules
determine answer production
contract/proof replay execution
sealed trace writing
It also must not call:
candidate generation outside the closed operator
open-ended search execution
repair
answer realization
field or algebra mutation
thread or process scheduling APIs
No hidden fallback is permitted. Missing upstream evidence, unsupported policy, identity mismatch, budget denial, or malformed spans produce a typed operator refusal.
17. Failure modes and fail-closed behavior
| Failure | Required outcome |
|---|---|
| Unsupported operator policy | CandidateOperatorRefusal |
| Unsupported operator name/version | CandidateOperatorRefusal |
| Unsupported residual kind | CandidateOperatorRefusal |
| Unsupported residual code | CandidateOperatorRefusal |
| Unsupported candidate organ | CandidateOperatorRefusal |
Ineligible SearchGateDecision |
CandidateOperatorRefusal |
Non-allowed ComputeBudgetDecision |
CandidateOperatorRefusal |
| Run identity mismatch | CandidateOperatorRefusal |
| Budget/run mismatch | CandidateOperatorRefusal |
| Gate/budget mismatch | CandidateOperatorRefusal |
| Missing residual fields required by operator | CandidateOperatorRefusal |
| Missing or ambiguous typed cue required by the v1 operator | CandidateOperatorRefusal |
| Malformed evidence spans | CandidateOperatorRefusal |
| Attempt index exceeds budget | CandidateOperatorRefusal |
Attempt index exceeds max_attempts_per_run |
CandidateOperatorRefusal |
| Fixed budget charge exceeds structural budget | CandidateOperatorRefusal |
max_parallelism != 1 |
CandidateOperatorRefusal |
| Operator-set mismatch | CandidateOperatorRefusal |
| Candidate reconstruction cannot reproduce canonical identity | CandidateOperatorRefusal |
No failure may become:
partial answer
best guess
soft proof
ranked candidate
serving fallback
promotion
teaching mutation
Unknown == False
A refusal does not consume a candidate attempt. A result consumes exactly the
declared fixed charge and still remains replay_pending.
18. Test obligations for future implementation PR
The future implementation must add executing tests that meaningfully fail for each prohibited state:
- Public API exports are exact.
- Operator-set identity is deterministic.
- Candidate operator input identity is deterministic.
- Valid residual/gate/budget/run context produces one candidate operator result.
- Candidate digest differs from candidate reconstruction digest.
- Candidate reconstruction binds run, attempt, residual, operator set, operator name/version, and candidate digest.
- Unsupported residual kind refuses.
- Ineligible gate refuses.
- Blocked/zero/unassessable budget refuses.
- Attempt index beyond
max_candidatesrefuses. - Run/gate/budget mismatch refuses.
- Operator-set mismatch refuses.
- Missing required residual field refuses.
- Malformed evidence span refuses.
- Duplicate evidence spans are preserved.
- Evidence span reorder changes identity if spans participate in identity.
- Explanation changes do not affect IDs.
- No
answer,final_answer, orserved_outputfields exist. - No
score,rank,priority,selected, orbestfields exist. - No candidate generation outside the closed operator, search, repair, replay, seal, serve, or mutation call is reachable.
- No dynamic plugin, filesystem, network, time, random, environment, UUID, hostname, or path identity is reachable.
- No reverse dependency from upstream modules into the operator module unless explicitly intended by the implementation PR.
- Candidate result alone cannot be represented as closure.
- Candidate result must be replayable through ADR-0229 and sealable through ADR-0230.
- Focused tests and the repository smoke lane pass.
Additional required controls:
- tests independently recompute operator-set, input, candidate, reconstruction, attempt, result, and refusal hashes rather than calling the implementation's private hashing helper;
- static coupling tests parse the operator module and enforce the allowed import/call surface;
- tests prove only
missing_role_candidate.v1is present in the v1 operator set; - tests prove residual codes other than
direction_unboundrefuse in v1; and - tests prove the produced
CandidateAttempt-compatible record remainsCandidateReplayStatus.REPLAY_PENDING.
19. Authorized next PR
This ADR authorizes exactly one next implementation PR:
feat(kernel): implement missing-role candidate operator shell
That PR may only:
- add candidate-operator frozen dataclasses, closed enums/helpers, canonical hashing helpers, and a discriminated outcome type;
- add a static one-row operator-set identity for
missing_role_candidate.v1; - implement exactly one deterministic operator shell for
ResidualKind.MISSING_ROLE,candidate_organ="unary_delta_transition", andresidual_code="direction_unbound"; - emit one
CandidateAttempt-compatible candidate reconstruction/result/refusal; - keep
CandidateReplayStatus.REPLAY_PENDINGon produced attempts; - validate residual/gate/budget/run/operator-set identity; and
- add tests only for the boundary behavior and isolation obligations in Section 18.
That PR explicitly excludes:
multi-operator search
recursive search
parallel search
candidate ranking
candidate selection
repair
contract/proof replay execution
sealed trace execution
answer production
Workbench
runtime/serving
teaching/proposal/report/eval mutation
pack/policy/identity mutation
promotion
filesystem persistence
artifact writing
new proof engine
No other implementation PR is authorized by this ADR. Broader missing-role coverage, missing-relation candidates, missing-proposal candidates, target binding, multi-attempt search, replay execution, sealed practice execution, Workbench display, answer production, serving, evaluation, and promotion each remain separately gated.