The push-convergence path synthesizes PR titles as '<kind>: converged
release' where kind comes from version_convergence.highest_warranted_bump
(minor|patch). semver_bump then re-derived the kind from the TITLE TEXT
and 'minor:' matched nothing → patch. Every feature tranche downgraded:
T18/T19/T20 feat ranges bumped 0.66.77-83 instead of 0.67.0. Fixed:
MINOR_PREFIX_RE carries the convergence decision through. Held-out
proves minor-title→0.67.0 and patch-title→0.67.1 (no accidental
re-minor). This is the 0.66.x problem the mandate named, root-caused.
- event_wait.find_matching_event(): replays matching completion events
from the durable bridge event log at registration. Root-caused from
TWO production cycles: on a serialized single-runner machine the CI
wake always lands before/around deploy registration — a forward-only
FIFO rendezvous can never deliver it (both cycles fell back to the
900s deadline, truthfully). The event log exists precisely for this;
the wake is still never the verdict.
- ci_event_wait: event_replayed mode short-circuits to the authority
read; 2 new held-outs (9 total).
- drift_resume.py (16P): claim-vs-observation reconciliation with
EXPECTED/BENIGN/MATERIAL/CONFLICTING/UNKNOWN classes; MATERIAL/
CONFLICTING/UNKNOWN block dependent work; user-locked claims escalate
to CONFLICTING (reality never silently overrides user-locked
authority). Held-outs driven by the REAL 16O fixture: the VAT
regression blocks resume; the checkpoint commit resumes clean.
- 16O benchmark persisted (store/evidence/t16/16O_benchmark.md): honest
result — on a tiny fixture cold and scaffold-assisted re-entry cost
about the same (8 vs 9 files); the scaffold's advantage is drift
classification, recorded next actions, and intent, not raw step
count. No fabricated win.
Suite 1884 green; ratchets PASS.
- provider_context.py: require_model() resolves the model from the
ambient provider context; missing context fails LOUDLY
(PROVIDER_UNRESOLVED, exit 3) — no silent default provider anywhere.
- t14/t15 builder drivers, evidence_probe, research executor/relay:
hardcoded 'Verboo Code/glm-5.3-flash' defaults REMOVED (6+ sites);
provider/model selection now belongs exclusively to the provider
layer via the ambient context. No caller anywhere set MIMO_MODEL —
every run silently used the code-chosen provider until now.
- failure_recovery: PROVIDER_UNAVAILABLE class (context-forced; real
status/API code preserved verbatim in evidence; policy
block_branch_preserve_error, 0 retries, no fallback — 16G-D).
- tests/test_provider_ownership.py: structural guard (provider names
in Scaffolding code lines fail the suite; module list live-discovered)
+ 16G held-outs (verbatim codes, zero fallback attempts, resumable
after block, provider preference never mutates scaffold truth).
- 16U root-cause completion: pyright skew 117⇄124 driven by ambient
VIRTUAL_ENV/PATH leakage into pyright's interpreter resolution —
the ratchet now neutralizes ambient-venv signals (PYTHONNOUSERSITE
was necessary but not sufficient; reproduced and closed).
- spine reconciliation: T15 modules (event_wait, stage_guards,
failure_recovery, programme_continuation) were missing belongs_to
relationships in the tracked store while the generated manifest had
them — registered with SCAFFOLD-TRANCHE-015 evidence; provider_context
registered under capability; manifest regenerated via export-umbrellas.
Suite 1869 green; ratchets PASS; Henxels rc=0.
- deploy.yml sleep-30 polling loop REPLACED: registers a gitea-identity
PROCESS_EVENT wait, yields in select() on the FIFO, resumes on the
ci.yml notify-event-bridge WAKE, then re-reads the canonical Gitea
verdict (deploy_qualify.py). Event payload is never completion truth.
- ci.yml gains notify-event-bridge (always(), best-effort): a red CI
wakes the waiter as loudly as a green one; bridge unreachable ->
watchdog recovery covers the lost wake.
- event_wait.py: register_process_wait gains provider/event_type/sha
identity (16E: ONE bridge primitive; builder + gitea are adapters);
open_fifo_reader + caller-owned fifo_fd close the 16Y#10 race (event
before waiter in select()); await_resume_event returns eof instead of
busy-looping on a caller-owned fd.
- ci_event_wait.py watchdog: failure DETECTOR only — liveness-gated
re-arm, one-shot authority recovery on lost events, truthful failure
when no verdict exists; timestamps emitted for the 16D latency proof.
- tests: 8 held-outs on the REAL daemon (shared wr_daemon fixture in
conftest — no duplicated helpers). Daemon-side: /process-event passes
through provider/event_type/sha (defaults preserve T15); lease_path
sanitizes slash-bearing session ids (resume delivery was impossible
for them — found live, fixed in daemon snapshot 79434e0).
Suite 1861 green; ratchets PASS; Henxels rc=0.
ConstructionInterface.discover_active_contract: ONE canonical home for
fresh-builder resume discovery (per-project scaffolds + main store;
most-recent contract; pure readback). T14/T15 driver helpers are now
shims. capabilities() advertises the op. Held-out: empty store invents
nothing; seeded store discovers the right contract.
15S: breaking-change lifecycle held-out — old API + consumer governed
before change; verifying→migrating→superseding enforced by the phase
map; the old path survives as governed history (supersession is
lifecycle state, not erasure); BUILD NEW never destroys before migration.
15T: src/rune/failure_recovery.py — deterministic classification
(TRANSIENT/INTERMITTENT/KNOWN_PROCESS_DEFECT/PERMANENT/APPROVAL_
REQUIRED/UNKNOWN) with explicit retry budgets; PERMANENT/APPROVAL
never auto-retry; no infinite loops possible. 2 held-outs.
scripts/t15_builder_session.py: real model builder (mimo run/Verboo) under
the event notifier; driver YIELDS on the bridge FIFO (no polling on the
normal path); readiness canary before launch; exit canaries + 15G test
truth after; evidence-gated closure.
Live findings (recorded honestly):
- real model builds run 10-65 min — the 900s watchdog misfired twice as a
false PROCESS_LOST; root cause: (a) deadline < real build time, (b)
node builders re-group/detach children so pgid AND cmdline liveness
scans can miss the work. Repair: await_resume_event re-arms bounded on
liveness; the driver consults CANONICAL WAITSTORE STATE after watchdog
expiry — a recorded verdict means completion, process-table absence is
not truth (§15E canonical-state-wins).
- an orphaned T14-era builder process (old .mjs-scope prompt, quota-stuck)
was discovered polluting process checks — killed and recorded.
Result: contract closed from evidence; 10/10 node tests; both builder
exit events processed by the bridge (verdict success both runs).
Evidence: store/evidence/t15/.
15N: capabilities() now exposes full argument schemas (types, required
fields, per-item shape, notes), lifecycle phase map + legal transitions,
and scope-amendment authority — a fresh builder DISCOVERS the
report_observation contract instead of trial-and-error (the T14
dict-vs-list + missing-entity mistakes become impossible). Held-out:
schema-guided first report succeeds; schema-violating payload rejected.
15O: audit_scope is PROVENANCE-AWARE — residue paths present before the
builder ran (or explicitly runtime-declared) stay exempt and recorded;
NEW residue-looking paths appearing mid-session (.mimocode/evil,
.git/config) are SCOPE VIOLATIONS. The hostile lookalike can no longer
hide behind the T14 prefix rule.
15P: PROJECT_BUILD bootstrap twice = one project identity (no
duplication); event replay after restart = readback-only recovery
(idempotent by construction, contract-inspected).
Root cause (proven by controlled experiment): pyright resolves the python
environment for import analysis; a developer's user-site packages
(~/.local/lib/python3.14/site-packages) leak into resolution. Same
checkout, same lock-pinned venv: nick 124/429, runner user 117/437.
PYTHONNOUSERSITE=1 pins both contexts to identical arithmetic (verified:
exact 117-error match both sides).
Fix: type_check_ratchet.py runs pyright with PYTHONNOUSERSITE=1 — local
preflight now PREDICTS CI. Residual 437-vs-443 warning delta noted
honestly (warnings are capped, not matched); error arithmetic identical.
'CI is the gate' was not the answer; the environments now agree.
t14_builder_session.py: real builder session driver.
- 14D: 'mimo run' (Verboo Code/glm-5.3-flash, PL1 provider policy default;
primary Xioami/mimo-v2.5-pro endpoint quota-exhausted 429) as a SEPARATE
OS process consumes the Construction Interface projection (scope,
obligations, phase, verification requirement, next action) and implements
a JS practice scheduler. Builder files: package.json, scheduler.js
(validated createPracticeItem/listItems/completeItem), scheduler.test.js
(11 node:test cases). Scope audit + independent node --test by the
driver; closure from evidence only (§14X.8).
- 14E: real SIGKILL to the builder process mid-session; fresh builder
process resumed from canonical state only (no transcript): recovered
project/contract/phase/scope, inspected-not-overwrote, zero duplication,
verification still required, evidence-gated closure.
- Runtime residue (.mimocode session scaffolding) classified and recorded
explicitly — not silently whitelisted, not a builder scope violation.
- Requirement spec governed as contract on project surface; Need references
it (REUSE of specification); BUILD_NEW silent-choice gate respected.
Evidence logs: store/evidence/t14/.
14B: the out-of-lock publisher (publish_out_of_lock) no longer trusts the
originating job's SHA. It re-reads CURRENT deployment authority immediately
before publishing and constructs the note from the authority's identity.
Adversarial race held-out: A promotes → B supersedes → B publishes → A
resumes → final history says B, A's SHA never enters. Unreadable authority
→ no publication (never fabricate an identity).
Body parsers (summary_from_body / sha_from_body) moved to release_notes.py
as the single authority; release_history_cli.py delegates.
14C: .convergence-intent.json ELIMINATED (1 writer, 0 readers, non-atomic
write, bogus recorded_at). Pending convergence is derived from the
deployment authority itself (atomic symlink + build identity): restart-
readable, corruption-immune, monotonic, idempotent by construction.
T11 stale-refusal invariant preserved (CLI revalidation unchanged).
§12C design insight from the 0.66.51 deploy (run 7721): the workflow-step
refresh fired (steps.promotion_truth gate worked) but the authority
readback disagreed — an overlapping deploy had superseded the candidate
between promotion-check and refresh-step. Correct refusal, but the WINNER
never converges because its own refresh can hit the same race.
DEPLOY_PROMOTION.PROMOTE now accepts on_promoted: invoked INSIDE the flock
critical section immediately after the symlink swap — only the promotion
WINNER converges, atomically with the swap. Failure degrades to STALE
(software valid, history lags, retry converges). deploy_stage passes a
convergence hook that builds evidence-derived notes and publishes to Gitea.
Tests: 4 new (hook fires on promotion, NOT fired for stale, failure
degrades not crashes, A→B race winner-converges-inside-lock); suite 1687
green; race tests all green.
The deploy workflow's promotion gate (steps.promotion_truth.outputs) is
necessary but not sufficient: two overlapping deploys can both observe
promoted=true at their own moments, and the STALE one's refresh could
overwrite the newer converged identity.
release_history_cli.py refresh now REVALIDATES against the deployment
authority at refresh time: if current's build identity differs from the
candidate, the stale refresh is REFUSED (T11 invariant preserved) and the
WINNING deploy's refresh converges the release to the final truth.
12C test plan: clean promotion refresh, already-current idempotence,
A→B race final-B convergence, candidate/authority disagreement refusal,
no-duplicate, missing-release explicit failure, evidence-derived notes.
§1 HELD-OUT CONVERGENCE DEFECT confirmed live: v0.66.45 release recorded
deployed ce328a80 while the deployment authority had converged to
e3263ef9/0.66.45 — repaired via the new refresh path; the release now
states BOTH honestly (tag commit ce328a80 · final deployed e3263ef9).
§2 audit answers: NO workflow invoked release_notes.py; release creation
used PR title/body paste (empty '##' bodies on direct pushes); nothing
refreshed after same-version deploys. T9 proved the mechanism, not
automatic adoption — recorded, history not rewritten.
§4 WIRING (one note-generation authority, no YAML logic):
- auto-version.yml: old PR-paste release step REMOVED (§6); replaced by
release_history_cli.py init — evidence-derived notes from the governed
scaffold store, fail-closed on publication failure (§5/§16: Rune cannot
silently create another empty historical release)
- deploy.yml: convergence refresh after successful promotion — same
release PATCHed with the converged deployed identity + newly-governed
facts; visible non-blocking failure (documentation failure never rolls
back valid software; retry converges)
- release_notes.py: auto_initial_note (no hand-written prose, §7) +
convergence_refresh (evidence accumulates, tag SHA never changes,
idempotent) + build_from_deployment
- §11 ledger-only: maintenance:builder-interaction-ledger created;
§16: release-history backlog entry amended (health indicator), no
duplicates
Tests: 10 new (§13 A-L incl. 0.66.45 convergence fixture, idempotence,
failure visibility, retry recovery, no-duplicate guarantee, unsupported-
claim exclusion through the auto path); suite 1620 green.
Observed live on deploy 7449 (SHA 535c2909): a delayed older candidate's
checkout cannot contain commits newer than itself — its local object
store lacks the deployed commit entirely, so the ancestry classification
returned 'divergent' and fail-closed refused. Correct refusal, wrong
reason: the candidate IS an ancestor of the deployed build.
Repair: on divergent classification, fetch origin's canonical branch
(best-effort, credentials already configured in the workflow checkout)
and re-classify. Still divergent afterwards = genuine divergence, refusal
stands. Regression test reproduces the 7449 shape with a depth-1
checkout that genuinely lacks the deployed commit's objects.
Release-path invariant unchanged: unknown/unprovable ordering still
refuses; the fetch only lets the checker SEE the newer reality before
refusing.
Root cause of the 2026-09-13 deploy-order race (deploys 7355/7359):
deploy_stage.py promoted unconditionally in job-finish order — no
comparison against the currently-deployed commit, no lock, unconditional
PROMOTED claim. Two overlapping deploys (tranche push + auto-bump push)
could finish out of order and move current BACKWARD.
Repair (governed through the Tranche 4 live Scaffolding path — decision
trace + contract DEPLOY-MONOTONIC-001 durably saved BEFORE edits):
- deploy_promotion.py: promotion authority with GIT ANCESTRY ordering
(canonical release branch; Rune's flow is linear master history):
same -> NOOP_ALREADY_CURRENT (idempotent), descendant -> PROMOTED,
ancestor -> STALE_SUPERSEDED (truthful non-error refusal), divergent
or unknown -> fail closed. flock'd critical section: the ancestry
check against the commit current ACTUALLY points to, the last-good
refresh, and the symlink swap happen atomically — no check/swap
interleaving window. Symlink swaps are os.replace(2) over temp links:
current is never absent, never half-updated (crash-safe).
- deploy_stage.py: promotion routed through the authority; truthful
outcome reporting — STALE_SUPERSEDED prints no PROMOTED claim,
mutates nothing, exits 0 (the newer build legitimately won);
divergent/unknown exit 1 (fail closed).
- deploy.yml: promotion truthfulness check reads the deployment
authority (current symlink) after staging; runtime verification only
runs for real promotions — build/stage success != promotion success.
Test matrix (brief §20 A-M): 21 tests — the exact race shape with B
promoting then A fenced, forward order, same-candidate idempotence,
retry-after-promotion last-good integrity, stale mutation-freedom
(current AND last-good untouched), divergent orphan-branch fail-closed,
qualification gate regression, stage/promote distinction, promoted-
outcome proven from deployment authority, auto-bump overlap shape,
crash-safety coherence across mixed outcomes, red-candidate refusal
regression, green forward deploy, concurrent-thread serialization, and
the §21 LIVE two-process race: old process paused at its promotion
barrier, new promotes, old resumes and is fenced — current remains the
newer build, deterministic via file barrier (no sleeps).
Suite 1497 green; henxels rc=0; pyright clean.
ROOT CAUSE (red-CI promotions 0.66.28/29): the required-checks
'Publish protected-branch gate' step read /tmp/need_* files that no step
ever wrote and echoed 'All required CI checks passed' UNCONDITIONALLY.
With if: always() the needs gate is overridden, so the job ran on red
needs and published success — deploy_qualify correctly trusted it and
promoted red commits (24413e4, fd6e9b4, de30cd9; status timelines in the
closure evidence).
FIX: publish step now runs only when every needs.*.result == 'success';
companion fail-closed step exits 1 otherwise, so the required-checks
context reports failure instead of lying.
DEFENSE-IN-DEPTH: deploy_qualify verdict is now the AND of two
independent legs — (1) newest required-checks observation for the exact
SHA, (2) the latest COMPLETED ci.yml@refs/heads/master run for that exact
SHA with every job concluded success (fan-out resolved by path, run
resolved by head_sha, jobs envelope unwrapped — no aggregate-status
fallback). Any pending/missing/disagreement = not deployable.
CANARY: gitea_pipeline now asserts the verdict evaluates needs results
and fails closed — the class locked shut. 20 qualifier tests with
fixtures from the real incident shapes; 1402 suite green.
Phase D consumer migration: the INWARD context builder reads structural
facts from the Spine store's generated compatibility view (same schema —
zero code-path change), documented as TEMPORARY SPINE-BACKED
COMPATIBILITY. Graceful degradation to a manual manifest if Spine is not
seeded in a checkout.
Open-world entity/relationship store (schema_version 1): kinds, relationship
types and metadata are open tags — unknown future capabilities enter as data,
never as core edits (extensibility test included, synthetic capability
round-trips with zero core change). Evidence-gated maturity ladder
DECLARED<PRESENT<WIRED<REACHABLE<EXERCISED<LIVE_PROVEN — no evidence, no
promotion; demotion rejected and recorded.
Umbrella migration Phase A→C: manifest ingested once (generated-view
refusal guard prevents the ingest cycle; manual truth archived at
config/umbrellas.manual.json), canaries preserved as obligated_canary
relationships, config/umbrellas.json now a GENERATED compatibility view for
legacy consumers (CI umbrella_impact gate + henxels) — single-authority
route proven by registering module:spine through Spine alone (one store
edit; the legacy view flows out; contract tests validate the round-trip).
Dynamic observations: deployed identity (_build_identity.json), drift
classes incl. registered_but_absent, present_but_unowned,
registered_no_static_importers (D6 dead-subnet now mechanical),
version_surfaces_disagree, relationship_disappeared. Deterministic
snapshots + diff. Scaffolding V1 now consumes Spine for ownership/deps/
consumers (spine_maturity surfaced in every card). 31 spine tests; full
gates green.
scripts/scaffold_projection.py derives per-component cards (subsystem,
dependencies, consumers, mirrors, proof obligations + evidence state) from
the umbrella manifest, canary registry, AST import graph (incl. relative
lazy imports), and tests/ filesystem. No new authority: projections only.
docs/SCAFFOLDING.md records the mechanism audit (KEEP/EVOLVE/REPLACE/RETIRE)
and the real-change proof on the context-discovery fix.
- docs/GITEA_ISSUE_RECONCILIATION.md: human-readable projection (states,
bug-label audit with per-issue evidence, open-issue reconciliation table)
- docs/gitea_issue_reconciliation.json: machine-readable authority
(72 records; all open issues covered; single live defect #324).
Placed under docs/ because state/ is the gitignored runtime dir.
- scripts/gitea_reconciliation_check.py: deterministic drift check —
state/label/coverage invariants; bug-label invariant applies to OPEN
issues (closed labels are historical); exit 1 with itemized drift.
- Part K audit: closed#139#157 (FIXED_VERIFIED with evidence comments);
bug labels removed from #204#145#126#37#16 (not defects) and the
stuck label on closed#314. Open bug-labelled: 8 → 1.
- 1289 tests green; ratchet 352/80; henxels clean.
Every debug session of the release pipeline (T15 Part F forensics included)
had to re-derive the release boundary by hand. The decision JSON now
carries boundary_sha on every return path.
_version_lt(): '0.66.9' < '0.66.10' is False lexicographically ('9' > '1'),
so a legitimate patch bump after v0.66.9 surfaced as
version_ahead_of_target and the release was silently skipped (run 6781,
2026-09-11). Both comparison sites (test-injection + production) now
compare numerically per segment. Regression tests added; 1284 green.
git --grep matches the commit BODY by default: a fix commit whose body
mentions 'chore(release)' was misdetected as the release boundary,
swallowing its own warranted bump (observed live: d01df79 boundary
detection). Anchor the pattern to the subject with --extended-regexp
and re-verify via is_release_commit(). Also drop unrecognized
--fixed-strings=False flag (fatal on this git version).
release_convergence() never assigned from
last_release_boundary() — the production path classified ALL commits
(959+) as unreleased, so the warranted bump (minor, from an ancient
feat) never converged and every queued push wrote its own bump commit
(5x 0.66.9 observed 2026-09-11 20:21). Now the boundary is the one
source of truth: last chore(release) commit, falling back to the
latest tag. Post-boundary subjects only.
Also: local path was NOT reached when _boundary_subject was injected;
test_cli_prints_plan_json expectation updated to boundary semantics
(feat below the tag no longer warrants minor).
The duplicate-release-commit defect: each queued push ran the release
workflow from its own pre-bump checkout, computed the same warranted bump,
and created another identical-version commit. The convergence script now
checks if master __version__ > last tag — if so, already converged, no
commit needed. This is the definitive fix for the 5x 0.66.8 defect.
Handoff 2026-09-11 Part A-C:
A. tui_startup_canary two modes: candidate (--stage, sys.path SCRUBBED of
/mnt/workdrive/rune-code/current + imported-rune purge + contamination
check that rune resolves from the stage) and current (consuming path).
Scrub proven: candidate mode on the 76f71fc stage reports 76f71fc/0.66.2
while current is bb536c9/0.66.3. FIXED a live exit-code bug: main()
returned 1 but was called without sys.exit — a failed canary exited 0
(the deploy gate's own returncode check in deploy_stage.py was correct,
so promotion was never actually bypassed; the standalone script was).
B. Negative deployment canary (tests/test_deployment_canary.py): broken
staged candidate → canary exit 1 naming the failure → current identity
byte-identical before/after. Plus current-mode identity regression.
C. Release recursion: is_release_commit() extracted into
version_convergence.py (ONE source of truth); workflow guard uses
--is-release-subject CLI (no inline python in YAML); regression tests
cover recognition, never-bump-from-release-commits (incl. the 5x loop
case), substantive commits still bumping, and the CLI exit contract.
1241 tests green; ratchet 353/80; henxels clean.
Handoff 2026-09-10 §5–§8 corrections:
1. Completion truth (§6): mutation-class tasks (build route AND
change/defect language) terminate 'completed' only with observable
evidence — verification method or a completed mutating tool event.
Text-only responses on mutation tasks now fail with
NOT_COMPLETED_NO_MUTATION_EVIDENCE (retryable). Neutral-verb tasks
(inspect/read/check...) remain conversation-complete. The
'I don't have the capability → completed' defect is structurally
impossible for mutation tasks.
2. Escalation lifecycle (§7): the adaptive decision happens BEFORE the
terminal transition. Verification failure on a local route → ONE
probe → unresolved = task transitions research_required (nonterminal,
research consumer continues) and returns ESCALATED_TO_RESEARCH —
never a false terminal failure. GROUNDED probe → honest failed
terminal (route was right, attempt slipped).
3. Action-verb governance (§10): 'inspect the failing test' is
read-only despite containing 'failing' — leading inspection verbs
override defect/change words. Structural evidence over lexical
fragments.
4. p1_01/p1_03 fixtures updated to the completion-truth contract.
1233 tests green; ratchet 353/80.
ADAPT-LIVE tranche (handoff 2026-09-10):
1. LOCAL_FIX vs STANDARD_CHANGE discriminator (epic #284 doctrine):
defect language (fix/bug/broken/regression) routes LOCAL_FIX; clear
change language (add/implement/extend/refactor) routes STANDARD_CHANGE.
Recorded in sufficiency provenance (task_class_language). Legacy
compat path keeps body-scan defect detection.
2. Verifiability NONE (no test surface named) no longer blocks LOCAL for
defect-class narrow tasks — self-evident fixes verify by inspection;
UNKNOWN still escalates (could not determine ≠ determined absent).
3. Route-specific execution context: LOCAL_FIX = focused verification,
no research; STANDARD_CHANGE = integration verification + DESIGN-LITE
(bounded build-contract: objective, owning surfaces from the task
text, verification class, invariants, escalation policy — no model
call, no dossier).
4. Probe-on-verification-failure (ADAPT-LIVE §4/§6): a real verification
failure on the operator spine triggers ONE bounded Evidence Probe.
GROUNDED → downgrade-eligible evidence; STILL_UNKNOWN/CONTRADICTED →
recorded ESCALATION to RESEARCHED_CHANGE (route change, not task
failure). Opt-in via MIMO_PROBE_ENABLED=1 (runtime policy, now in
the TUI launcher); tests stay deterministic.
5. Probe-grounded downgrade (§7): after ALL probes ground and local
authority is established, STANDARD_CHANGE downgrades to LOCAL_FIX
with recorded trigger probe_grounded_local_authority.
Canary evidence (live, deployed Rune):
- LOCAL_FIX: 'Fix the failing test in tests/test_example.py' → LOCAL_FIX
route event, task_language=defect, no research stages
- STANDARD_CHANGE: 'Add config validation...' → STANDARD_CHANGE,
task_language=change
- Escalation: LOCAL_FIX + verification failure + probe STILL_UNKNOWN →
ESCALATION event (verification_failure_probe_unresolved) →
RESEARCHED_CHANGE persisted with evidence
- Route latency: 0.2-16ms (vs fixed-depth 384s+ wasted on LOCAL tasks)
1227→1231 tests green; ratchet 353/80; henxels clean.
deploy_stage.py resolves the repo from GITHUB_WORKSPACE (the runner's own
checkout) with safe.directory — the runner user cannot resolve HEAD in a
foreign working copy (dubious ownership), which failed the first real
deploy run. Re-deploying an already-staged SHA now restages cleanly.
The embedded multi-line python in the workflow run blocks terminated the
YAML literal scalars — Gitea could not parse deploy.yml at all (dispatch
500: 'could not find expected :'), which is why no deploy runs appeared.
Logic now lives in scripts/deploy_qualify.py + deploy_stage.py; the
workflow is pure shell + script calls.
Pre-PL1 closure (handoff 2026-09-10):
1. research_required: new nonterminal task status (records enum, state
machine edges created/running -> research_required -> running/
completed/failed/cancelled/superseded). The operator's RESEARCHED_CHANGE
disposition now transitions to research_required instead of failed —
research routing is not failure. Live canary: status=research_required,
outcome_ref=research_dispatch, route_ref set.
2. Experience consumer skips research-dispatch tasks — no false
failure-learning evidence in PL0 (verified: 0 FAILURE records for
research tasks).
3. recall_uplift A/B documented honestly in the module: latency 521-1213ms
→ 14-18ms; terms bounded (893-1665 → 16-21); recall ROW COUNTS identical
per query but 1/8-7/7 of recalled IDs differ (expansion terms differ →
different lexical candidates). Marked PROVISIONAL — quality not proven
equivalent; harness original retained for rollback.
4. Hook hardening: durable fire-trace (/tmp/rune-bridge-fire.log, one line
per actual injection with token count) + harness-internal-step guard
(checkpoint-writer/compaction steps no longer receive capsules).
5. QX7 canary fully purged: entries, contentless-FTS ghosts, both stores;
retrieval-level verification shows zero hits.
1227 tests pass; ratchet 353/80; henxels clean.
Handoff 2026-09-10 §§2–10. One canonical implementation, not copied logic:
1. Route authority moved into Rune Core (src/rune/):
adaptive_router.py, route_risk_flags.py (renamed — the old name
collided with the research-stage concept), evidence_probe.py.
Dependency direction enforced: executor imports Core; Core never
imports the executor. Research executor consumes via
rune.adaptive_router / rune.evidence_probe (dual-import guards for
standalone script execution).
2. derive_sufficiency() in Core (unified grounding semantics): the
research path passes contract-compiler fields; the operator path
grounds from task-text absence-of-defeaters. NONE vs UNKNOWN fixed
(no test surface = determined NONE, not unknown).
3. Live operator consumption: send_task routes every task via the same
authority, persists INITIAL_ROUTE to ~/.rune/routes/route.jsonl,
records task.route, and attaches an actionable research dispatch
(executor-consumable payload) for RESEARCHED_CHANGE. Proven live:
local task → STANDARD_CHANGE (research chain not entered);
floor architecture task → RESEARCHED_CHANGE + dispatch recorded.
The dispatch's downstream consumer is still the MiMo session
(documented §11 gap, not solved here).
4. builder_bridge.py: thin adapter over Rune Core — preflight(task)
runs derive_sufficiency → classify_route → (optional) memory
selection. Zero model calls, bounded capsule (12 memory / 6 lesson
lines). lesson_lines stays empty until PL1 promotes its first
lesson — the seam exists, the pressure does not.
5. PL0 qualification: qualify_experience() (TELEMETRY vs CANDIDATE,
cheap rule-based, no model). Live ledger: 132 raw → 54 candidates.
Raw outcomes preserved verbatim; SEED never auto-VALIDATED.
6. Umbrella manifest updated (4 new modules → capability umbrella).
Verification: 1213 tests pass; ruff clean; silent-handler ratchet at
baseline (353/80). The audit's §1 qualification is honored: the
earlier spine canary proved model routing, not task-class routing —
task-class routing on the live spine is proven HERE.
Runtime Reachability audit (reports/RUNTIME_REACHABILITY_AUDIT_2026-09-10.md,
4 parallel tracers + live canaries) established: ONE real end-to-end path
exists (send_task → ContextBroker → Ollama tool loop → durable events),
proven live by canary during the audit. The spine exists; the missing
pieces were edges, not architecture.
P0 implemented (choice-free items):
1. task.outcome producer edge: send_task emits a typed journal event on
EVERY terminal transition (completed AND failed) with task/model/
verification/route fields — the producer PL0/PL1 needs (outcome_ledger
was de-wired; experience ledger had zero producers).
2. Experience consumer: scripts/process_experience.py
record_operator_outcomes() reads the authoritative task-state
checkpoint (the journal is a one-way integrity journal — payload_hash
only) and appends idempotent SEED experience records. 132 live records
created; rerun = 0 (idempotent).
3. Spine defect: _routing_profiles_path and the profile-save site had bare
relative imports that fail in standalone CLI mode — trace recording
degraded to tier-2 on every CLI run (mislabeled 'inference_trace').
Fixed with the established dual-import pattern; verified clean run.
4. Standalone bootstrap: package parent now on sys.path for script
execution.
5. hook_engine.fire dead call removed (rune_tui called a nonexistent
function; AttributeError silently swallowed — hooks never ran). Left
as an honest note, not a false-liveness surface.
Verification: Phase 8 canary through the real spine — all 9 rungs
observable from persisted state (trace v5 fields incl. memory
abstention + broker budget, routing reason, checkpoint outcome, journal
event, idempotent experience record). 1213 tests pass; ruff clean;
ratchet at baseline.
The route-authority placement choice (A/B/C) is presented in the audit
report §G and awaits Nick's decision before wiring adaptive routing into
the operator path.
The OUTWARD artifact-missing handler was stranded after an unconditional
return (~100 lines of unreachable code including a legacy stdout parser).
Worse: a missing OUTWARD artifact fell through to legacy stdout parsing
instead of failing loud per ADAPT-9 authority. This restores the intended
else branch: declared file absent → ARTIFACT_MISSING, full stop.
Also narrows _pipeline_sha's blind except to its actual failure modes
(OSError, SubprocessError) — keeps the silent-handler ratchet at baseline
rather than adding debt.
Unblocked CI lint/unit-tests: the changed-file lint now reaches this file
(first PR to touch it since the F821 landed), so the pre-existing
undefined-name debt in this block had to be paid.
1213 tests pass in the CI-identical venv (requirements-ci.txt pinned).
Handoff 2026-09-10 §20.D–E, canaries 2–4:
- evidence_probe.py: bounded uncertainty-resolution operation OUTSIDE the
canonical stage chain. Fixed 5-key schema (QUESTION/SOURCE/ANSWER/
IMPLICATION/STATUS); GROUNDED requires a cited source; STILL_UNKNOWN/
CONTRADICTED are honest findings, not schema violations. Executor-owned
artifact path (ADAPT-9); no stdout rescue. Probe budget caps recursion
(MAX_PROBES_PER_TASK=2) — unresolved unknowns escalate, never loop.
- Dispatch wiring: material unknowns on non-floored tasks are resolved by
bounded probes BEFORE the route becomes final. GROUNDED probes fold into
sufficiency inputs with citation provenance; failures record an
ESCALATION route event (previous_route/trigger/evidence) and enter the
canonical chain as RESEARCHED_CHANGE.
- Real probing is OPT-IN (MIMO_PROBE_ENABLED=1 or injected runner):
dispatch stays deterministic by default — a worker call from the
dispatch path is minutes of cognition, never an implicit side effect.
- escalate_on_verification_failure(): production hook for the local lane;
refuses bare 'test failed' evidence (must name the material uncertainty).
- ROUTED_LOCAL_FIX contract message names the exact escalation call.
- Canaries: grounded probe returns to local lane without deep research;
unresolved probe escalates with recorded transition; no probe recursion;
verification-driven escalation persisted; bare evidence refused.
- Suite: 1185 passed (3 pre-existing env failures excluded)
Refs #291
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)