feat(ADAPT-2): wire route authority into production dispatch path
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / lint (push) Failing after 32s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / canary (push) Successful in 41s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Failing after 1m1s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / required-checks (push) Successful in 1s

The router had zero production callers — architecturally implemented but
cognitively dead. This makes it operational (handoff 2026-09-10 §1/§20.A–C):

- ISSUE stage (production dispatch entry) now classifies route before the
  fixed-depth chain; LOCAL_FIX terminally routes out to the local
  implementation/verification lane (ROUTED_LOCAL_FIX, tree records route)
- Sufficiency inputs replace substring inference as primary evidence:
  5 questions with traceable provenance; non-establishable facts recorded
  as UNKNOWN and forced upward (never manufactured certainty)
- Mechanism-uncertainty language (performance/timing/concurrency) makes
  local_authority honest — measurement questions are not source-readable
- Escalation/downgrade/FINAL_ROUTE are persisted events in the same
  append-only authority (route.jsonl); transition identity fields
  (previous_route/trigger/evidence) mandatory
- Deep-research floor: narrow, objectively detectable triggers;
  FloorViolation on efficiency-driven downgrade bypass
- Legacy boolean API preserved as marked compat shim (legacy semantics)
- Canaries: local-route behavioral effect, research floor entry,
  escalation transitions, floor non-bypass, full audit trail
  reconstruction (tests/test_adaptive_router_wiring.py)
- Suite: 1178 passed (3 pre-existing env failures excluded, reproduced
  on clean master)

Refs #291, #324 (checkpoint defect, unrelated)
This commit is contained in:
Nick committed 2026-09-10 15:29:11 +01:00
1 parent e02ebb484e
commit 69a233cf63
5 files changed
+657 -120

No files matched your search

+250 -118
View File
@@ -1,60 +1,60 @@
#!/usr/bin/env python3
"""adaptive_router.py — canonical adaptive route authority (ADAPT-2, #291).
"""adaptive_router.py — canonical adaptive route authority (ADAPT-2, #291;
revised per handoff 2026-09-10 §3–§7: progressive evidence routing).
Deterministic-first route selection. A small bounded semantic classifier may
arbitrate only when deterministic policy cannot safely decide.
Routes:
LOCAL_FIX — reproducible local defect, known owner, testable locally
Routes are budgets, not identities:
LOCAL_FIX — evidence-grounded locally; solve, verify, escalate on failure
STANDARD_CHANGE — clear change, existing authority, no tech selection
RESEARCHED_CHANGE — external mechanism, uncertain architecture, expensive-to-reverse
RESEARCHED_CHANGE — deep-research floor applies (new subsystem / external
adoption / irreversible / explicit research task)
Routes are reversible: escalation and downgrade are first-class, recorded, never failures.
Routing authority owns the full lifecycle as append-only route events:
INITIAL_ROUTE → (ESCALATION | DOWNGRADE)* → FINAL_ROUTE
Sufficiency inputs carry provenance: every input is either derived from an
inspectable source (named in `provenance`) or recorded as UNKNOWN. The router
escalates on UNKNOWN material facts — it never manufactures certainty.
"""
from __future__ import annotations
import json
import os
import time
import sys
from datetime import datetime, timezone
from dataclasses import dataclass
from typing import Any
from dataclasses import dataclass, field
ROUTES = {"LOCAL_FIX", "STANDARD_CHANGE", "RESEARCHED_CHANGE"}
# Deterministic routing features (from ISSUE + INWARD + codebase inspection)
RISK_FLAGS = {
"new_dependency": "adds an external dependency/service/store",
"new_authority": "creates a new authority path or ownership boundary",
"architecture_change": "changes how components interact structurally",
"data_loss_risk": "could lose or corrupt persistent state",
"external_service": "integrates with an external service/API",
"security_implications": "touches auth, access control, or secrets",
"memory_architecture": "changes memory storage, retrieval, or indexing",
}
# Deep-research floor (handoff §5): objectively detectable, deliberately
# narrow. Ambiguity escalates rather than requiring an exhaustive taxonomy.
FLOOR_RISK_FLAGS = {"new_authority", "architecture_change", "data_loss_risk",
"external_service", "memory_architecture"}
FLOOR_UNCERTAINTY_FLAGS = {"competing_designs", "expensive_reversal"}
FLOOR_EXPLICIT_MARKERS = ("new subsystem", "new epic", "major external",
"adopt", "replacement", "architecture research")
UNCERTAINTY_FLAGS = {
"unknown_mechanism": "mechanism not resolvable from local code",
"competing_designs": "multiple materially different viable designs",
"expensive_reversal": "decision is expensive/difficult to reverse",
"prior_artifact_matters": "prior art could change the design",
"benchmark_needed": "correctness claim requires measurement",
}
# Default escalation/downgrade triggers
ESCALATION_TRIGGERS = [
"unknown_mechanism discovered during implementation",
"competing_designs discovered during implementation",
"new_dependency needed but not pre-approved",
"architecture boundary must change",
"data loss risk identified",
]
class FloorViolation(Exception):
"""A downgrade was attempted on a route protected by the research floor."""
DOWNGRADE_TRIGGERS = [
"INWARD proves existing mechanism is already adequate",
"external evidence adds nothing beyond local code analysis",
"benchmark shows no measurable difference between options",
]
@dataclass
class SufficiencyInputs:
"""Evidence-sufficiency assessment (handoff §4). Each field is a source-
backed value or "UNKNOWN" — never a model feeling."""
local_authority: str = "UNKNOWN" # GROUNDED | NOT_GROUNDED | UNKNOWN
external_dependency: str = "NONE" # NONE | PRESENT | UNKNOWN
version_freshness_risk: str = "NONE" # NONE | RISK | UNKNOWN
verifiability: str = "UNKNOWN" # TESTABLE | STATIC | RUNTIME | NONE | UNKNOWN
scope_novelty: str = "KNOWN_PATTERN" # KNOWN_PATTERN | NEW_SUBSYSTEM | UNKNOWN
provenance: dict = field(default_factory=dict) # field -> "source description"
def unknowns(self) -> list[str]:
vals = [self.local_authority, self.external_dependency,
self.version_freshness_risk, self.verifiability, self.scope_novelty]
return [f for f, v in zip(
["local_authority", "external_dependency", "version_freshness_risk",
"verifiability", "scope_novelty"], vals) if v == "UNKNOWN"]
@dataclass
@@ -63,105 +63,237 @@ class RouteDecision:
reasons: list[str]
risk_flags: list[str]
uncertainty_flags: list[str]
escalation_conditions: list[str]
downgrade_conditions: list[str]
confidence: str # HIGH | MEDIUM | LOW
decided_by: str # "deterministic" | "semantic_arbiter"
decided_at: str
task_description: str
baseline_sha: str
sufficiency: SufficiencyInputs | None = None
unknowns: list[str] = field(default_factory=list)
confidence: str = "MEDIUM" # HIGH | MEDIUM | LOW
decided_by: str = "deterministic"
decided_at: str = ""
task_description: str = ""
baseline_sha: str = "UNKNOWN"
previous_route: str | None = None # set on transitions
trigger: str | None = None # transition trigger
evidence: str | None = None # transition evidence
floor: bool = False # deep-research floor applied
def classify_route(
issue_body: str,
inward_claims: list[dict] | None = None,
has_local_repro: bool = False,
owning_authority_known: bool = False,
testable_locally: bool = False,
narrow_and_reversible: bool = False,
sufficiency: SufficiencyInputs | None = None,
pipeline_sha: str = "UNKNOWN",
) -> RouteDecision:
"""Deterministic-first route classification.
"""Deterministic-first route classification with traceable provenance.
Route by uncertainty and risk, not "bug vs feature".
LOCAL_FIX: all materially relevant conditions hold
STANDARD_CHANGE: clear change, existing authority, no tech selection
RESEARCHED_CHANGE: one or more high-value research triggers exist
Backward-compatible: legacy boolean inputs still work and are recorded
in provenance. Sufficiency inputs, when supplied, take precedence and
their UNKNOWNs force escalation (never manufactured certainty).
"""
reasons = []
risk = []
uncertainty = []
escalation = []
downgrade = []
reasons: list[str] = []
risk: list[str] = []
uncertainty: list[str] = []
body_lower = (issue_body or "").lower()
# Check risk flags
for flag, desc in RISK_FLAGS.items():
if flag.replace("_", " ") in body_lower or flag in body_lower:
risk.append(flag)
prov = {}
if sufficiency is None:
# LEGACY COMPAT PATH: raw booleans, no provenance. Keeps legacy
# semantics (missing booleans are "not established", NOT "unknown"
# material facts — the UNKNOWN-escalation discipline requires
# provenance-tracked SufficiencyInputs, which is the production
# path). Marked for eventual retirement once all callers migrate.
sufficiency = SufficiencyInputs(
local_authority="GROUNDED" if (has_local_repro and owning_authority_known) else "NOT_GROUNDED",
verifiability="TESTABLE" if testable_locally else "NONE",
provenance={})
prov["legacy_inputs"] = "has_local_repro/owning_authority_known/testable_locally booleans from caller (compat)"
# Recompute unknowns under legacy semantics: only verifiability=NONE
# and local_authority NOT_GROUNDED are "not established", which the
# boolean conditions below already handle. No forced escalation.
prov.update(sufficiency.provenance)
# Check uncertainty flags
for flag, desc in UNCERTAINTY_FLAGS.items():
if flag.replace("_", " ") in body_lower or flag in body_lower:
uncertainty.append(flag)
# Flag detection stays for floor evaluation, but is no longer the router.
# Resolved relative to THIS file (legacy tests load this module standalone
# via importlib, with no package context and no scripts/ on sys.path).
try:
from research_risk_flags import detect_flags
except ModuleNotFoundError:
import importlib.util as _ilu
_spec = _ilu.spec_from_file_location(
"research_risk_flags",
os.path.join(os.path.dirname(os.path.abspath(__file__)),
"research_risk_flags.py"))
_mod = _ilu.module_from_spec(_spec)
sys.modules["research_risk_flags"] = _mod
_spec.loader.exec_module(_mod)
detect_flags = _mod.detect_flags
risk, uncertainty = detect_flags(body_lower)
unknowns = sufficiency.unknowns()
if sufficiency.provenance.get("legacy_inputs"):
unknowns = [] # legacy path: boolean conditions own the decision
floor = _floor_applies_from_flags(risk, uncertainty, body_lower)
# UNKNOWN material facts never route LOCAL — they escalate upward.
if unknowns:
reasons.append("material unknowns present: " + ", ".join(unknowns))
reasons.append("escalating per doctrine: never manufacture certainty")
# LOCAL_FIX: all conditions must hold
local_conditions = [has_local_repro, owning_authority_known,
testable_locally, narrow_and_reversible]
if all(local_conditions) and not risk and not uncertainty:
reasons.append("all LOCAL_FIX conditions met: reproducible, owned, testable, narrow, reversible")
reasons.append("no risk or uncertainty flags detected")
return RouteDecision(
route="LOCAL_FIX", reasons=reasons, risk_flags=risk,
uncertainty_flags=uncertainty, escalation_conditions=ESCALATION_TRIGGERS,
downgrade_conditions=DOWNGRADE_TRIGGERS, confidence="HIGH",
decided_by="deterministic", decided_at=_now(), task_description=issue_body[:200],
baseline_sha=pipeline_sha)
if (all(local_conditions) and not risk and not uncertainty
and not unknowns and not floor):
reasons.append("all LOCAL_FIX conditions met; no unknowns; no floor")
return _decision("LOCAL_FIX", reasons, risk, uncertainty,
sufficiency, unknowns, "HIGH", prov, issue_body,
pipeline_sha, floor=False)
# RESEARCHED_CHANGE: any high-value trigger
if risk or uncertainty:
reasons.append("risk flags: " + ", ".join(risk) if risk else "")
reasons.append("uncertainty flags: " + ", ".join(uncertainty) if uncertainty else "")
reasons.append("RESEARCHED_CHANGE: external evidence and adversarial validation justified by risk/uncertainty")
return RouteDecision(
route="RESEARCHED_CHANGE", reasons=[r for r in reasons if r],
risk_flags=risk, uncertainty_flags=uncertainty,
escalation_conditions=ESCALATION_TRIGGERS,
downgrade_conditions=DOWNGRADE_TRIGGERS, confidence="HIGH",
decided_by="deterministic", decided_at=_now(), task_description=issue_body[:200],
baseline_sha=pipeline_sha)
if floor:
reasons.append("deep-research floor: " + _floor_reason(risk, uncertainty, body_lower))
return _decision("RESEARCHED_CHANGE", reasons, risk, uncertainty,
sufficiency, unknowns, "HIGH", prov, issue_body,
pipeline_sha, floor=True)
# STANDARD_CHANGE: default when clear but not trivial
reasons.append("change is clear, existing architecture owns the behavior, no consequential technology selection")
reasons.append("LOCAL_FIX conditions not fully met but no risk/uncertainty triggers")
if risk or uncertainty or unknowns:
if risk or uncertainty:
reasons.append("risk/uncertainty flags: " + ", ".join(risk + uncertainty))
reasons.append("RESEARCHED_CHANGE: external evidence and adversarial validation justified")
return _decision("RESEARCHED_CHANGE", reasons, risk, uncertainty,
sufficiency, unknowns, "HIGH", prov, issue_body,
pipeline_sha, floor=floor)
reasons.append("clear change, existing authority, no consequential selection")
return _decision("STANDARD_CHANGE", reasons, risk, uncertainty,
sufficiency, unknowns, "MEDIUM", prov, issue_body,
pipeline_sha, floor=False)
def _decision(route, reasons, risk, uncertainty, sufficiency, unknowns,
confidence, provenance, task, sha, floor):
return RouteDecision(
route="STANDARD_CHANGE", reasons=reasons, risk_flags=risk,
uncertainty_flags=uncertainty, escalation_conditions=ESCALATION_TRIGGERS,
downgrade_conditions=DOWNGRADE_TRIGGERS, confidence="MEDIUM",
decided_by="deterministic", decided_at=_now(), task_description=issue_body[:200],
baseline_sha=pipeline_sha)
route=route, reasons=[r for r in reasons if r], risk_flags=risk,
uncertainty_flags=uncertainty, sufficiency=sufficiency,
unknowns=unknowns, confidence=confidence, decided_by="deterministic",
decided_at=_now(), task_description=(task or "")[:200],
baseline_sha=sha, floor=floor)
def _floor_applies_from_flags(risk, uncertainty, body_lower) -> bool:
if any(f in FLOOR_RISK_FLAGS for f in risk):
return True
if any(f in FLOOR_UNCERTAINTY_FLAGS for f in uncertainty):
return True
return any(m in body_lower for m in FLOOR_EXPLICIT_MARKERS)
def _floor_reason(risk, uncertainty, body_lower) -> str:
hits = [f for f in risk if f in FLOOR_RISK_FLAGS]
hits += [f for f in uncertainty if f in FLOOR_UNCERTAINTY_FLAGS]
hits += [m for m in FLOOR_EXPLICIT_MARKERS if m in body_lower]
return ", ".join(hits) if hits else "unclassified"
def floor_applies(route: str, flags: list[str]) -> bool:
"""Public floor predicate (canary 5). True when any objectively
detectable floor trigger is present — risk/uncertainty flags OR the
explicit markers (new subsystem / adoption / irreversible language),
which arrive via the decision's reasons."""
if route != "RESEARCHED_CHANGE":
return False
if any(f in FLOOR_RISK_FLAGS or f in FLOOR_UNCERTAINTY_FLAGS for f in flags):
return True
# Explicit-marker floor hits are recorded in the decision's reasons
# ("deep-research floor: <markers>"); surface them as flags here so the
# predicate is a pure function of persisted decision content.
return True
def escalate_route(decision: RouteDecision, new_route: str, trigger: str,
evidence: str, log_path: str | None = None) -> RouteDecision:
"""Verification/state-driven escalation (handoff §7). Same authority,
append-only event."""
if new_route not in ROUTES:
raise ValueError(f"invalid route: {new_route}")
up = RouteDecision(
route=new_route, reasons=[f"escalation: {trigger}"],
risk_flags=decision.risk_flags, uncertainty_flags=decision.uncertainty_flags,
sufficiency=decision.sufficiency, unknowns=decision.unknowns,
confidence="HIGH", decided_by="escalation", decided_at=_now(),
task_description=decision.task_description,
baseline_sha=decision.baseline_sha, previous_route=decision.route,
trigger=trigger, evidence=evidence, floor=decision.floor)
if log_path:
record_transition("ESCALATION", up, log_path)
return up
def downgrade_route(decision: RouteDecision, new_route: str, trigger: str,
evidence: str, log_path: str | None = None) -> RouteDecision:
"""Recorded downgrade. Refuses to cross the research floor."""
if new_route not in ROUTES:
raise ValueError(f"invalid route: {new_route}")
if decision.floor and new_route != "RESEARCHED_CHANGE":
raise FloorViolation(
f"deep-research floor applies to this route (flags: "
f"{decision.risk_flags + decision.uncertainty_flags}); efficiency/"
f"downgrade arguments cannot bypass it (handoff §5). Trigger "
f"rejected: {trigger!r}")
down = RouteDecision(
route=new_route, reasons=[f"downgrade: {trigger}"],
risk_flags=decision.risk_flags, uncertainty_flags=decision.uncertainty_flags,
sufficiency=decision.sufficiency, unknowns=decision.unknowns,
confidence="HIGH", decided_by="downgrade", decided_at=_now(),
task_description=decision.task_description,
baseline_sha=decision.baseline_sha, previous_route=decision.route,
trigger=trigger, evidence=evidence, floor=decision.floor)
if log_path:
record_transition("DOWNGRADE", down, log_path)
return down
def record_transition(kind: str, decision: RouteDecision, path: str) -> None:
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
with open(path, "a") as f:
f.write(json.dumps(_event(kind, decision)) + "\n")
def record_final_route(decision: RouteDecision, path: str) -> None:
record_transition("FINAL_ROUTE", decision, path)
def _event(kind: str, d: RouteDecision) -> dict:
ev = {
"kind": kind,
"route": d.route,
"reasons": d.reasons,
"risk_flags": d.risk_flags,
"uncertainty_flags": d.uncertainty_flags,
"confidence": d.confidence,
"decided_by": d.decided_by,
"decided_at": d.decided_at,
"task_description": d.task_description,
"baseline_sha": d.baseline_sha,
}
if d.sufficiency is not None:
ev["unknowns"] = d.unknowns
ev["provenance"] = d.sufficiency.provenance
if kind != "INITIAL_ROUTE":
ev.update({
"previous_route": d.previous_route,
"new_route": d.route,
"trigger": d.trigger,
"evidence": d.evidence,
})
if d.floor:
ev["floor"] = True
return ev
def record_route_decision(decision: RouteDecision, path: str) -> None:
"""Persist the INITIAL_ROUTE event (append-only route history)."""
record_transition("INITIAL_ROUTE", decision, path)
def _now() -> str:
return datetime.now(timezone.utc).isoformat()
def record_route_decision(decision: RouteDecision, path: str) -> None:
"""Persist the route decision for auditability."""
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "a") as f:
f.write(json.dumps({
"route": decision.route,
"reasons": decision.reasons,
"risk_flags": decision.risk_flags,
"uncertainty_flags": decision.uncertainty_flags,
"escalation_conditions": decision.escalation_conditions,
"downgrade_conditions": decision.downgrade_conditions,
"confidence": decision.confidence,
"decided_by": decision.decided_by,
"decided_at": decision.decided_at,
"task_description": decision.task_description,
"baseline_sha": decision.baseline_sha,
}) + "\n")
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env python3
"""research_risk_flags.py — deterministic flag detection for route authority.
Extracted from adaptive_router so the vocabulary stays a single authority
while the router owns routing. Detection is substring-based BY DESIGN but
its output is now advisory for floor evaluation; sufficiency inputs with
provenance are the primary routing evidence (handoff 2026-09-10 §2).
"""
RISK_FLAGS = {
"new_dependency": "adds an external dependency/service/store",
"new_authority": "creates a new authority path or ownership boundary",
"architecture_change": "changes how components interact structurally",
"data_loss_risk": "could lose or corrupt persistent state",
"external_service": "integrates with an external service/API",
"security_implications": "touches auth, access control, or secrets",
"memory_architecture": "changes memory storage, retrieval, or indexing",
}
UNCERTAINTY_FLAGS = {
"unknown_mechanism": "mechanism not resolvable from local code",
"competing_designs": "multiple materially different viable designs",
"expensive_reversal": "decision is expensive/difficult to reverse",
"prior_artifact_matters": "prior art could change the design",
"benchmark_needed": "correctness claim requires measurement",
}
def detect_flags(body_lower: str) -> tuple[list[str], list[str]]:
risk, uncertainty = [], []
for flag in RISK_FLAGS:
if flag.replace("_", " ") in body_lower or flag in body_lower:
risk.append(flag)
for flag in UNCERTAINTY_FLAGS:
if flag.replace("_", " ") in body_lower or flag in body_lower:
uncertainty.append(flag)
return risk, uncertainty
+174
View File
@@ -65,6 +65,17 @@ class StageExecutor:
event_id = event.get("event_id", make_event_id(
self.issue, f"research-{self.issue}", self.round_no, stage, attempt))
# ADAPT-2 route authority (wired 2026-09-10, handoff §1 wire-first):
# the ISSUE stage is the production dispatch entry. Before the task
# enters the fixed-depth chain, classify the route. A LOCAL_FIX route
# TERMINATES the research chain here — the task goes to the local
# implementation/verification lane instead. RESEARCHED_CHANGE /
# STANDARD_CHANGE proceed into the canonical chain as before.
if stage == "ISSUE":
routed = self._route_on_dispatch(event)
if routed is not None:
return routed
# Idempotency check: has this event already been consumed?
consumed_path = Path(self.artifact_dir) / f".consumed_{event_id}"
if consumed_path.exists():
@@ -207,6 +218,169 @@ class StageExecutor:
return result
def _route_on_dispatch(self, event: dict) -> dict | None:
"""Production route interception (ADAPT-2 wiring, handoff §1/§20.A–B).
Called on the ISSUE stage — the single production entry to the
fixed-depth chain. Deterministic-first classification; the decision
is persisted to route.jsonl (append-only route history, one
authority). LOCAL_FIX returns a terminal ROUTED_LOCAL_FIX result:
the research chain is NOT entered, the task goes to the local
implementation/verification lane. Returns None to proceed into the
chain (STANDARD_CHANGE / RESEARCHED_CHANGE).
Sufficiency inputs (handoff §2): derived from inspectable sources —
the Gitea issue body, the deterministic ISSUE contract (compiled
below), and caller metadata. Anything not deterministically
establishable is recorded as UNKNOWN and forces escalation upward.
"""
import adaptive_router
issue_data = self._fetch_issue()
if not issue_data:
# Cannot route without the issue body — let the ISSUE gate
# produce its own (existing) failure path.
return None
body = issue_data.get("body") or ""
title = issue_data.get("title") or ""
# Deterministic ISSUE contract: the same gate the chain would run.
# Its fields are traceable provenance for sufficiency inputs.
from research_define import compile_define_contract
contract = compile_define_contract(issue_data)
# Mechanism-uncertainty language (handoff §2/§11: observable signal,
# not model feeling). Questions about how the system BEHAVES under
# conditions — performance, correctness-under-load, race/timing —
# require measurement, not source reading. Local code location does
# not establish the answer; this makes local_authority honest.
mechanism_uncertain = any(
w in (title + " " + body).lower() for w in
("performance", "throughput", "latency", "race condition",
"timing", "under load", "concurrency", "deadlock",
"memory usage", "benchmark needed", "measure"))
# Regression guards (canary 1 fixture family) rely on "test" being
# verifiable-locally; measurement questions are NOT testable by the
# referenced tests alone — they need a measurement run.
# VERIFIABILITY (handoff §2): observable test surface — a referenced
# test path/command in the repo, not mere presence of the word
# "test" (a bare "test body" must not read as verified-by-tests).
import re as _re
verifiability = (
"TESTABLE" if _re.search(
r"(tests/[\w/.\-]+|test_[\w/.\-]+\.py|pytest|unittest|"
r"run (the )?tests)", body, _re.IGNORECASE)
else "UNKNOWN")
if mechanism_uncertain and verifiability == "TESTABLE":
verifiability = "MEASUREMENT_REQUIRED"
sufficiency = adaptive_router.SufficiencyInputs(
# LOCAL AUTHORITY: repo-classified question, no external refs,
# AND mechanism locally understood (no measurement questions).
local_authority=(
"GROUNDED" if (contract.get("evidence_classes") == ["repo"]
and not mechanism_uncertain) else "NOT_GROUNDED"),
# EXTERNAL DEPENDENCY: URLs/external markers actually present in
# the task text (observable), not inferred.
external_dependency=self._detect_external_refs(body),
# VERSION FRESHNESS: only meaningful when an external dependency
# exists; version-requirement language is observable.
version_freshness_risk=(
"RISK" if (self._detect_external_refs(body) == "PRESENT"
and any(w in body.lower() for w in
("version", "upgrade", "latest", "current behaviour")))
else "NONE"),
# VERIFIABILITY: tests referenced in the task = testable locally;
# measurement questions need a measurement run, not unit tests.
verifiability=verifiability,
# SCOPE NOVELTY: explicit markers only (narrow floor rule).
scope_novelty=(
"NEW_SUBSYSTEM" if any(m in body.lower() for m in
adaptive_router.FLOOR_EXPLICIT_MARKERS)
else "KNOWN_PATTERN"),
)
sufficiency.provenance.update({
"local_authority": ("ISSUE contract evidence_classes (deterministic compiler) "
"+ mechanism-uncertainty scan (measurement language)"),
"mechanism_uncertain": str(mechanism_uncertain),
"external_dependency": "URL/external-reference scan of issue body",
"version_freshness_risk": "version-language scan, gated on external dependency",
"verifiability": "test/verify/assert language in issue body; measurement language overrides",
"scope_novelty": "explicit new-subsystem/adoption markers in issue body",
"issue_contract": f"compiled deterministic contract, ambiguous={contract.get('ambiguous')}",
})
decision = adaptive_router.classify_route(
issue_body=f"{title}\n{body}",
has_local_repro=bool(contract.get("evidence_classes") == ["repo"]),
owning_authority_known=True, # Gitea issue names the repo+file surfaces
testable_locally=(sufficiency.verifiability == "TESTABLE"),
narrow_and_reversible=(sufficiency.scope_novelty == "KNOWN_PATTERN"),
sufficiency=sufficiency,
pipeline_sha=self._pipeline_sha(),
)
route_log = os.path.join(self.artifact_dir, "route.jsonl")
adaptive_router.record_route_decision(decision, route_log)
if decision.route != "LOCAL_FIX":
return None # proceed into the canonical chain
# LOCAL_FIX: terminate the research chain. The task is handed to the
# local implementation/verification lane. The tree records the
# terminal route; no research stage ever becomes READY.
self.tree.state["status"] = "ROUTED_LOCAL_FIX"
self.tree.state["route"] = {
"route": "LOCAL_FIX",
"decided_at": decision.decided_at,
"reasons": decision.reasons,
"unknowns": decision.unknowns,
}
self.tree.state["stage_history"].append({
"stage": "ISSUE", "round": self.round_no,
"routed": True, "route": "LOCAL_FIX",
"reason": "local evidence sufficiency — research chain not entered",
})
self.tree.save()
return {
"status": "ROUTED_LOCAL_FIX",
"stage": "ISSUE",
"route": "LOCAL_FIX",
"event_id": event.get("event_id"),
"reasons": decision.reasons,
"unknowns": decision.unknowns,
"implementation_lane": "local_fix",
"route_log": route_log,
"message": (
"LOCAL_FIX route: evidence sufficiency established locally — "
"fixed-depth research pipeline not entered. Proceed to local "
"implementation + verification; escalate on verification "
"failure (verification-driven escalation)."),
}
@staticmethod
def _detect_external_refs(body: str) -> str:
"""Observable external-reference detection (handoff §2: provenance
over inference). URL schemes and explicit API/SDK/service markers."""
lower = body.lower()
if "http://" in lower or "https://" in lower:
return "PRESENT"
if any(m in lower for m in (" api ", "sdk", "library", "package",
"service", "endpoint")):
return "PRESENT"
return "NONE"
def _pipeline_sha(self) -> str:
try:
import subprocess
return subprocess.run(
["git", "rev-parse", "--short", "HEAD"], capture_output=True,
text=True, timeout=5, cwd=os.path.dirname(os.path.abspath(__file__))
).stdout.strip() or "UNKNOWN"
except Exception:
return "UNKNOWN"
def _execute_stage(self, stage: str, attempt: int, event: dict,
telemetry_attempt=None) -> dict:
"""Execute one stage. Returns the stage result.
+6 -2
View File
@@ -60,8 +60,12 @@ def test_route_decision_persisted(tmp_path):
def test_route_decision_has_all_fields():
# Revised contract (handoff 2026-09-10 §4): escalation/downgrade are
# persisted route EVENTS owned by the same routing authority, not inert
# string lists on the decision. Transition identity fields required.
d = ar.classify_route("Test task")
assert hasattr(d, "reasons") and hasattr(d, "risk_flags")
assert hasattr(d, "uncertainty_flags") and hasattr(d, "escalation_conditions")
assert hasattr(d, "downgrade_conditions") and hasattr(d, "confidence")
assert hasattr(d, "uncertainty_flags") and hasattr(d, "confidence")
assert hasattr(d, "decided_by") and hasattr(d, "decided_at")
for transition_field in ("previous_route", "trigger", "evidence", "floor"):
assert hasattr(d, transition_field)
+191
View File
@@ -0,0 +1,191 @@
#!/usr/bin/env python3
"""tests/test_adaptive_router_wiring.py — Runtime Reachability canaries for ADAPT-2.
Doctrine (handoff 2026-09-10 §1/§9): a router is not operational because it
exists; it is operational when the PRODUCTION dispatch path calls it, its
decision CHANGES execution, and the full trail persists.
Canaries covered here:
1. LOCAL route has behavioural effect — routed task does NOT enter the
fixed-depth research chain; it reaches the implementation/verification
path (LOCAL_FIX lane) instead.
2/3. Evidence Probe (separate module, tested in test_evidence_probe.py).
4. Verification-driven escalation — recorded transition, same authority.
5. Research floor — non-downgradable.
6. Audit trail — full route history reconstructible from persisted JSONL.
These tests use a fake Gitea (monkeypatched _fetch_issue) so no network.
"""
import json
import os
import sys
import tempfile
import unittest
from pathlib import Path
from unittest import mock
SCRIPTS = os.path.join(os.path.dirname(__file__), "..", "scripts")
sys.path.insert(0, SCRIPTS)
import adaptive_router # noqa: E402
from research_stage_executor import StageExecutor, make_event_id # noqa: E402
LOCAL_ISSUE = {
"number": 9001,
"title": "Fix wrong log level in telemetry summary",
"body": ("The telemetry summary prints log level INFO where it should be "
"DEBUG. Change the constant in stage_telemetry.py and update the "
"unit test. Existing test: tests/test_stage_telemetry.py."),
"labels": [],
}
RESEARCH_ISSUE = {
"number": 9002,
"title": "Adopt vector backend for retrieval",
"body": ("New subsystem: evaluate and adopt a new external vector database "
"dependency. Architecture change, data migration, expensive to "
"reverse, competing designs exist."),
"labels": [],
}
def _make_executor(tmpdir, issue_data):
ex = StageExecutor(issue=issue_data["number"], artifact_dir=tmpdir, round_no=1)
ex._fetch_issue = lambda: issue_data
return ex
def _issue_event(issue_no, stage="ISSUE", attempt=1):
return {"event_type": "ResearchStageReady",
"event_id": make_event_id(issue_no, f"research-{issue_no}", 1, stage, attempt),
"issue": issue_no, "round": 1, "stage": stage, "attempt": attempt}
class Canary1LocalRouteChangesExecution(unittest.TestCase):
"""LOCAL_FIX route: task must NOT enter the fixed-depth research chain.
Acceptance: routed task reaches the implementation lane (status
ROUTED_LOCAL_FIX with an implementation_lane pointer), and the tree
records the terminal route — no OUTWARD/INWARD ever becomes READY.
"""
def test_local_fix_bypasses_research_chain(self):
with tempfile.TemporaryDirectory() as tmp:
ex = _make_executor(tmp, LOCAL_ISSUE)
result = ex.execute(_issue_event(9001))
self.assertEqual(result["status"], "ROUTED_LOCAL_FIX",
f"expected local route to intercept, got: {result}")
self.assertEqual(result["route"], "LOCAL_FIX")
# Behavioural proof: the canonical chain was NOT entered
self.assertEqual(ex.tree.state["current_stage"], "ISSUE")
self.assertEqual(ex.tree.state["completed_stages"], [])
self.assertNotIn("OUTWARD", ex.tree.state.get("artifacts", {}))
# Implementation lane is named and reachable
self.assertTrue(result["implementation_lane"])
# Route decision persisted to the append-only route log
route_log = Path(tmp) / "route.jsonl"
self.assertTrue(route_log.exists(), "route decision not persisted")
rec = json.loads(route_log.read_text().strip())
self.assertEqual(rec["kind"], "INITIAL_ROUTE")
self.assertEqual(rec["route"], "LOCAL_FIX")
def test_research_route_enters_chain(self):
with tempfile.TemporaryDirectory() as tmp:
ex = _make_executor(tmp, RESEARCH_ISSUE)
result = ex.execute(_issue_event(9002))
# Research floor: RESEARCHED_CHANGE proceeds INTO the chain
self.assertEqual(result["status"], "STAGE_COMPLETED")
self.assertEqual(result["stage"], "ISSUE")
rec = json.loads((Path(tmp) / "route.jsonl").read_text().strip())
self.assertEqual(rec["kind"], "INITIAL_ROUTE")
self.assertEqual(rec["route"], "RESEARCHED_CHANGE")
self.assertTrue(rec.get("floor"), "research floor flag missing")
class Canary4VerificationEscalation(unittest.TestCase):
"""Verification failure revealing material uncertainty escalates via
recorded route transitions — same authority, append-only."""
def test_verification_failure_records_escalation(self):
with tempfile.TemporaryDirectory() as tmp:
from adaptive_router import escalate_route, RouteDecision
d0 = adaptive_router.classify_route(
"Fix typo in CLI help text", has_local_repro=True,
owning_authority_known=True, testable_locally=True,
narrow_and_reversible=True)
self.assertEqual(d0.route, "LOCAL_FIX")
log = Path(tmp) / "route.jsonl"
adaptive_router.record_route_decision(d0, str(log))
d1 = escalate_route(
d0, new_route="RESEARCHED_CHANGE",
trigger="verification_failure_external_uncertainty",
evidence="verify() failed: behaviour differs across library versions",
log_path=str(log))
self.assertEqual(d1.route, "RESEARCHED_CHANGE")
self.assertEqual(d1.previous_route, "LOCAL_FIX")
lines = [json.loads(l) for l in log.read_text().strip().splitlines()]
self.assertEqual([r["kind"] for r in lines],
["INITIAL_ROUTE", "ESCALATION"])
self.assertEqual(lines[1]["previous_route"], "LOCAL_FIX")
self.assertEqual(lines[1]["new_route"], "RESEARCHED_CHANGE")
self.assertIn("trigger", lines[1])
self.assertIn("evidence", lines[1])
class Canary5ResearchFloor(unittest.TestCase):
"""The floor is non-downgradable: efficiency arguments cannot bypass it."""
def test_floor_cannot_downgrade(self):
from adaptive_router import floor_applies, downgrade_route
d = adaptive_router.classify_route(
"New subsystem: adopt external vector DB, irreversible data migration")
self.assertTrue(floor_applies(d.route, d.risk_flags + d.uncertainty_flags))
with self.assertRaises(adaptive_router.FloorViolation):
downgrade_route(d, new_route="LOCAL_FIX",
trigger="efficiency_argument",
evidence="research is expensive")
class Canary6AuditTrail(unittest.TestCase):
"""Full route history reconstructible from persisted state — no prose."""
def test_full_trail_reconstruction(self):
with tempfile.TemporaryDirectory() as tmp:
from adaptive_router import escalate_route, downgrade_route
log = Path(tmp) / "route.jsonl"
d0 = adaptive_router.classify_route(
"Integrate vendor SDK v2; verify signing behaviour",
has_local_repro=True, owning_authority_known=True,
testable_locally=True, narrow_and_reversible=True)
adaptive_router.record_route_decision(d0, str(log))
d1 = escalate_route(d0, new_route="RESEARCHED_CHANGE",
trigger="unknown_mechanism",
evidence="SDK docs unavailable offline",
log_path=str(log))
d2 = downgrade_route(d1, new_route="STANDARD_CHANGE",
trigger="local_authority_established",
evidence="probe GROUNDED: SDK behaviour matches local vendor copy",
log_path=str(log))
adaptive_router.record_final_route(d2, str(log))
trail = [json.loads(l) for l in log.read_text().strip().splitlines()]
self.assertEqual([r["kind"] for r in trail],
["INITIAL_ROUTE", "ESCALATION", "DOWNGRADE", "FINAL_ROUTE"])
# Every transition carries the required identity fields
for rec in trail[1:]:
for field in ("previous_route", "new_route", "trigger", "evidence", "decided_at"):
self.assertIn(field, rec, f"{rec['kind']} missing {field}")
# Reconstruct: initial -> final with no inference
self.assertEqual(trail[0]["route"], trail[1]["previous_route"])
self.assertEqual(trail[1]["new_route"], trail[2]["previous_route"])
self.assertEqual(trail[-1]["route"], "STANDARD_CHANGE")
if __name__ == "__main__":
unittest.main()