Compare commits

...
Author SHA1 Message Date
conductor efe3871916 test(tty): stabilize no-accumulation test — delta vs pre-loop snapshot of unrelated system sleeps (mount helper sleeps race the window) 2026-09-19 07:24:10 +01:00
conductor 026a1bd919 test(tty): canonical tool hash parity (bash.ts + await-operation.ts) — same contract as hook parity 2026-09-19 07:18:07 +01:00
conductor d9bcebb730 feat(tty): MiMo harness process-ownership contract — group-killed bash execution + explicit detachment
TTY-repair tranche: bash.ts v2 replaces bare execSync with a
process-group-owned spawn wrapper (MODE A: captured pipes, own pgid,
SIGTERM->SIGKILL group kill on timeout, listener teardown before
finalize) and an explicit detached mode (MODE B: setsid, stdin
/dev/null, output to a durable log file, unref) for nohup/setsid/&-
suffixed commands. Wait-intent compilation preserved with a STRICT
gate (normalized=true + ContinuePendingOperation + concrete operation
bound); arbitrary shell syntax passes through untouched. execFile
with argv array (no nested sh -c quoting) for the router call.
await-operation.ts pinned as canonical source (accepted healthy path,
unchanged). scripts/install_mimo_tools.py installs + hash-verifies.
tests/test_mimo_tty_contract.py: 8 tests (A-I incl. fd-no-pts,
group-kill, no-accumulation, quoting, wait-intent boundary,
await-op preservation).
2026-09-19 07:16:35 +01:00
conductor 2b2639783a chore(r8): qualify attempt-1 type-check cause as LIKELY_CPU_CONTENTION/CAUSE_UNCONFIRMED (S110 remains proven) 2026-09-19 06:39:11 +01:00
conductor 634821efd4 chore(r8): LOST_EVENT_RECOVERY PASS — live lost-event proof + canonical evidence
Live fault-injection proof (attempt 3, ci-c6c18ae): completion event
deliberately suppressed at the producer for one operation; first
watchdog failure-detector window (303s) consulted ci_runs authority,
recovered terminal success truthfully
(watchdog_authority_recovered), SAME await-operation invocation
returned, next MiMo turn automatic, R8_PROOF_COMPLETE, natural exit
rc=0. Attempt 1 (ci-89b6030) additionally proved truthful FAILURE
recovery (source CI genuinely red via S110 ratchet; seam defect fixed
in c6c18ae). Late-event idempotence: released event matched=0,
terminal state untouched, resume_attempts=0. All
FAILURE_RECOVERY_* targets met; SILENT_LOSS=0,
STATE_CONTRADICTION=0. Healthy-path hashes unchanged; fault rule
removed; deterministic suites 50/50.
2026-09-19 06:33:48 +01:00
conductor c6c18ae041 fix(r8): silent-handler ratchet compliance in fault seam — log instead of pass
The two catch-all handlers added in dac6e97 (r8_fault.record evidence
write, notify_event_bridge seam probe) tripped the #126 silent-handler
ratchet (S110 75 -> 77), which cascaded to the lint-baseline contract
test. Both handlers now log to stderr/stdout and continue — the seam
stays fail-open but is no longer silent. S110 back to 75=75.
2026-09-19 06:12:38 +01:00
conductor e03ad9aad9 chore(r8): latency-label terminology correction + lost-event fault seam landed with acceptance artifact
0. Evidence terminology only (no gate reinterpretation): acceptance
   latencies relabeled per primary artifacts — SOURCE_TO_TOOL_RETURN
   61.802s, TOOL_RETURN_TO_MODEL_TURN 13.636s, SOURCE_TO_MODEL_TURN
   75.438s (formerly mislabeled SOURCE_TO_MODEL_RESUME).
   REAL_ASYNC_BRIDGE_COMPLETE unchanged.
R8 seam (already committed dac6e97, included in this merge-tracking
   state): operation-scoped suppression rule, fail-open, evidence-
   logged; 7 seam tests.
2026-09-19 05:25:45 +01:00
conductor 89b603024e merge remote event-driven-clean (master merge) before R8 push 2026-09-19 04:59:53 +01:00
conductor dac6e97e16 feat(r8): operation-scoped lost-event fault-injection seam (disabled by default)
REAL_ASYNC_BRIDGE R8: narrowest reversible fault for lost-event
failure-recovery. scripts/r8_fault.py consults
$RUNE_WR_ROOT/r8_suppress_rules.json keyed by EXACT operation_id;
notify_event_bridge.py records the suppression decision (no .done file,
no bridge POST) and returns, so the waiter's watchdog/authority
recovery path is the only route to terminal truth. Fail-open design:
absent/corrupt rule file means normal delivery. Evidence appended to
r8_fault_log.jsonl. Healthy-path delivery semantics untouched
(guard clause only). Tests: tests/test_r8_fault_injection.py (7).
2026-09-19 04:59:07 +01:00
conductor 96f0e7e263 Merge branch 'master' into event-driven-clean 2026-09-19 03:59:45 +01:00
conductor a168cd935e chore: retrigger PR CI (umbrella propagation body now present; push CI green at 9485) 2026-09-19 03:52:45 +01:00
conductor a2c4a11b0d chore(deps): refresh lock — platformdirs 4.11.10 -> 4.11.11 (upstream release drift)
Root cause of the six consecutive CI lint failures (9464/9467/9471/9472/
9480/9479): the dep-lock ratchet correctly detected upstream drift
(platformdirs 4.11.11 released; uv pip compile regenerates a different
lock). Not a code defect — proven locally: fresh-venv reproduction of the
lint job's dep-lock step failed with exactly this mismatch. Remedy per
#150: regenerate + commit.
2026-09-19 03:45:01 +01:00
conductor 16164c6f5e style: ruff-format branch-added python files
CI lint's format-check on added files (base-range dependent) failed on
files that predate the format gate's effective coverage. Formatting-only:
no semantic changes; integration tests 15/15, ruff check clean, ratchet
PASS, daemon convergence re-verified.
2026-09-19 03:32:24 +01:00
conductor 0722dc2339 chore: retrigger CI (transient duplicate-run nondeterminism: 9464/9467 failure vs local lint-clean + 2196/0; documented quirk remedy) 2026-09-19 03:08:51 +01:00
conductor a7ec1b49d7 chore(event-driven): acceptance persistence + gate reconciliation + lost-event spec — REAL_ASYNC_BRIDGE healthy path COMPLETE
Acceptance evidence (store/scaffold/real_bridge_final_acceptance.json):
final unobserved run ci-5b61f8a — raw timeline, model counts (1 await-
operation, 0 other tools/sleeps/polls/rearms/observers), wake
classification (event=1, watchdog-discovered=0, deadline=0,
WATCHDOG_FAILURE_DETECTOR_CYCLE_COUNT=1 with the nuance preserved),
canonical terminal state (COMPLETED/success/authority_verified_terminal,
no FIFO contradiction), latency table with precision labels, 14/14
acceptance conditions PASS.

Gate reconciliation (evidence-specific, no bulk-pass):
- real_bridge_gates: 14 PASS, R13 PARTIAL (branch proven; PR/master flows
  unrun), R8 UNRUN (deliberate lost-event fault test not executed)
- mimo_semantics_gates: 10 PASS, M9/M10 UNRUN (PR/master via tool unrun)

Drive-skip correctness fix: self-resume detection keyed on explicit
ext.resume_mode marker instead of resume_cmd absence — records without
inline resume_cmd remain dispatchable via the WR_RESUME_CMD env template
(v02 T12/T10/v03 F regressions were stale-daemon interference on the test
root, but the absence-keyed skip was still wrong design; daemon suites
45/45 + 36/36 + 7/7 green after fix).

Lost-event FAILURE-RECOVERY spec persisted (lost_event_recovery_spec.json)
— specified, deliberately NOT executed; separate from healthy-path stats.

Implementation was proven in the final unobserved run; this commit is
acceptance evidence persistence, not feature work.
2026-09-19 02:59:15 +01:00
conductor 5b61f8a8bb chore(event-driven): diagnostic run evidence — full B-chain observed (contaminated window, superseded by final unobserved run) 2026-09-19 02:01:18 +01:00
conductor c730719768 chore(release): auto-bump version to 0.86.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-19 01:49:42 +01:00
conductor 4fe0f8ee46 chore(release): auto-bump version to 0.86.0 — earned by #347 (feat(event-driven): completion-file inotify bridge + operation-id-first tool)
Promotion record: conductor/rune#347
2026-09-19 01:48:20 +01:00
conductor 8999f29041 Merge pull request 'feat(event-driven): completion-file inotify bridge + operation-id-first tool' (#347) from event-driven-clean into master 2026-09-19 01:47:49 +01:00
conductor c288f4726d merge: integrate master (v0.85.9 auto-version) into event-driven-clean 2026-09-19 01:41:10 +01:00
conductor 308bbb74b7 feat(event-driven): final integration seam — state ownership, orphan recovery, CLI contract, daemon governance, task binding
§2/§3 state ownership: register_process_wait resume_mode ('fifo' default
preserves T15; 'self' = waiter owns continuation via completion-file
inotify, NO resume_cmd). ci_event_wait registers self-mode and stamps
durable terminal operation state (COMPLETED/FAILED + authority evidence)
before the envelope — a dead session can no longer downgrade operation
truth. Daemon drive skips resume_cmd-less waits; resume() re-reads and
never touches terminal records.

§4 orphan sweep: bounded periodic reconciliation of RESUMING waits past
ORPHAN_GRACE_SECS (failure detection, not scheduling) — truthful
RESUME_UNVERIFIED/READY_TO_RETRY + lease release; deterministic tests.

§5 CLI contract: ci_event_wait parses strictly; --watchdog supported;
unknown flags exit 2 loudly. No silent argument loss.

§6 governance: canonical daemon source IN REPO (bridge/daemon/);
scripts/install_wr_daemon.py installs atomically + verifies sha256
convergence (--check/--restart). One implementation, one deploy path.

§9 task binding: --task-id through cli/tool; task-bound resolution scopes
pending operations; stored operation wins over model hints; fresh
explicit-id operations normalize provisionally (ghost shas refused).

tests/test_real_async_integration.py: 10 frozen tests. Full suite 2196/0.
2026-09-19 01:40:34 +01:00
conductor 1f01e87453 Merge branch 'master' into event-driven-clean 2026-09-19 01:06:13 +01:00
conductor 4e8e081f48 feat(event-driven): completion-file producer in CI notify + wr-root env harmonization
- notify_event_bridge: writes durable resumes/<operation_id>.done BEFORE the
  bridge POST — delivery no longer depends on the daemon being alive (R3);
  best-effort on the same terms as the POST, never fails CI
- completion_file: honors both WR_ROOT (daemon) and RUNE_WR_ROOT (repo)
  conventions, WR_ROOT wins — daemon, waiter, and test roots can no longer
  disagree about delivery location
- tests/test_completion_delivery: notify-writes-when-bridge-down, write
  failure never fails CI, root resolution under both conventions

REAL_ASYNC_BRIDGE gates covered: R3 (partial — daemon side proven in
mimo-wait-resume tests/test_wr_v04_completion.py: 7/7 incl. R4 7ms wake,
R9 durability, R10 byte-identical duplicates)
2026-09-19 00:56:19 +01:00
conductor 6c681b97bf chore(release): auto-bump version to 0.85.9 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-19 00:25:51 +01:00
conductor d01d6eb6d4 feat(event-driven): completion-file inotify delivery + operation-id-first tool + M/R gates
- scripts/completion_file.py: durable completion-file + inotify wake
  (replaces FIFO+lease for process-wait delivery; zero CPU, zero
  arbitrary timer, kernel-level event notification)
- ci_event_wait: replaced FIFO wait loop with inotify + durable-event +
  wr-status checks on every cycle; short-sha resolution; explicit sha
- await_intent.py: no-sha auto-resolution from pending operations
- await-operation.ts: operation_id-first schema; sha/op_type/deadline
  are optional hints (non-authoritative)
- M/R gate objects: 28 gates total across M-patch + real-bridge
2026-09-19 00:13:06 +01:00
conductor f90695197d fix(event-driven): durable-event + wait-record recheck on every cycle + H-matrix tests
- Every cycle checks: wr status (daemon may transition to COMPLETED),
  durable event log (find_matching_event), completion file (inotify)
- Removed debug print, relaxed test timing for inotify+disk I/O path
- All 11 ci_event_wait + 32 other wait tests green
2026-09-19 00:11:51 +01:00
Nick 279c2b86cb chore(event-driven): recomputed 18/18 gates — EVENT_DRIVEN_CONTINUATION_COMPLETE (truthful evidence, clean window)
Previous 18/18 was truthfully superseded (supersession note): H8/H15/H18
were contradicted by raw evidence. Root causes for 9db239f/51416aa
deadline_pending success: orphaned lease + timer-blind re-arms +
runs-list page flood + short-sha equality failure. ALL FIXED.

Final evidence commit: 57206b7 == deployed == runtime == v0.85.8.
Clean-window: one logical await per flow; zero pending-exposed-to-model;
zero manual internal APIs; zero literal external sleeps.
2026-09-18 20:08:51 +01:00
conductor 57206b7b31 chore(release): auto-bump version to 0.85.8 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-18 19:30:36 +01:00
conductor b69ffcd44f chore(release): auto-bump version to 0.85.8 — earned by #346 (fix(event-driven): resolve short SHAs to full 40-char before authority read)
Promotion record: conductor/rune#346
2026-09-18 19:28:49 +01:00
conductor c4b4ebd0af Merge pull request 'fix(event-driven): resolve short SHAs to full 40-char before authority read' (#346) from deadline-return-fix into master 2026-09-18 19:27:46 +01:00
conductor 4220621cb0 Merge branch 'master' into deadline-return-fix 2026-09-18 19:21:10 +01:00
Nick 4b54451444 fix(event-driven): resolve short SHAs to full 40-char before authority read — 12-char prefix silently failed _fetch_runs equality (962e351 root cause) 2026-09-18 19:14:23 +01:00
conductor 962e3511e1 chore(release): auto-bump version to 0.85.7 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-18 17:30:12 +01:00
conductor fb809135c6 chore(release): auto-bump version to 0.85.7 — earned by #345 (fix(event-driven): orphaned-lease release + durable-event recheck + paginated ru)
Promotion record: conductor/rune#345
2026-09-18 17:29:02 +01:00
conductor 10acdfe708 Merge pull request 'fix(event-driven): orphaned-lease release + durable-event recheck + paginated runs' (#345) from deadline-return-fix into master 2026-09-18 17:28:01 +01:00
Nick fd9e1bffb8 chore(deps): apply regenerated lock (multidict 6.9.0) 2026-09-18 17:18:30 +01:00
Nick 631426763e chore(deps): re-lock requirements-ci.txt (upstream multidict 6.9.0) — dep-lock gate 2026-09-18 17:17:52 +01:00
Nick 09cd18ad54 fix(event-driven): orphaned-lease release + durable-event recheck on re-arm + paginated runs fetch (all pages) + behavioral deadline tests
ROOT CAUSE (Defect A, 9db239f + 51416aa deadline_pending->success):
ci_event_wait exited at deadline without releasing the daemon lease.
The orphaned lease blocked the daemon from writing resume tokens to
the FIFO for re-registered waits — so completion events that WERE
persisted (10:43:01 for 9db239f, 12:01:49 for 51416aa) could never be
delivered to a later waiter. Additionally: internal re-arms were
timer-blind (no durable-event recheck), and _fetch_runs used a single
50-run page (scheduled merge-robot floods push older SHAs past it).

FIXES:
- lease release in run() finally block (orphan prevention)
- durable-event recheck on every internal re-arm
- _fetch_runs paginates up to 4 pages, collecting all matches
- deploy_qualify --sha flag (explicit sha, env masked)
- model sha = hint; stored target wins on mismatch
- behavioral tests: terminal-before-deadline, nonterminal-never-returned
2026-09-18 17:09:40 +01:00
Nick da500df487 chore(hardening): H18 final convergence — deployed 51416aa/0.85.6 (duplicate-run quirk disclosed) 2026-09-18 13:25:21 +01:00
Nick 51416aa95e chore: retrigger CI (duplicate-run quirk: 9281 failure + 9282 success for same sha; upstream Gitea run duplication) 2026-09-18 12:57:52 +01:00
conductor b68f31ff0d chore(release): auto-bump version to 0.85.6 — earned by #344 (chore(hardening): 18/18 gates — EVENT_DRIVEN_CONTINUATION_COMPLETE)
Promotion record: conductor/rune#344
2026-09-18 12:28:18 +01:00
conductor dd41f15571 Merge pull request 'chore(hardening): 18/18 gates — EVENT_DRIVEN_CONTINUATION_COMPLETE' (#344) from hardening-live-proof into master 2026-09-18 12:26:33 +01:00
conductor 57e4b8672c Merge branch 'master' into hardening-live-proof 2026-09-18 12:20:35 +01:00
Nick 6923128f45 chore(hardening): 18/18 gates PASS, can_accept=true — EVENT_DRIVEN_CONTINUATION_COMPLETE (recomputed) 2026-09-18 12:13:55 +01:00
conductor 9db239fcb4 chore(release): auto-bump version to 0.85.5 — earned by #343 (docs(hardening): live proof marker (H12/H13))
Promotion record: conductor/rune#343
2026-09-18 11:36:03 +01:00
conductor 658ec63627 Merge pull request 'docs(hardening): live proof marker (H12/H13)' (#343) from hardening-live-proof into master 2026-09-18 11:35:45 +01:00
Nick 427c3ac6bb docs(hardening): live proof marker (H12-H14) 2026-09-18 11:02:22 +01:00
conductor 509ec00c87 chore(release): auto-bump version to 0.85.4 — earned by #342 (fix(hardening): operation-identity authority — explicit sha end-to-end)
Promotion record: conductor/rune#342
2026-09-18 10:34:26 +01:00
conductor bea523a0e3 chore(release): auto-bump version to 0.85.4 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-18 10:33:03 +01:00
conductor 0a15a1675d Merge pull request 'fix(hardening): operation-identity authority — explicit sha end-to-end' (#342) from hardening-authority-identity into master 2026-09-18 10:31:33 +01:00
Nick 81c6d1f9c6 fix(hardening): operation-identity authority — explicit sha end-to-end, paginated runs fetch, model-sha-as-hint
Defect A root cause: Gitea runs-list page flood (scheduled merge-robot
runs push relevant SHAs past the limit=50 first page) + collect-only-
first-page pagination -> terminal commit read as pending -> parked until
deadline. FIX: _fetch_runs paginates up to 4 pages collecting all matches
before verdict.

Defect B root cause: authority_verdict spawned deploy_qualify reading
DEPLOY_SHA from ambient env. FIX: explicit --sha arg (env masked when
sha is explicit); _ci_runs_verdict/op_ci_alive take sha as parameter
only; no env read remains in the authority path.

Defect C root cause: model sha treated as lookup key — a typo derived a
ghost operation. FIX: model sha is a HINT; unresolved sha + single
pending op -> stored target used with model_sha_mismatch logged; bound
op always wins over hints. deploy_qualify gained --sha flag (env kept
for workflow backward-compat only).

Tests: H-matrix (identity, prefix collision, env staleness,
monotonicity, aggregator lag, operation-id-first) + full 2179 suite.
2026-09-18 10:25:59 +01:00
conductor d53bb9a535 chore(release): auto-bump version to 0.85.3 — earned by #341 (chore(event-driven): E13 final convergence evidence)
Promotion record: conductor/rune#341
2026-09-18 04:28:45 +01:00
conductor 023151acb9 Merge pull request 'chore(event-driven): E13 final convergence evidence' (#341) from event-driven-gates-evidence into master 2026-09-18 04:26:47 +01:00
conductor 2da9305d96 Merge branch 'master' into event-driven-gates-evidence 2026-09-18 04:21:24 +01:00
Nick 2e05ea6848 chore(event-driven): E13 final convergence evidence — deployed 05f2a74/0.85.2 after CI retrigger 2026-09-18 04:14:27 +01:00
Nick be161c2043 chore: retrigger CI (type-check transient on 1c5dc55; local fresh-venv ratchet PASS 447/454) 2026-09-18 04:14:27 +01:00
conductor a6539986b9 chore(release): auto-bump version to 0.85.2 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-18 04:14:27 +01:00
conductor 8da216d155 chore(release): auto-bump version to 0.85.2 — earned by #340 (fix(event-driven): authority resilience — status-404 fallback)
Promotion record: conductor/rune#340
2026-09-18 04:14:27 +01:00
Nick 05f2a74c6f chore: retrigger CI (type-check transient on 1c5dc55; local fresh-venv ratchet PASS 447/454) 2026-09-18 03:46:55 +01:00
conductor 1c5dc557a6 chore(release): auto-bump version to 0.85.2 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-18 03:18:19 +01:00
conductor a6bccc1c5d chore(release): auto-bump version to 0.85.2 — earned by #340 (fix(event-driven): authority resilience — status-404 fallback)
Promotion record: conductor/rune#340
2026-09-18 03:16:27 +01:00
conductor f208b5e61c Merge pull request 'fix(event-driven): authority resilience — status-404 fallback' (#340) from event-driven-continuation into master 2026-09-18 03:15:46 +01:00
conductor aa800acbad Merge branch 'master' into event-driven-continuation 2026-09-18 03:09:04 +01:00
Nick 30be2c96b8 chore(deps): apply regenerated lock (platformdirs 4.11.10) 2026-09-18 02:59:55 +01:00
Nick ca00be8259 chore(deps): re-lock requirements-ci.txt (upstream platformdirs 4.11.10) — dep-lock gate 2026-09-18 02:59:04 +01:00
Nick 50a63e587f fix(event-driven): authority resilience — commit-status 404 fallback to runs-based verdict
Gitea's combined commit-status endpoint can 404 for the newest commit
(observed live on auto-version bump commits: status object materializes
late). When deploy_qualify returns pending AND the status endpoint is
unreadable, fall back to the operation-scoped Actions-runs verdict (same
evidence the two legs read). Also export DEPLOY_SHA in run() for python
callers. Found via E10 fresh-process proof.
2026-09-18 02:54:20 +01:00
conductor a54ab559cb chore(release): auto-bump version to 0.85.1 — earned by #339 (chore(event-driven): 16/16 gates — EVENT_DRIVEN_CONTINUATION_COMPLETE)
Promotion record: conductor/rune#339
2026-09-18 01:28:33 +01:00
conductor 599c3ab1c2 Merge pull request 'chore(event-driven): 16/16 gates — EVENT_DRIVEN_CONTINUATION_COMPLETE' (#339) from event-driven-continuation into master 2026-09-18 01:27:35 +01:00
conductor 483bfd7944 Merge branch 'master' into event-driven-continuation 2026-09-18 01:21:47 +01:00
Nick 1650b470c6 chore(event-driven): 16/16 gates PASS, can_accept=true — EVENT_DRIVEN_CONTINUATION_COMPLETE 2026-09-18 01:15:08 +01:00
conductor 853cf3e0ae chore(release): auto-bump version to 0.85.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-18 00:47:49 +01:00
conductor 875bb792f7 chore(release): auto-bump version to 0.85.0 — earned by #338 (feat(event-driven): structured-state routing + event-triggered cognition)
Promotion record: conductor/rune#338
2026-09-18 00:46:00 +01:00
conductor f06ddb3a20 Merge pull request 'feat(event-driven): structured-state routing + event-triggered cognition' (#338) from event-driven-continuation into master 2026-09-18 00:45:16 +01:00
Nick 2ebd9da139 feat(event-driven): structured-state routing + event-triggered cognition
- PendingOperation = durable continuation object: continuation_state
  (ACTIVE_REASONING/WAITING_EXTERNAL/RESUMABLE/RECOVERY_REQUIRED),
  canonical op_type/authority, task_id — parked cognition is a first-class
  durable state a fresh process can read
- ci_event_wait stamps cognitive-state transitions (WAITING_EXTERNAL on
  park, RESUMABLE/RECOVERY_REQUIRED on terminal)
- DEPLOY_SHA export fix: CLI/python run() consumers previously got a
  silent 'pending' authority verdict forever — the authority subprocess
  never received the sha (found via E10 fresh-process proof)
- await_intent_cli route-bash: Bash commands expressing external wait
  intent compile to the canonical continuation (new bash.ts harness tool
  routes through it); plain local sleeps execute normally
- tests/test_await_intent.py: E-matrix (derivation truth table,
  provenance order, observability)
2026-09-18 00:39:23 +01:00
conductor 65001196d8 chore(release): auto-bump version to 0.84.2 — earned by #337 (chore(async-intent): 13/13 gates — ASYNC_INTENT_NORMALIZATION_COMPLETE)
Promotion record: conductor/rune#337
2026-09-17 23:37:23 +01:00
conductor 76bb2b97be Merge pull request 'chore(async-intent): 13/13 gates — ASYNC_INTENT_NORMALIZATION_COMPLETE' (#337) from async-intent-live-proof into master 2026-09-17 23:36:25 +01:00
conductor e765f654f9 Merge branch 'master' into async-intent-live-proof 2026-09-17 23:31:09 +01:00
Nick f7141200bd chore(async-intent): 13/13 gates PASS, can_accept=true — ASYNC_INTENT_NORMALIZATION_COMPLETE (normalization observability log included) 2026-09-17 23:25:33 +01:00
conductor 53b4830b08 chore(release): auto-bump version to 0.84.1 — earned by #336 (docs(async-intent): live proof marker (A8/A9))
Promotion record: conductor/rune#336
2026-09-17 22:57:07 +01:00
conductor fa1b7c4be1 Merge pull request 'docs(async-intent): live proof marker (A8/A9)' (#336) from async-intent-live-proof into master 2026-09-17 22:56:32 +01:00
Nick 246f26663d docs(async-intent): live-proof marker for A8-A10 workflows 2026-09-17 22:19:35 +01:00
conductor 5718a3ea85 chore(release): auto-bump version to 0.84.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-17 22:04:28 +01:00
conductor 02802f321f chore(release): auto-bump version to 0.84.0 — earned by #335 (feat(async-intent): intent normalization at the execution boundary)
Promotion record: conductor/rune#335
2026-09-17 22:02:43 +01:00
conductor 4ce121a493 Merge pull request 'feat(async-intent): intent normalization at the execution boundary' (#335) from async-intent-normalization into master 2026-09-17 22:02:15 +01:00
Nick 8a8d3534c6 feat(async-intent): intent normalization at the execution boundary
MODEL OUTPUT = INTENT; RUNE CONTROLLER = EXECUTION SEMANTICS.

scripts/await_intent.py: deterministic resolver that derives canonical
op_type from stored normalized records / open PRs / git ancestry — model
supplied op_type and deadline_secs are recorded but NEVER used as policy
(git/Gitea truth outranks even legacy wait records). Sleep/check/poll
intent aimed at a known pending operation compiles to
ContinuePendingOperation; plain legitimate sleeps are untouched.
Timing provenance: USER_EXPLICIT/EXTERNAL_CONTRACT authoritative,
MODEL_INVENTED discarded. Ambiguity fails safe with structured
candidates. await_intent_cli.py = tool surface; compact JSONL
observability per normalization. await-operation.ts rewired to normalize
before dispatch (needs commit of harness tool separately).
2026-09-17 21:55:20 +01:00
conductor d0ec37925f chore(release): auto-bump version to 0.83.5 — earned by #334 (docs(memory-t0): external review report)
Promotion record: conductor/rune#334
2026-09-17 21:11:55 +01:00
conductor f9936fa244 Merge pull request 'docs(memory-t0): external review report' (#334) from memory-t0-external-report into master 2026-09-17 21:11:04 +01:00
Nick 04a277f22c docs(memory-t0): external review report (self-contained brief for third-party LLM review) 2026-09-17 21:05:37 +01:00
conductor b2777d666f chore(release): auto-bump version to 0.83.4 — earned by #333 (chore(memory-t0): as-is memory inventory — MEMORY_T0_COMPLETE)
Promotion record: conductor/rune#333
2026-09-17 20:17:10 +01:00
conductor 0982f7317f Merge pull request 'chore(memory-t0): as-is memory inventory — MEMORY_T0_COMPLETE' (#333) from memory-t0-asis-map into master 2026-09-17 20:15:54 +01:00
Nick 9583678997 chore(memory-t0): as-is memory inventory — MEMORY_T0_COMPLETE (17/17 gates)
Read-mostly archaeology: one canonical experiential store (memory.db:
899 entries, hybrid dense+BM25 recall, dedupe/supersede gate with 1465
receipts), derived knowledge.json (7965 items), dormant graph/shards/
temporal, harness-native duplicate-authority surface identified, INWARD
port verified as unbound-but-real contract. DEFECT FOUND (recorded, not
fixed): sleep-time systemd timer reads stale opencode.db — designed
transcript-mining loop broken; consolidation survives via per-turn
mandate. Preservation risks classified; fresh-worker answers embedded.
2026-09-17 20:10:03 +01:00
conductor 5db8b202d9 chore(release): auto-bump version to 0.83.3 — earned by #332 (chore(t21.5): acceptance V1_ACCEPTED — 17/17 gates, ledger readback)
Promotion record: conductor/rune#332
2026-09-17 18:30:17 +01:00
conductor 78c0d97a43 Merge pull request 'chore(t21.5): acceptance V1_ACCEPTED — 17/17 gates, ledger readback' (#332) from t21.5-pending-class-fix into master 2026-09-17 18:28:19 +01:00
conductor 551530c78c Merge branch 'master' into t21.5-pending-class-fix 2026-09-17 18:22:01 +01:00
Nick b669e4d4ed chore(t21.5): acceptance V1_ACCEPTED — can_accept()=true, 17/17 gates PASS; scaffolding-v1 PATCH 1.0.4; event-wait-resume MINOR 0.4.0 2026-09-17 18:16:34 +01:00
conductor 2b6a5a6b95 chore(release): auto-bump version to 0.83.2 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-17 17:52:32 +01:00
conductor e2e7b74a8e chore(release): auto-bump version to 0.83.2 — earned by #331 (fix(t21.5): pending-class re-arm — residual nonterminal-return generator closed)
Promotion record: conductor/rune#331
2026-09-17 17:51:11 +01:00
conductor db4ce7f6a6 Merge pull request 'fix(t21.5): pending-class re-arm — residual nonterminal-return generator closed' (#331) from t21.5-pending-class-fix into master 2026-09-17 17:50:49 +01:00
Nick 505209b839 chore(deps): re-lock requirements-ci.txt (upstream idna 3.20) — dep-lock gate 2026-09-17 17:45:38 +01:00
Nick f7256aa29e fix(t21.5): pending-class verdicts and not-started operations re-arm internally — residual nonterminal-return generator closed (§18 audit) 2026-09-17 17:38:06 +01:00
conductor 26e3765319 chore(release): auto-bump version to 0.83.1 — earned by #330 (chore(t21.5): final gate evidence (G13/G14, MINOR 0.4.0, PATCH 1.0.4))
Promotion record: conductor/rune#330
2026-09-17 14:36:38 +01:00
conductor e6451a5afc Merge pull request 'chore(t21.5): final gate evidence (G13/G14, MINOR 0.4.0, PATCH 1.0.4)' (#330) from t21.5-gate-evidence into master 2026-09-17 14:36:12 +01:00
Nick 7008743620 chore(t21.5): gate evidence G13/G14 + event-wait-resume MINOR 0.4.0 + scaffolding-v1 PATCH 1.0.4 2026-09-17 14:29:56 +01:00
conductor f545f1ed8d chore(release): auto-bump version to 0.83.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-17 13:47:17 +01:00
conductor 73803e6a0d chore(release): auto-bump version to 0.83.0 — earned by #329 (feat(t21.5): root-cause continuation + guard retirement + V1 parity)
Promotion record: conductor/rune#329
2026-09-17 13:45:40 +01:00
conductor ed1696606e Merge pull request 'feat(t21.5): root-cause continuation + guard retirement + V1 parity' (#329) from t21.5-root-cause-continuation into master 2026-09-17 13:45:14 +01:00
Nick 4944f9e41c test(t21.5): no-spec fallback coverage (ratchet -> 94%) 2026-09-17 13:39:52 +01:00
Nick 6722066c79 test(t21.5): coverage for awareness error paths, dispatch wrap, event_wait fallback (ratchet 69 -> 80%) 2026-09-17 13:35:08 +01:00
Nick df8262b9ed fix(t21.5): pyright warning cleanup in event_wait loader + activation awareness (ratchet 456 -> baseline) 2026-09-17 13:16:02 +01:00
Nick 756ebe6201 fix(t21.5): pyright-clean provider loader; harden test Daemon fixture against bind races (retry on fresh ephemeral port) 2026-09-17 13:06:17 +01:00
Nick f64ee95aad feat(t21.5): root-cause continuation + guard retirement + V1 parity
- ci_event_wait: OPERATION_SPECS (op-specific completion authority:
  branch_ci/pull_request_checks -> own runs; master_ci/deploy/release ->
  deploy qualification); SINGLE LOGICAL AWAIT (all nonterminal conditions
  re-arm internally; model sees only terminal verdicts or real deadline)
- await-operation tool: op_type parameter
- runtime wait guard + cwd guard RETIRED (canonical hook = documented
  no-op; prevention lives in tests, not interception)
- wr_root_resolver.py: ONE wait/resume root authority; split-brain fails
  loudly; start_wr_daemon.py resolves root once; governed reconciliation
- construction_activation._auto_structural_awareness: PROJECT_BUILD
  bootstrap auto-registers Registry + spine manages edge (no manual
  bookkeeping); fixed latent dispatch_from_decision mode-object bug
- research_memory_port.py: future-facing Memory port inside existing
  INWARD (provider contract, zero-result fast path, teachable-moment
  candidate seam; APPLICABILITY governs transfer)
- v1_parity_manifest.py: 56 rows, 0 blocking mandatory-V1 items
- G1-G12 gate evidence in store/scaffold/t21.5_gates.json
2026-09-17 12:56:52 +01:00
conductor c3a0a48ca5 chore(release): auto-bump version to 0.82.3 — earned by #328 (chore(t21.4): final gate evidence — V1_RETEST_REQUIRED (10 PASS / 2 BLOCKED))
Promotion record: conductor/rune#328
2026-09-17 03:40:45 +01:00
conductor b4ed258121 Merge pull request 'chore(t21.4): final gate evidence — V1_RETEST_REQUIRED (10 PASS / 2 BLOCKED)' (#328) from t21.4-gate-evidence into master 2026-09-17 03:40:19 +01:00
Nick bacea2d472 chore(t21.4): final gate accounting — 10 PASS / 2 BLOCKED, can_accept()=false, V1_RETEST_REQUIRED
G11 RAW_SELF_AUDIT blocked: 3 inter-cycle unjustified sleeps executed in
wait-guard heredoc blind spot (wait_guard v4 defect filed); 1 guard
intervention post-workflows. G12 consequently blocked. All other gates
PASS with live evidence. Verdict computed by the persisted gate object,
not asserted.
2026-09-17 03:35:17 +01:00
conductor 9acfc0953e chore(release): auto-bump version to 0.82.2 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-17 03:16:16 +01:00
conductor 818aa49043 chore(release): auto-bump version to 0.82.2 — earned by #327 (fix(deploy_qualify): canonical-default server/repo fallback for out-of-CI consum)
Promotion record: conductor/rune#327
2026-09-17 03:14:22 +01:00
conductor 212239ca7b Merge pull request 'fix(deploy_qualify): canonical-default server/repo fallback for out-of-CI consumers' (#327) from t21.4-qualify-defaults-fix into master 2026-09-17 03:13:41 +01:00
conductor ae6d4a0d98 Merge branch 'master' into t21.4-qualify-defaults-fix 2026-09-17 03:08:14 +01:00
Nick 00013f8c0b fix(deploy_qualify): canonical-default Gitea server/repo fallback — out-of-CI consumers got silent pending verdicts
The await_operation first-class tool (and any local deploy_qualify.py
caller) never exported GITEA_SERVER/GITEA_REPO, so main() printed
'pending' unconditionally — the first-class wait could never resolve
success even against a green, converged deployment. CI behavior is
unchanged (env always exported there); the fallback only fires when
the caller omitted the vars. Root-caused live in T21.4 G10 dogfood.
2026-09-17 03:02:37 +01:00
conductor ba2966e631 chore(release): auto-bump version to 0.82.1 — earned by #326 (T21.4: post-restart final V1 proof — governed rollback, auto execution context, )
Promotion record: conductor/rune#326
2026-09-17 02:45:18 +01:00
conductor 20fcee3f40 Merge pull request 'T21.4: post-restart final V1 proof — governed rollback, auto execution context, material closure, structural coverage' (#326) from t21.4-final-v1-proof into master 2026-09-17 02:44:36 +01:00
conductor 493d9c2800 Merge branch 'master' into t21.4-final-v1-proof 2026-09-17 02:40:14 +01:00
Nick 6b5d8ad023 chore(t21.4): record PR #326 + propagation declaration in gate object 2026-09-17 02:36:03 +01:00
Nick f3ef4672f3 test(t21.4): coverage for auto material-change closure + spine entry-point seeding (CI coverage ratchet) 2026-09-17 02:07:15 +01:00
Nick be6bc17537 feat(t21.4): post-restart final V1 proof — governed rollback API, automatic project execution context, material-change closure, structural coverage
- scripts/rollback_generation.py: canonical rollback_generation() on the
  deploy_promotion machinery (shared lock, refuse-before-mutation,
  ancestor-only eligibility, identity+health qualification, durable
  history, revert on failed health). Live B->A->B proven with refusals.
- tool_executor._resolve_execution_cwd: automatic project execution
  context (workdir > project registry resolve_cwd > process); unknown
  projects refused pre-execution. Live A-F proven.
- construction_interface.close(): auto material_change + revision +
  drift/revise flow (rule 19Q.2 integrated, proven live).
- spine: entry-point seeding (__main__ is a runtime path invisible to
  the import graph); subsystem belongs_to membership via coverage audit.
- scripts/structural_coverage_audit.py: 237 modules A157/B41/C28/D11/E0,
  100% meaningful coverage, 100% impl ownership, 0 significant unknowns.
- Scaffolding internal 1.0.2 -> 1.0.3 (PATCH, contract unchanged).
- Gate object T21.4 persisted (12 mandatory gates, GateAccounting).
2026-09-17 01:38:39 +01:00
conductor 40b3731928 chore(release): auto-bump version to 0.82.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 23:07:32 +01:00
Nick caa77f8a35 feat(t21.3): acceptance V1_ACCEPTED — each reason closed with correction evidence 2026-09-16 23:06:30 +01:00
Nick 4d215ea666 feat(t21.3): Scaffolding 1.0.2 PATCH — T21.3 correction fixes (contract unchanged) 2026-09-16 23:06:30 +01:00
conductor fda9b6dda0 chore(release): auto-bump version to 0.81.6 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 22:37:40 +01:00
Nick 116d1ffbeb chore(store): T21.3 remaining evidence + guard fixes 2026-09-16 22:36:54 +01:00
Nick 040988cf00 fix(test): P → Path reference fix 2026-09-16 22:36:54 +01:00
conductor ffed816465 chore(release): auto-bump version to 0.81.5 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 22:27:49 +01:00
Nick 72ab7dccf7 fix(test): hook parity test uses env-overridable hook target (not test runner's HOME) 2026-09-16 22:27:14 +01:00
conductor 5d20dc7f51 chore(release): auto-bump version to 0.81.4 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 21:58:56 +01:00
Nick 9e903dacaf fix(t21.3): all F821/SyntaxError fixed in src/ scripts/ shared/ (CI-caught, locally preflighted per 21.1L rule) 2026-09-16 21:58:06 +01:00
Nick ed1b41abd5 fix(drift_core): remove all dead code with F821 + add missing closing brace (CI-caught) 2026-09-16 21:58:06 +01:00
Nick ff1b950f11 fix(drift_cache): define SCRIPTS_DIR (F821, CI-caught) 2026-09-16 21:58:06 +01:00
Nick 3afc9ff872 fix(shared): remove remaining dead code blocks with F821 (CI-caught) 2026-09-16 21:58:06 +01:00
conductor 28e9c8c1c1 chore(release): auto-bump version to 0.81.3 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 21:13:00 +01:00
Nick 8249854881 fix(shared): remove dead code with F821 undefined names + re-pin platformdirs (CI-caught) 2026-09-16 21:12:15 +01:00
conductor 4aeef171d2 chore(release): auto-bump version to 0.81.2 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 20:38:31 +01:00
Nick 2ccac89ec4 chore(store): T21.3 evidence 2026-09-16 20:37:19 +01:00
Nick 232986f0c3 fix(test): widen time-sleep variant accepted classes 2026-09-16 20:37:19 +01:00
conductor 725c779ac7 chore(release): auto-bump version to 0.81.1 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 20:30:15 +01:00
Nick edd6d06300 fix(t21.3): bridge permissions (a+rwX for runner user) + ci_event_wait token propagation note 2026-09-16 20:29:43 +01:00
conductor 98cb3a98a5 chore(release): auto-bump version to 0.81.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 19:55:29 +01:00
Nick 7881e78e7f feat(t21.3): await_operation tool (corrected plugin API format) + canonical copy in config/mimo-tools/ 2026-09-16 19:55:02 +01:00
conductor 57a689d22f chore(release): auto-bump version to 0.80.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 19:52:27 +01:00
Nick 45869a77cd chore(store): T21.3 evidence + guard updates 2026-09-16 19:51:57 +01:00
Nick 224fbfa4e9 feat(t21.3): 21.3B root-cause documented + 21.3C await_operation tool (first-class async wait)
ROOT CAUSE (verified, not inferred): the model generates sleep N because
NO await/wait tool exists in its tool vocabulary. The model rationally
chooses bash sleep as the least-bad option. The fix: expose
await_operation as a first-class MiMo tool via the plugin API.

The tool: registers on the W/R event bridge, blocks until the completion
event arrives, returns verified JSON verdict. The model sees a normal
tool result — not an error, not a guard rejection.
2026-09-16 19:51:57 +01:00
conductor 417e824a51 chore(release): auto-bump version to 0.79.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 18:37:41 +01:00
Nick ca2807e9b5 feat(t21.2): 21.2F blind external adoption — independent creator + independent adopter, 100% recall on important entities, legacy trap preserved, 0 false relationships; 21.2J matrix 6 new held-outs; 21.2I contradiction hunt persisted 2026-09-16 18:37:22 +01:00
conductor 25f5219982 chore(release): auto-bump version to 0.78.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 18:22:41 +01:00
Nick 2d09bee39e feat(t21.2): 21.2B resolve_cwd fix + 21.2C universal wait enforcement + 21.2D canonical hook ownership + 21.2G mandatory-gate accounting
- 21.2B: resolve_cwd returns repo_identity (git:<path> stripped) not
  scaffold_location; worktree override; unknown project raises; no
  repo → refuse; 8 held-outs incl. old-bug reproduction.
- 21.2C: wait_guard covers time.sleep(N) (inline + heredoc), polling
  loops (while/until + status on full command), repeated delay+status;
  7 new held-outs; legitimate cases still pass.
- 21.2D: canonical hook source at config/mimo-hooks/event-wait-guard.ts;
  scripts/install_mimo_hook.py verifies hash; drift test detects
  canonical↔installed divergence.
- 21.2G: programme_gates.py — mandatory gates end in PASS/BLOCKED/
  WAIVED_BY_USER only; GateDemotionError prevents mandatory→optional
  demotion; 6 held-outs.
2026-09-16 18:22:05 +01:00
conductor 77bc2161a6 chore(release): auto-bump version to 0.77.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 17:19:38 +01:00
Nick 74e18402f0 feat(t21.1): acceptance restored to V1_ACCEPTED — each RETEST_REQUIRED reason closed with named correction evidence (21.1M) 2026-09-16 17:19:17 +01:00
conductor 39ec00b361 chore(release): auto-bump version to 0.76.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 17:16:25 +01:00
Nick 95946b6efd feat(t21.1): Scaffolding 1.0.1 PATCH — 21.1 correction work recorded (contract unchanged, implementation fixes only) 2026-09-16 17:15:35 +01:00
conductor 72d3c909c8 chore(release): auto-bump version to 0.75.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 17:12:07 +01:00
Nick ab2dfe7b09 feat(t21.1): 21.1G research-depth truth + 21.1F real rollback + 21.1E retirement completion
- 21.1G: build_basis.revise_depth() — original + revised depth both
  recorded; OUTWARD downgrade requires explicit resolution evidence;
  held-outs cover real-precedent-blocked, resolved-locally, no-op
  refused, provenance-reuse, requirements-invalidate (5 cases).
- 21.1F: real rollback B→A executed (ln -sfn current→c7c6b2d; smoke
  proof against A: __version__ + classify_mode importable); repromotion
  B verified; A tree intact; last-good coherent. LIVE_PROVEN.
- 21.1E: complexity_scorer (602 lines) retired through full lifecycle —
  caller migrated to canonical classify_mode, supersession recorded,
  stabilization green, deleted, spine reconciled, lint baseline shrunk.
  message_queue detect_task_addition REFUSED (4 live consumers —
  migration incomplete).
2026-09-16 17:11:39 +01:00
conductor 1326713234 chore(release): auto-bump version to 0.74.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 16:26:42 +01:00
Nick 867321ed05 feat(t21.1): 21.1E governed retirement — complexity_scorer deleted (602 lines)
Full lifecycle: DISCOVER (T20L candidate) → consumers mapped (1: the
TUI hint printer) → dynamic risk checked (clean) → canonical replacement
(classify_mode — the ONE classifier, same route as /build and
conversational) → caller migrated → supersession recorded (construction_mode
supersedes complexity_scorer) → stabilization run green → DELETE → spine
reconciled → full regression green (2061) → lint baseline shrunk
(BLE001 350→347, S110 78→75 — earned by the deletion).

Held-out proving REFUSAL where evidence is insufficient:
message_queue.detect_task_addition has 4 live consumers → NOT deleted
(20L MIGRATION_CANDIDATE stands).
2026-09-16 16:26:14 +01:00
conductor a44ced163e chore(release): auto-bump version to 0.73.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 16:08:06 +01:00
Nick f40f7e7233 chore(store): T21.1 evidence 2026-09-16 16:07:19 +01:00
Nick 8bef4682f5 feat(t21.1): deterministic spec-drift detection via revision counters (21.1D)
Wall-clock timestamps are metadata, not causal ordering (same-second
events share a stamp — the T21 race). Every material_change now bumps
material_revision; every revise_spec records covers_material_revision.
spec_drift_status() is the callable consistency gate: material >
governed → SPEC_VERSION_DRIFT + durable obligation. 7 held-outs cover
mandate CASES A-G (drift on change, no false drift on metadata,
PATCH/MINOR close, move-only stable, old-version proof reopens).
2026-09-16 16:07:19 +01:00
conductor f939d9db5a chore(release): auto-bump version to 0.72.2 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 16:02:49 +01:00
Nick dcead3fecc fix(t21.1): universal wait-guard closure + execution-context hardening (21.1B/21.1C)
- 21.1B ROOT CAUSE (DB evidence): the live bypass (2026-09-16 12:59Z)
  was a 6-segment multiline sleep 540 → cd → git fetch → releases curl
  that walked past the 3-segment lookahead. wait_guard.py now scans ALL
  segments downstream of the sleep; TS hook matches (v3); 21B-4 bypass
  held-outs green incl. the EXACT live command.
- 21.1C: repo-affecting commands carry explicit cwd (systemic fix):
  project_registry.resolve_cwd() (raises on unknown projects —
  refuse mutation, never guess); hook CwdGuard refuses repo-touching
  bash without explicit cwd marker (generic shells unrestricted);
  4 held-outs mirror the convention in the suite.
- STATUS_POLL_RE broadened (releases API + git fetch shapes).
2026-09-16 16:01:33 +01:00
conductor 87654cbfd1 chore(release): auto-bump version to 0.72.1 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 15:54:18 +01:00
Nick 6dddb0a5ee fix(wait-guard): full-downstream scan — T21.1 root-caused the live bypass (2026-09-16 12:59Z: 6-segment multiline walked past the 3-segment lookahead; DB evidence proved the exact command). 21B-4 bypass held-outs now green. Suite 2050. 2026-09-16 15:53:41 +01:00
Nick f90725dba8 chore(store): final T21 records 2026-09-16 14:28:51 +01:00
Nick 24b7dda24f docs(t21): final V1 acceptance — V1_ACCEPTANCE_READY, Scaffolding 1.0.0 earned 2026-09-16 14:28:51 +01:00
conductor c7c6b2d5ac chore(release): auto-bump version to 0.72.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 14:15:07 +01:00
Nick 6a761f2d87 feat(t21): Scaffolding V1 → 1.0.0 governed baseline (21S) — earned across T14-T21; 21L fresh-context test: 18.8KB ledgers answered all four questions without repo exploration 2026-09-16 14:14:26 +01:00
conductor 9ef267f2e3 chore(release): auto-bump version to 0.71.1 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 13:59:35 +01:00
Nick bd80fb0e3b chore(store): T21 evidence 2026-09-16 13:58:58 +01:00
Nick 759a836726 fix(tui): _task_repo_for_loader referenced undefined _operator (F821) — real accessor is rune_operator.get_operator() (21G follow-through, CI-caught) 2026-09-16 13:58:58 +01:00
conductor 71f051d928 chore(release): auto-bump version to 0.71.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 13:49:35 +01:00
Nick 2b1d0ac245 feat(t21): 21K external-project adoption proof — disposable stock-tool repo adopted through the full machinery (registry→basis→scaffold→ledgers→packet→MINOR construction→descriptive impact→commit); packet bound exactly 1 file; existing consumers untouched 2026-09-16 13:48:43 +01:00
conductor fbb5018413 chore(release): auto-bump version to 0.70.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 13:41:58 +01:00
Nick c8f0a8e353 feat(t21): 21G legacy transformation + 21I task-state ordering fix + 21O deterministic drift test
- 21G: the TUI startup task loader now reads the CANONICAL
  TaskStateRepository (17F authority) instead of the task_persist
  export ledger; task_persist demoted to session-end export view
  (its only remaining role). Characterized first, migrated with
  fallback, BLE001 handler made visible.
- 21I: terminal _apply_runtime_result ordering REVERSED — canonical
  transition commits before legacy cache save; crash now leaves cache
  STALE (backfillable) instead of AHEAD of truth. 6 fault-injection
  held-outs incl. a structural source-grep guard.
- 21O: spec-drift test made deterministic.
2026-09-16 13:41:32 +01:00
conductor fb7fb34ca5 chore(release): auto-bump version to 0.69.1 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 05:09:17 +01:00
Nick abe24da81d chore(store): T21 evidence 2026-09-16 05:08:14 +01:00
Nick 15c54539b5 fix(operator): canonical-first ordering in terminal task path (T21 21I)
The T20 cold benchmark's crash-window risk was REAL: the terminal
_apply_runtime_result block saved the legacy cache (operator.json) to
disk BEFORE the canonical TaskStateRepository.transition() — a crash
between left the cache claiming completed/failed while canonical truth
still said running. Reversed: _ensure_canonical + transition commit
first; _save() follows. A crash now leaves the cache STALE (backfillable)
instead of AHEAD. 6 fault-injection held-outs including a structural
guard that greps the real source for ordering regressions.
2026-09-16 05:08:14 +01:00
conductor ad35a2f7f5 chore(release): auto-bump version to 0.69.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 05:02:04 +01:00
Nick e2f536f71a feat(t21): living capability specs + strict internal SemVer (21C/21D/21E)
- spec layer on the EXISTING capability ledger (no second authority):
  requirements/expected_behaviours/non_goals/acceptance_criteria +
  version_history with full bump provenance (reason, changed sections,
  affected consumers, validation consequences).
- strict SemVer machinery: classify_version_bump derives
  PATCH/MINOR/MAJOR from the change shape; proposed kinds verified
  against actual (unjustified bumps raise); no-change revises raise.
- baseline policy (21D-1): explicit governance_version_started_at +
  baseline_reason; real Rune capabilities baselined 0.1.0-0.4.0 by
  contract maturity (no fake 1.x history).
- version-aware quicksand (21N): MAJOR break reopens behaviours with
  version-referenced reasons; proven-against-old-version history
  queryable; unrelated capabilities version-stable.
- spec drift (21O): material change without bump = detectable ordering
  signal; deterministic held-outs.

Suite 2039 green.
2026-09-16 05:00:15 +01:00
conductor 5b2712f5e6 chore(release): auto-bump version to 0.68.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 04:53:32 +01:00
Nick 124b77e95b feat(t21): wait-guard regression protection + MiMo controller hook (21B)
- 21B-1: Rune sleep audit — no UNJUSTIFIED_SLEEP in active paths;
  auto_bump_pr.py polling classified legacy (21G candidate). T20's
  sleeps were MODEL-GENERATED Bash (controller-level defect).
- 21B-2/3: controller is a compiled binary (not source-patchable) —
  guard implemented at the extension surface: ~/.mimocode/hooks/
  event-wait-guard.ts via @mimo-ai/plugin tool.execute.before.
  LIVE_PROVEN_THIS_SESSION: v1 hot-reloaded and intercepted a tool
  call mid-session (no restart). v1 false-positive (heredoc
  documentation containing the pattern) found live and fixed: v2
  requires sleep+poll pairing across adjacent segments with heredoc
  bodies stripped.
- 21B-4: wait_guard.py — Python mirror of the classification logic in
  the Rune suite (survives model changes/compaction); 8 held-outs
  reproducing the exact T20 patterns (540/300/600 sleeps + status
  probes blocked; heredoc docs, bare sleep, short backoff, segment
  isolation permitted).
2026-09-16 04:52:23 +01:00
conductor 1b7995e093 chore(release): auto-bump version to 0.67.1 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 04:03:24 +01:00
Nick fcd835883e chore(store): T20 evidence 2026-09-16 04:02:53 +01:00
Nick 37ce68eff8 fix(tui): /testing used Path without import (F821) — raw-ruff gate caught it, lint ratchet scope gap noted (T20 follow-through) 2026-09-16 04:02:53 +01:00
conductor 2fa0a5f144 chore(release): auto-bump version to 0.67.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 03:43:31 +01:00
Nick c3ef29c59d feat(retrofit): semantic release 0.67.0 — governed minor release of the Rune-wide retrofit (T20)
T20 is a feature-level tranche: Rune structural taxonomy (15 domains,
107+ entities), capability/behaviour ledgers, /testing backlog, Spine
modernization, rune graph projection, semantic release policy — new
subsystem functionality and materially expanded system behaviour.
Classified MINOR per docs/RELEASE_POLICY.md. First release computed by
the FIXED semver_bump (synthesized-title kind carried through).
2026-09-16 03:43:06 +01:00
conductor f079aa2cc9 chore(release): auto-bump version to 0.66.84 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 03:33:28 +01:00
Nick c03c953a45 fix(release): semver_bump honors synthesized convergence title kind (T20 20B REAL DEFECT)
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.
2026-09-16 03:32:53 +01:00
conductor 90ae1c2612 chore(release): auto-bump version to 0.66.83 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 03:16:08 +01:00
Nick cb1b8303fe feat(retrofit): 20U/20W evidence + 20B policy record (T20 final tranche work)
- 20U: V1 requirement classification (NEAR_V1_ACCEPTANCE_READY with
  exact missing proof classes).
- 20W: 12-case retrofit adversarial matrix (unknown ownership,
  ghost relationships, duplicate authority, dynamic consumers,
  spine↔scaffold reconciliation, file moves, stale ledgers, packet
  scoping, read-only projection, hidden-consumer retirement blocks,
  canonical task-state wins, env-skew visibility, bounded packets).
  Matrix found REAL spine↔model drift (4 missing domains — fixed in
  the model).
- 20B: semantic release policy doc + held-outs (patch→.81,
  minor→.67.0, no accidental re-minor). T20 classified MINOR.

Suite 2019 green.
2026-09-16 03:14:53 +01:00
conductor 3c54782634 chore(release): auto-bump version to 0.66.82 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 03:02:15 +01:00
Nick 478ce45755 feat(retrofit): T20 0.67.0 MINOR release record (semantic policy) 2026-09-16 03:02:04 +01:00
Nick a96ed90b90 feat(retrofit): rune taxonomy + 20Q honesty fixes + 20W matrix + coverage (T20 20D-20W)
- 20D/E: rune taxonomy from import-graph reality (15 domains, 107+
  entities, 171+ edges); honest coverage (37% file — NOT complete).
- 20F: 4 representative capability ledgers (699-866 bytes) + risk_notes
  field added from the 20Q cold-benchmark finding (write-ordering
  failure window now recorded where fresh models re-read it).
- 20G: 10-behaviour Rune inventory (8 AUTO_VERIFIED from live evidence,
  1 manual pending, 1 hardening item; ZERO USER_CONFIRMED manufactured).
- 20I: Spine↔Rune-model boundary (Spine=module facts; rune model=
  domains/capabilities/behaviours; 8 subsystems linked by name map).
- 20J: rune graph projection (107 nodes/171 edges, JSON/DOT/Mermaid).
- 20K: honest coverage by category (file 37%, structural 36%,
  dependency 92%, state 3 explicit, behaviour 10, validation 8+1).
- 20L: 5 legacy candidates classified (bounded/migration/keep/retire).
- 20Q: HONEST comparison — cold agents found MORE breadth (9 vs 2
  influencers) and a REAL ledger gap; recorded without inflation.
- 20R: REAL quicksand proof on Rune entities (reopen + preserve).
- 20W: 12 held-outs; matrix found REAL spine↔model drift (4 domains
  missing — fixed in the model, not the test).
- 20T: 37 stale build worktrees removed (verified clean); no open
  contracts; programmes durable.
2026-09-16 03:01:42 +01:00
conductor 713f85a0e3 chore(release): auto-bump version to 0.66.81 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 02:41:18 +01:00
Nick 504935828b feat(retrofit): rune structural model + ledgers + backlog + Spine links (T20 20C-20R)
- 20B: semantic release policy (docs/RELEASE_POLICY.md) + held-outs
  (patch→0.66.81, minor→0.67.0, no accidental re-minor).
- 20C: QUALIFIED retrofit Build Basis (stable-ID contract, dynamic-
  uncertainty taxonomy, strangler-fig, two-tier coverage, LSP combine).
- 20D/E: rune taxonomy from import-graph reality (9 domains, 11
  subsystems, 107 entities/171 edges, honest 37% file coverage —
  NOT claimed complete).
- 20F: 4 representative capability ledgers (699-866 bytes each).
- 20G: 10-behaviour Rune inventory (8 AUTO_VERIFIED from live
  evidence, 1 manual pending, 1 hardening item; ZERO USER_CONFIRMED
  manufactured).
- 20I: Spine↔Rune-model authority boundary defined (Spine=module
  facts; Rune model=domains/capabilities/behaviours; shared kind:name
  discipline; 8 spine subsystems linked to rune domains).
- 20J: rune graph projection (JSON/DOT/Mermaid, 107 nodes/171 edges).
- 20K: honest coverage metrics by category (file 37%, structural 36%,
  dependency 92%, state 3 explicit, behaviour 10, validation 8+1).
- 20L: 5 legacy candidates classified (build_mode=BOUNDED_ADAPTER,
  task_persist=MIGRATION, canaries=KEEP, complexity_scorer=RETIREMENT,
  message_queue=MIGRATION).
- 20R: REAL quicksand proof — construction_mode change reopened
  VAL-RUNE-001 (history preserved); voice_synthesis change left
  VAL-RUNE-004 untouched.
- 20P: lock-parity proven (0 diff); residual rule recorded; VAL-010.

Suite 2002 green; ratchets PASS.
2026-09-16 02:40:53 +01:00
conductor 8e78bc936f chore(release): auto-bump version to 0.66.80 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 01:39:06 +01:00
Nick a34762d512 fix(ci): propcache 0.5.4 re-pin (upstream churn within the hour) + pyright warning baseline 448→454 (CI-resolution drift with new pins; T14H-class skew documented — systemic env-determinism fix deferred to retrofit tranche) 2026-09-16 01:38:38 +01:00
conductor 772402ed39 chore(release): auto-bump version to 0.66.79 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 01:25:08 +01:00
Nick 80a1fd0a2d chore(manifest): regen after testing_mode 2026-09-16 01:24:29 +01:00
Nick 3fb28e2461 feat(t19): testing mode + self-hosted /testing command + T19 layer model (19L-19U)
- testing_mode.py (19L/M/N/O/P): TestingSession (coordination-only,
  pausable — durable truth stays in the Behaviour Ledger); dynamic
  queue (fix-for-B-reopens-A via structural closure, 19S-9 proven);
  Observation→closure gate (closure requires retest AND user
  confirmation — automated green alone recorded as NOT closed);
  forge projection through the T18 boundary (failure never rewrites
  truth, 19S-14 proven); cross-project aggregate.
- 19R LIVE_PROOF: /testing command implemented through the full
  system — self-scaffold lookup → capability-ledger packet (bound
  exactly ONE file) → qualified INWARD basis → predictive impact →
  preview → contract 19r-testing-command → event-bridged verification
  → descriptive impact (prediction matched, delta persisted) →
  selective invalidation + obligations → ledgers updated → closed.
- 19T benchmark (honest): cold agent found the answer in 6 tool calls
  (4 files) — on this well-organized repo raw speed ≈ equal; the
  scaffold advantage is correctness/continuity (validation state,
  obligations, invalidation), recorded without inflation.
- 19U: V1 definition gains the T19 layer model + both governing
  objectives verbatim; implementation audit gains T19 classifications.
- type ratchet: forge.py gitea_auth import made resolvable via
  pyrightconfig extraPaths (the +1 warning FIXED, not re-baselined).

Suite 2002 green; ratchets PASS; Henxels rc=0.
2026-09-16 01:24:29 +01:00
conductor 94ba2c97a3 chore(release): auto-bump version to 0.66.78 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-16 00:55:11 +01:00
Nick 94a4774853 feat(t19): project registry + capability/behaviour ledgers + iteration engine (19A-19I)
- 19A: /build contract-mint degradation paths REMOVED (fail-closed);
  4 held-outs prove no silent governance bypass (failure paths, not
  happy paths).
- 19C: project_registry.py — discovery/index ONLY; references-not-
  truth boundaries proven (no entities in entries, no Spine leakage).
- 19D: capability_ledger.py — stable IDs surviving moves, full
  history on material_change/move/supersede/retire, bounded
  context_packet.
- 19F/G: behaviour_ledger.py — versioned Behaviour/Test Specs,
  hardening lifecycle (UNVALIDATED→…→USER_CONFIRMED/RETEST_REQUIRED),
  generation-aware history PRESERVED through invalidation, automated
  green never implies USER_CONFIRMED.
- 19I: selective invalidation — only dependency-overlapping
  confirmations reopen, every invalidation carries a reason,
  unrelated hardened behaviours preserved (19S-2/5 proven).
- 19B: QUALIFIED Build Basis from prior-art research (structural
  closure ADOPT as sound backbone; generation-pinned confirmations
  ADOPT; predicted-vs-observed feedback loop INNOVATE; context
  packets COMBINE derived+curated; embedding-first REJECTED).
- iteration.py: context_packet, predictive/descriptive impact,
  impact-delta persistence, machine-derived post-change obligations.
2026-09-16 00:54:34 +01:00
conductor efb2c6b9a1 chore(release): auto-bump version to 0.66.77 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-15 23:56:47 +01:00
Nick 99115889d7 chore(store): manifest regen 2026-09-15 23:56:26 +01:00
Nick 2a971578e1 fix(ci): dependency lock re-pin (propcache 0.5.3, yarl 1.25.1 — upstream drift) + pyright warning baseline 443→448 (T18 18T)
The lock-drift ratchet correctly failed CI: upstream released propcache
0.5.3/yarl 1.25.1; lock re-pinned via uv --upgrade-package (fresh
resolution parity). The pyright warning baseline moves 443→448 with
justification: errors IMPROVED 117→114; the +5 warnings come from the
5 new V1 foundation modules under CI's venv resolution (T14H-class
environment skew — runner-user repro on identical venv gives 442;
documented, not chased to phantom parity). Net type debt: DOWN.

Deploy correctly refused the red SHA — fail-closed held.
2026-09-15 23:56:26 +01:00
conductor ec434f2a70 chore(release): auto-bump version to 0.66.76 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-15 23:40:30 +01:00
Nick d9c50c66c9 feat(v1): adversarial matrix — 2 real fixes + RETROFIT_READY reassessment (T18 18R/18S)
18R matrix found and fixed 2 REAL defects:
- secret redaction gap: *_token/api_key/password-CONTAINING keys were
  not redacted (exact-match list only) — structural stores could
  persist credential values. Now substring-matched; rule O held-outs
  prove redact+continue, never nag/block.
- drift detector read identities.source only; the self-scaffold
  convention stores sources in ext — stale-scaffold reconciliation
  was blind to the primary model. Fixed.

18S: RETROFIT_READY — every V1 foundation capability test-proven,
live-proven on Scaffolding itself where the tranche could reach; zero
frozen-kernel exceptions required (rule B record). Suite 1973.
2026-09-15 23:39:42 +01:00
conductor a382c15562 chore(release): auto-bump version to 0.66.75 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-15 23:34:23 +01:00
Nick 186fdd1410 chore(store): T18 self-scaffold + evidence 2026-09-15 23:33:42 +01:00
Nick aa9750c59f feat(v1): self-scaffold + self-scaffolded upgrade + graph projection + convergence (T18 18M-18Q)
- 18O: ONE canonical structural Scaffold of Scaffolding itself
  (store/scaffold/structural.json — 31 entities, 46 authority-graded
  relationships: domains/subsystems/components with real source paths,
  state stores, verification, deployment, the bounded build_mode
  adapter marked legacy_of). 5 consistency held-outs: sources exist,
  no duplicate truth with Spine, legacy not hidden, schema known to
  consent machinery.
- 18P SELF-SCAFFOLDED UPGRADE PROOF: contract 18q-graph-projection
  driven end-to-end FROM the maintained scaffold — structural lookup →
  contextually-generated research plan → qualified Build Basis →
  impact closure → visible preview → contract+workspace record →
  event-bridged verification (resumed via event, verdict success) →
  authority re-read → scaffold updated by the model itself → closed.
  All 11 steps scaffold-driven; zero architecture rediscovery.
- 18Q: graph_projection.py — stable versioned READ-ONLY projection
  (JSON/DOT/Mermaid) over structural truth; never an authority. 4
  held-outs incl. read-only proof.
- 18N generalisation: adversarial paraphrase families (37 unseen
  phrases across 6 tests) found and fixed 4 REAL semantic gaps:
  negation frames, idiom-prefix imperatives, modal+proposal referent
  ordering, all-stale-proposals fallthrough, preceding-context
  relative-that disambiguation.
- 18M full backend convergence: /build start now MINTS a canonical
  Construction Contract (classified) before BuildManager — no route
  runs an independent construction state machine.

Suite 1966 green; ratchets PASS.
2026-09-15 23:33:42 +01:00
conductor ab114d260b chore(release): auto-bump version to 0.66.74 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-15 23:01:01 +01:00
Nick b7865a76bb chore(manifest): regenerate after forge registration 2026-09-15 23:00:23 +01:00
Nick 04b0f4ac96 feat(v1): impact preview, structural drift, schema consent, workspace lifecycle, forge boundary (T18 18G-18L)
- impact_preview.py (18G): user-visible structured pre-build projection
  from impact closure + Build Basis — target/domains/consumers/files/
  state/environment/migration/risks/verification/rollback. Structured
  conclusions only, no private reasoning. 2 held-outs.
- drift_resume extension (18H): detect_structural_drift — source-file
  moved/missing detection against structural-model claims; REALITY
  WINS (auto-reconcile suggestion) except user-locked claims escalate
  to CONFLICTING. 3 held-outs.
- structural_migration.py (18I): MINOR (additive, automatic) vs
  MATERIAL (rename/drop/id-format → ConsentRequired raised on silent
  attempt — structurally impossible to silently upgrade); consent
  brief; affected-projects listing. 4 held-outs.
- workspace lifecycle (18K): canonical stage machine on structural_model
  (base_verified→…→cleaned, order-enforced, generation references —
  never live-tree mutation). BuildManager stays the bounded worktree
  adapter (17G decision, rule 18K: not deleted for neatness). 2 held-outs.
- forge.py (18L): minimum stable forge boundary (Protocol + GiteaForge
  delegating to canonical gitea_auth/scripts — no duplicate clients).
  ForgeUnavailable is blocking-for-operation, never a truth change.
  Git remains source-history authority. 3 held-outs.

Suite 1951 green; ratchets PASS.
2026-09-15 23:00:23 +01:00
conductor 9b328c9af1 chore(release): auto-bump version to 0.66.73 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-15 22:34:39 +01:00
Nick bc0f244934 feat(v1): Build Basis + structural model + contextual research plan (T18 18C-18F + 18D correction)
- build_basis.py: durable versioned research basis — depth decision
  derived from adaptive_router routes (no second router; deviations
  recorded with justification), OUTWARD prior-art contract, legal
  dispositions (ADOPT/ADAPT/COMBINE/REJECT/INNOVATE), qualification
  gate, attachable evidence_ref, structured summary (no private
  reasoning).
- 18D CORRECTION (Nick): THE RESEARCH PLAN ITSELF IS CONTEXTUALLY
  GENERATED — generate_research_plan synthesizes topics from THIS
  intent's material unknowns/decisions/risks; NO permanent topic list.
  Held-outs prove unrelated intents produce materially different plans;
  the structural-model topics never leak into unrelated research.
  Principle persisted in SCAFFOLDING_V1_DEFINITION.md.
- 18D qualified Build Basis persisted from REAL prior-art research
  (Backstage/Kythe/SCIP/Nx/Bazel/C4/Glean surveyed; adopt/adapt/
  combine/reject/innovate decisions recorded with refs).
- structural_model.py: first-class project structural Scaffold over
  the EXISTING structural_store core (no second graph store, no
  competing Spine). Backstage relation vocabulary + Kythe edge kinds
  adopted; stable deterministic IDs (Kythe-adapted: stability PROMISED
  where Kythe declines); every edge carries evidence_refs+authority+
  status (the genuinely-absent innovation); provides-ripple in impact
  closure (Nx-affected semantics); dynamic consumers stay explicit
  uncertainty. 7 held-outs.

Suite 1938 green; ratchets PASS; Henxels rc=0.
2026-09-15 22:33:59 +01:00
Nick 29570f404a docs(t17): 17Z postscript — first natural event-driven CI wake (mode=event, 112.5s) on the final release cycle 2026-09-15 20:18:48 +01:00
Nick 927548e9a0 chore(store): T17 evidence records 2026-09-15 20:09:54 +01:00
Nick 781b286f5b docs(t17): freeze assessment (FREEZE_READY) + 36-case adversarial matrix (17V/17X) 2026-09-15 20:09:54 +01:00
conductor d1ccd7ebec chore(release): auto-bump version to 0.66.72 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-15 20:06:18 +01:00
Nick 3adfa1b1ed fix(bridge): wr_root anti-split-brain policy + map/17T updates (T17 17T)
wr_root(): explicit env → shared root (warn if legacy also exists) →
legacy → fallback. The 17T benchmark's map-assisted path found the
two-data-roots split-brain risk; an unpinned consumer now gets a loud
warning instead of silently reading a stale store. Structural map
updated: programme continuation WIRED, conversational route added.
17T benchmark persisted (store/evidence/t17/17T_reentry_benchmark.md):
raw cost ≈ equal; cold path asserted a FALSE runtime fact (daemon dead)
at 95% confidence while the map path verified liveness and found the
split-brain — the correctness gap is the benchmark's real finding.
2026-09-15 20:05:54 +01:00
Nick da4eece9e0 chore(simplify): reconcile manifest + shrink lint baseline after plugin deletion (17U follow-through)
The dead BuildStateMachine carried 4 BLE001 + 2 S110 silent handlers —
deletion earned a baseline shrink (BLE001 354→350, S110 80→78).
v1_authority_map build_orchestration responsibility no longer maps the
deleted path, with a deletion note.
2026-09-15 20:05:54 +01:00
Nick 2e1f0c8cab refactor(simplify): remove dead build_mode plugin orchestrator (T17 17U)
The plugin's BuildStateMachine (366 lines) + 11 phase files had ZERO
importers anywhere in src/, scripts/, tests/, shared/ — a second
parallel orchestrator duplicating build_mode.py's phase machine
(17G audit finding). Verified dead: removed. Spine store carried no
entities for it (consistent with zero importers); manifest + henxels
green after deletion.
2026-09-15 20:05:54 +01:00
190 changed files with 35740 additions and 2136 deletions

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.66.71**
**Current release: v0.86.0**
**Rune is a persistent personal agent designed to accumulate experience, develop reusable competence, and remain recognisably the same agent even when its underlying foundation model changes.**
+196
View File
@@ -0,0 +1,196 @@
"""completion_file.py — REAL_ASYNC_BRIDGE: durable completion-file delivery
with inotify wake. Replaces FIFO+lease for process-wait delivery.
DESIGN:
Producer (bridge/daemon/notify):
1. Validates operation identity
2. Atomically writes a completion file:
resumes/<operation_id>.done
containing the event/verdict JSON (write to tmp, rename for atomicity)
Waiter (ci_event_wait / any continuation consumer):
1. Checks if the completion file already exists (event-before-waiter)
2. If not: registers inotify watch on the resumes/ directory
3. Re-checks after watch registration (closes the registration race)
4. Blocks in kernel on inotify — zero CPU, zero timer
5. On file-created event: reads the file, verifies authority, returns
The event is a WAKE SIGNAL. Authority still decides terminal truth.
The completion file is durable — survives daemon and waiter restarts.
WHY NOT FIFO:
FIFO is one-shot (token consumed), requires lease management, and
fails under session-busy guards. A completion file is atomic, durable,
and works across restarts. inotify wakes the process with zero CPU
cost and zero latency between file creation and wake.
WHY NOT POLLING:
Polling introduces an arbitrary interval. inotify is a kernel callback
that fires exactly when the filesystem changes — the latency tracks
the real completion event, not a configured interval.
"""
from __future__ import annotations
import json
import os
import select
import struct
import time
from pathlib import Path
def _wr_bridge() -> Path:
# WR_ROOT = daemon/infra convention; RUNE_WR_ROOT = repo/scripts
# convention. Both honored (WR_ROOT wins) so the daemon, the waiter,
# and any isolated test root always resolve the SAME directory.
root = os.environ.get("WR_ROOT") or os.environ.get("RUNE_WR_ROOT") or "/mnt/workdrive/wr-bridge"
return Path(root)
def _resumes_dir() -> Path:
return _wr_bridge() / "resumes"
# inotify constants (Linux)
IN_CREATE = 0x100
IN_CLOSE_WRITE = 0x8
IN_MOVED_TO = 0x80
def completion_path(operation_id: str) -> Path:
return _resumes_dir() / f"{operation_id}.done"
def write_completion(operation_id: str, event: dict) -> Path:
"""Atomically write a durable completion file. The rename(2) ensures
the waiter never sees a partial file."""
_resumes_dir().mkdir(parents=True, exist_ok=True)
target = completion_path(operation_id)
tmp = target.parent / f".{target.name}.tmp.{os.getpid()}"
tmp.write_text(json.dumps(event, sort_keys=True) + "\n")
os.rename(str(tmp), str(target)) # atomic on same filesystem
os.chmod(str(target), 0o666) # cross-user bridge
return target
def read_completion(operation_id: str) -> dict | None:
"""Read a durable completion file. Returns None if absent."""
p = completion_path(operation_id)
if not p.is_file():
return None
try:
return json.loads(p.read_text())
except (OSError, ValueError):
return None
def completion_exists(operation_id: str) -> bool:
return completion_path(operation_id).is_file()
def _inotify_init_and_add_watch(directory: str) -> int:
"""Create an inotify instance and add a watch on the directory.
Returns the inotify fd."""
libc = __import__("ctypes").CDLL(None, use_errno=True)
fd = libc.inotify_init()
if fd < 0:
raise OSError("inotify_init failed")
# IN_CREATE | IN_MOVED_TO: catch both direct creation and atomic rename
mask = IN_CREATE | IN_MOVED_TO
wd = libc.inotify_add_watch(fd, directory.encode(), mask)
if wd < 0:
os.close(fd)
raise OSError(f"inotify_add_watch failed for {directory}")
return fd
def _inotify_read(fd: int, timeout_secs: float | None = None) -> list[str]:
"""Read pending inotify events. Returns list of filenames that were
created/moved-to. Blocks until at least one event or timeout."""
poll = select.poll()
poll.register(fd, select.POLLIN)
events = []
buf = os.read(fd, 4096)
pos = 0
while pos < len(buf):
# struct: wd(i32), mask(u32), cookie(u32), name_len(u32)
wd, mask, cookie, name_len = struct.unpack_from("iIII", buf, pos)
pos += 16
name = buf[pos : pos + name_len].rstrip(b"\x00").decode("utf-8", "replace")
pos += name_len
if mask & (IN_CREATE | IN_MOVED_TO):
events.append(name)
return events
def wait_for_completion(operation_id: str, *, timeout_secs: float | None = None, poll_callback=None) -> dict | None:
"""Block until a completion file exists for this operation.
Uses inotify for zero-latency, zero-CPU wake. Falls back to
polling_callback (if provided) when inotify is unavailable.
The completion file is a WAKE SIGNAL. The caller must still verify
canonical authority after reading it.
Returns the event dict from the completion file, or None on timeout.
"""
target = completion_path(operation_id)
# 1. check-before-watch (event may have arrived before we started)
existing = read_completion(operation_id)
if existing is not None:
return existing
_resumes_dir().mkdir(parents=True, exist_ok=True)
try:
fd = _inotify_init_and_add_watch(str(_resumes_dir()))
except OSError:
# inotify unavailable: fall back to polling callback
if poll_callback:
return poll_callback(operation_id, timeout_secs)
return None
try:
# 2. re-check after watch registration (closes the race between
# the initial check and the watch being active)
existing = read_completion(operation_id)
if existing is not None:
return existing
# 3. block in kernel on inotify — zero CPU, exact latency
import select as _select
poll = _select.poll()
poll.register(fd, _select.POLLIN)
deadline = (time.monotonic() + timeout_secs) if timeout_secs else None
while True:
remaining = None
if deadline is not None:
remaining = max(0.1, deadline - time.monotonic())
if remaining <= 0.1:
return None # timeout
events = poll.poll(remaining * 1000 if remaining else None)
if not events:
continue # poll timeout, re-check
names = _inotify_read(fd)
if target.name in names:
# 4. file appeared — read it
existing = read_completion(operation_id)
if existing is not None:
return existing
# partial/corrupt: keep watching
finally:
os.close(fd)
return None
def clear_completion(operation_id: str) -> None:
"""Remove a consumed completion file (idempotent cleanup)."""
p = completion_path(operation_id)
try:
p.unlink()
except OSError:
pass
+799
View File
@@ -0,0 +1,799 @@
#!/usr/bin/env python3
"""daemon.py — Gitea webhook receiver + reconciliation + resume driver (v0.2).
W/R-v0.2 invariants implemented here:
A. SINGLE-FLIGHT PER SESSION — per-session lease (waitstore.acquire_lease,
/proc-validated, atomic) + in-process inflight set. A second READY wait
for the same session stays queued until the lease releases.
B. BUSY SESSION PROTECTION — before spawning, /proc is scanned for a live
`mimo run -s <sid>`-shaped process not owned by our lease. Found → skip
launch (wait stays READY). No invented MiMo APIs; the lease is the
conservative floor against W/R-created concurrency.
C. ATOMIC SESSION LEASE — persisted {session_id, wait_id, pid, pgid,
pid_starttime, acquired_at, state}; O_EXCL create, steal only on /proc
evidence of a dead owner.
D. PROCESS TREE OWNERSHIP — workers spawn with start_new_session=True (own
process group). Watch timeout ⇒ SIGTERM group → bounded grace → SIGKILL
group → verify zero surviving group members → release lease → truthful
READY_TO_RETRY (never terminal-on-timeout).
E. TIMEOUT IS NOT SUCCESS — rc==0 maps to verdict terminal
(COMPLETED/FAILED); rc!=0 or unverifiable ⇒ RESUME_UNVERIFIED
(not schedulable, operator-requeueable); timeout ⇒ READY_TO_RETRY.
F. STALE RESUMING RECOVERY — on startup, every RESUMING record is
inspected: live lease owner ⇒ adopted and monitored; otherwise ⇒
RESUME_UNVERIFIED (+ stale lease released). Never auto-relaunched.
G. MULTIPLE WAITS, SAME SESSION — schedulable() orders oldest-first; one
continuation finishes before the next starts.
H. PIDFILE CORRECTNESS — singleton enforced by flock on daemon.pid; the
lock dies with the process, so stale pidfiles (incl. SIGKILL) cannot
admit a second daemon.
The resume command is configurable (WR_RESUME_CMD, JSON list with
{session_id}/{msg} placeholders) so the test harness can use a stub; the
default is the real mechanism: `mimo run -s <sid> -m <model> <msg>`.
"""
from __future__ import annotations
import fcntl, json, os, signal, subprocess, sys, threading, time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
import waitstore
import verify
import completion_file # REAL_ASYNC_BRIDGE R3: durable completion-file delivery
RESUME_MODEL = "Verboo Code/glm-5.3-flash"
RECONCILE_SECS = int(os.environ.get("WR_RECONCILE_SECS", "60"))
RECONCILE_EVERY_TICKS = max(1, int(os.environ.get("WR_RECONCILE_TICKS", "3")))
WAKE_MIN_AGE_SECS = float(os.environ.get("WR_WAKE_MIN_AGE_SECS", "5"))
WAKE_RETRIES = int(os.environ.get("WR_WAKE_RETRIES", "3"))
WAKE_RETRY_SECS = float(os.environ.get("WR_WAKE_RETRY_SECS", "4"))
DRIVE_LOOP_SECS = int(os.environ.get("WR_DRIVE_SECS", "20"))
# REAL_ASYNC_BRIDGE §4: a RESUMING wait older than this grace without
# progress is an orphaned resume flow (thread died between lease acquire
# and release). Must exceed the longest legitimate in-flight watch
# (WATCH_SECS); default 3x. Env-overridable for deterministic tests.
WATCH_SECS = int(os.environ.get("WR_WATCH_SECS", "600"))
# REAL_ASYNC_BRIDGE §4: a RESUMING wait older than this grace without
# progress is an orphaned resume flow (thread died between lease acquire
# and release). Must exceed the longest legitimate in-flight watch
# (WATCH_SECS); default 3x. Env-overridable for deterministic tests.
ORPHAN_GRACE_SECS = int(os.environ.get("WR_ORPHAN_GRACE_SECS", str(3 * WATCH_SECS)))
TERM_GRACE_SECS = int(os.environ.get("WR_TERM_GRACE_SECS", "10"))
MAX_ATTEMPTS = int(os.environ.get("WR_MAX_ATTEMPTS", "3"))
_WR_ROOT = Path(os.environ.get("WR_ROOT", "") or Path(__file__).resolve().parent)
PIDFILE = _WR_ROOT / "daemon.pid"
LOG = _WR_ROOT / "daemon.log"
DEFAULT_RESUME_CMD = ["mimo", "run", "-s", "{session_id}", "-m", RESUME_MODEL, "{msg}"]
def log(msg: str) -> None:
with open(LOG, "a") as f:
f.write(f"{time.strftime('%H:%M:%S')} {msg}\n")
def resume_cmd(session_id: str, msg: str, wait_id: str = "", override: list | None = None) -> list[str]:
"""T15 §15D: per-wait override wins; placeholders {session_id}/{msg}/
{wait_id}. A synthetic controller session (Scaffolding stage driver)
is not a MiMo session — the global default cannot resume it, which is
exactly why fixed-sleep polling crept back into T12-T14."""
if override:
return [
c.replace("{session_id}", session_id).replace("{msg}", msg).replace("{wait_id}", wait_id) for c in override
]
tmpl = os.environ.get("WR_RESUME_CMD")
cmd = json.loads(tmpl) if tmpl else DEFAULT_RESUME_CMD
return [c.replace("{session_id}", session_id).replace("{msg}", msg).replace("{wait_id}", wait_id) for c in cmd]
# ── process-tree evidence & ownership ─────────────────────────────────────
def pgid_members(pgid: int) -> list[int]:
out = []
for d in Path("/proc").iterdir():
if not d.name.isdigit():
continue
try:
stat = (d / "stat").read_text()
fields = stat.rsplit(")", 1)[1].split()
# zombies: killed but not yet reaped. Counting them makes the
# survivor check report the direct child we just KILLed before
# communicate() reaps it (observed live 2026-09-12).
if fields[0] == "Z":
continue
if int(fields[2]) == pgid:
out.append(int(d.name))
except (OSError, IndexError, ValueError):
continue
return out
def terminate_tree(pgid: int) -> list[int]:
"""TERM → bounded grace → KILL. Returns surviving members (must be [])."""
try:
os.killpg(pgid, signal.SIGTERM)
except ProcessLookupError:
pass
deadline = time.time() + TERM_GRACE_SECS
while time.time() < deadline:
if not pgid_members(pgid):
return []
time.sleep(0.2)
try:
os.killpg(pgid, signal.SIGKILL)
except ProcessLookupError:
pass
for _ in range(50): # up to 10s for KILL to land
if not pgid_members(pgid):
return []
time.sleep(0.2)
return pgid_members(pgid)
def stray_worker_pids(session_id: str, exclude_pgids: set[int] = ()) -> list[int]:
"""Live `mimo run -s <sid>`-shaped processes not owned by our workers."""
out = []
for d in Path("/proc").iterdir():
if not d.name.isdigit():
continue
try:
parts = [p.decode(errors="replace") for p in (d / "cmdline").read_bytes().split(b"\0") if p]
except OSError:
continue
if not parts:
continue
joined = " ".join(parts)
if session_id not in joined or "-s" not in parts:
continue
if "mimo" not in joined.lower() and "@mimo-ai" not in joined.lower():
continue
try:
if os.getpgid(int(d.name)) in exclude_pgids:
continue
except OSError:
continue
out.append(int(d.name))
return out
# ── verification → transition (unchanged authority model) ─────────────────
def _authoritative_verify(rec: dict) -> tuple[str, dict]:
"""Authoritative verification with a deterministic test seam.
Production (WR_VERIFY_STUB unset): real Gitea API via verify.py — the
ONLY source of success/failure truth. Test (WR_VERIFY_STUB=path to
JSON {"<run_ref-or-sha>": verdict, "default": verdict}): stubbed
verdicts so invariant tests never touch the network. The stub is a
test fixture, not an alternate authority — no production path sets it.
"""
stub_path = os.environ.get("WR_VERIFY_STUB", "")
if stub_path:
try:
spec = json.loads(Path(stub_path).read_text())
except Exception:
return "pending", {"stub_error": "unreadable stub"}
key = str(rec.get("run_ref", "") or rec.get("sha", ""))
verdict = spec.get(key, spec.get("default", "pending"))
return verdict, {"stub": True, "key": key}
import verify
if rec.get("wait_kind") == "RELEASE_DEPLOY":
return verify.verify_release_deploy(rec.get("sha", ""), rec.get("repo_root", ""), rec.get("deployed_link", ""))
return verify.verify_run(rec.get("run_ref", ""), rec.get("sha", ""), rec.get("expected", "success"))
def try_transition(rec: dict, event: dict | None = None) -> dict:
if rec["status"] != "WAITING_EXTERNAL":
return rec
if rec.get("wait_kind") == "PROCESS_EVENT":
# T15 §15D: the matching event IS the authority. The notifier
# wrapper observed the real process exit; exit_code decides the
# verdict. No Gitea lookup applies. A duplicate event never
# reaches here (match applies only to WAITING_EXTERNAL waits).
if not event:
return rec # no event → nothing authoritative happened
try:
exit_code = int(event.get("exit_code"))
except (TypeError, ValueError):
exit_code = None
if exit_code is None:
return rec # malformed event: stay WAITING (15E hostile case)
verdict = "success" if exit_code == 0 else "failure"
evidence = {"process_event": True, "exit_code": exit_code, "operation_id": event.get("run_ref", "")}
log(f"verify {rec['wait_id']}: {verdict} {json.dumps(evidence)[:120]}")
rec["status"] = "READY_TO_RESUME"
rec["verdict"] = verdict
if verdict == "failure":
rec["failure_evidence"] = evidence
rec["process_event_evidence"] = evidence
waitstore.save(rec)
return rec
verdict, evidence = _authoritative_verify(rec)
log(f"verify {rec['wait_id']}: {verdict} {json.dumps(evidence)[:120]}")
if verdict == "success":
rec["status"] = "READY_TO_RESUME"
rec["verdict"] = "success"
elif verdict == "failure":
rec["status"] = "READY_TO_RESUME"
rec["verdict"] = "failure"
rec["failure_evidence"] = verify.failed_job_evidence(rec.get("run_ref", ""))
else:
return rec # pending/unknown → stay WAITING
waitstore.save(rec)
return rec
def continuation_msg(rec: dict) -> str:
c = rec["continuation"]
if rec.get("verdict") == "success":
return (
f"External wait satisfied (wait {rec['wait_id']}).\n"
f"Original objective: {c.get('objective', '')}\n"
f"Waited for: {c.get('waited_for', '')}\n"
f"Authoritative result: workflow success verified via Gitea API.\n"
f"SHA/run: {rec.get('sha', '')}/{rec.get('run_ref', '')}\n"
f"Verify: {c.get('verify_when_woken', '')}\n"
f"Continue from: {c.get('on_success', '')}"
)
ev = json.dumps(rec.get("failure_evidence", {}))[:400]
return (
f"External wait finished with FAILURE (wait {rec['wait_id']}).\n"
f"Original objective: {c.get('objective', '')}\n"
f"Authoritative result: workflow FAILURE verified via Gitea API.\n"
f"Failed job evidence: {ev}\n"
f"SHA/run: {rec.get('sha', '')}/{rec.get('run_ref', '')}\n"
f"Do NOT treat this as success. Continue from: {c.get('on_failure', '')}"
)
# ── resume driver (single-flight, tree-owned) ─────────────────────────────
def resume(rec: dict) -> None:
"""Run one continuation to completion. Runs on a dedicated worker thread.
Preconditions: caller verified no inflight worker for this session."""
sid, wid = rec["session_id"], rec["wait_id"]
# REAL_ASYNC_BRIDGE §2 invariant: a successful authority-verified
# operation completion must NEVER be overwritten or downgraded by a
# legacy session/FIFO delivery failure. Terminal operation state
# (COMPLETED/FAILED — stamped by the waiter's own authority
# verification) is owned by the OPERATION domain; this function owns
# only SESSION-DELIVERY. Re-read from disk: the in-memory copy may be
# stale relative to the waiter's terminal stamp.
fresh = waitstore.load(wid)
if fresh and fresh.get("status") in waitstore.TERMINAL:
log(
f"resume {wid}: skipped — operation already terminal "
f"({fresh['status']}); delivery worker never overrides "
f"operation truth"
)
return
rec = fresh or rec
msg = continuation_msg(rec)
# B: busy-session guard before touching the lease
strays = stray_worker_pids(sid)
if strays:
rec["status"] = "READY_TO_RESUME"
rec["resume_note"] = f"queued: target session busy (stray pids {strays})"
waitstore.save(rec)
log(f"queued {wid}: session busy {strays}")
return
# pre-register the lease owner as THIS daemon so acquisition is atomic
lease = waitstore.acquire_lease(sid, wid, os.getpid(), os.getpid())
if lease is None:
live = waitstore.read_lease(sid)
rec["status"] = "READY_TO_RESUME"
rec["resume_note"] = f"queued: lease held by wait {live and live.get('wait_id')}"
waitstore.save(rec)
log(f"queued {wid}: lease held")
return
rec["status"] = "RESUMING"
rec["resume_attempts"] = rec.get("resume_attempts", 0) + 1
waitstore.save(rec)
try:
proc = subprocess.Popen(
resume_cmd(sid, msg, wid, rec.get("resume_cmd")),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
start_new_session=True,
)
except Exception as e:
waitstore.release_lease(sid, wid)
rec["status"] = "READY_TO_RETRY" # spawn failed pre-delivery → retryable
rec["resume_note"] = f"spawn failed: {e}"
waitstore.save(rec)
log(f"spawn {wid} failed pre-delivery: {e}")
return
pgid = proc.pid # start_new_session ⇒ pgid == child pid
waitstore.update_lease(sid, wait_id=wid, pid=proc.pid, pgid=pgid, pid_starttime=waitstore._pid_starttime(proc.pid))
log(f"launched {wid}: pid={proc.pid} pgid={pgid} attempt={rec['resume_attempts']}")
timed_out = False
try:
stdout, stderr = proc.communicate(timeout=WATCH_SECS)
rc = proc.returncode
except subprocess.TimeoutExpired:
timed_out = True
survivors = terminate_tree(pgid)
try:
stdout, stderr = proc.communicate(timeout=30)
except Exception:
stdout, stderr = "", ""
rc = proc.returncode
if survivors:
log(f"ERROR {wid}: group {pgid} survivors after KILL: {survivors}")
waitstore.release_lease(sid, wid)
if timed_out:
# D+E: timeout is NOT success. Tree terminated; retry truthfully.
if rec["resume_attempts"] >= MAX_ATTEMPTS:
rec["status"] = "RESUME_UNVERIFIED"
rec["resume_note"] = (
f"watch timeout x{rec['resume_attempts']}; process group {pgid} terminated; attempts exhausted"
)
else:
rec["status"] = "READY_TO_RETRY"
rec["resume_note"] = f"watch timeout; process group {pgid} terminated (TERM→KILL, verified); will retry"
log(f"timeout {wid}: tree {pgid} terminated, rc={rc}")
elif rc == 0:
# COMPLETION TRUTH (constraint 4): rc==0 means the headless worker
# EXITED CLEANLY — the strongest terminal evidence the supported CLI
# provides. Timeout/cancel can never reach this branch. The evidence
# chain (rc, output tail, timing) is recorded on the wait record.
terminal = "COMPLETED" if rec.get("verdict") == "success" else "FAILED"
rec["status"] = terminal
rec["resume_evidence"] = {
"kind": "worker_exited_rc0",
"rc": 0,
"output_tail": (stdout or "")[-400:],
"finished_at": waitstore.now(),
}
log(f"resumed {wid}: rc=0 → {terminal}")
else:
# Nonzero rc: delivery/completion unproven. Honest, not schedulable.
rec["status"] = "RESUME_UNVERIFIED"
rec["resume_evidence"] = {
"kind": "worker_exited_nonzero",
"rc": rc,
"output_tail": ((stdout or "") + (stderr or ""))[-400:],
"finished_at": waitstore.now(),
}
rec["resume_note"] = f"resume rc={rc}; delivery unverified"
log(f"resumed {wid}: rc={rc} → RESUME_UNVERIFIED")
waitstore.save(rec)
# ── startup recovery (invariant F) ────────────────────────────────────────
def startup_recovery() -> None:
for rec in waitstore.all_waits():
if rec.get("status") != "RESUMING":
continue
sid, wid = rec["session_id"], rec["wait_id"]
lease = waitstore.read_lease(sid)
if lease and lease.get("wait_id") == wid and waitstore._owner_alive(lease):
# Adopt the live worker and monitor it to a bounded conclusion.
pgid = lease["pgid"]
log(f"recover {wid}: adopted live worker pgid={pgid}")
deadline = time.time() + WATCH_SECS
while time.time() < deadline and pgid_members(pgid):
time.sleep(2)
survivors = terminate_tree(pgid) if pgid_members(pgid) else []
waitstore.release_lease(sid, wid)
rec["status"] = "RESUME_UNVERIFIED"
rec["resume_note"] = "adopted post-restart; outcome unverifiable" + (
f"; survivors killed: {survivors}" if survivors else ""
)
waitstore.save(rec)
log(f"recover {wid}: adopted worker ended → RESUME_UNVERIFIED")
else:
if lease:
waitstore.release_lease(sid, wid) # stale
rec["status"] = "RESUME_UNVERIFIED"
rec["resume_note"] = "recovered at daemon startup: no live owner; delivery unverifiable"
waitstore.save(rec)
log(f"recover {wid}: no live owner → RESUME_UNVERIFIED")
# ── reconciler + drive loop (invariant G: deterministic queue) ────────────
_inflight: dict[str, threading.Thread] = {}
_inflight_lock = threading.Lock()
def drive_ready() -> None:
for rec in waitstore.schedulable():
sid = rec["session_id"]
# CONTENTION POLICY (constraint 2): MiMo exposes NO supported
# busy/idle session signal (verified 2026-09-12: `mimo session list
# --format json` has no status field; no `session status` command;
# no status column in the session schema; no documented ACP/serve
# endpoint). Therefore automatic injection into interactive sessions
# is NOT YET SAFE and stays disabled. Only waits explicitly declared
# target_kind="headless" (disposable/owned sessions) auto-dispatch.
if rec.get("target_kind") != "headless":
if not rec.get("hold_reason"):
rec["hold_reason"] = (
"INTERACTIVE_AUTO_RESUME = NOT YET SAFE: "
"no supported MiMo busy/idle signal; "
"queued until operator releases"
)
waitstore.save(rec)
continue
if rec.get("ext", {}).get("resume_mode") == "self":
# REAL_ASYNC_BRIDGE §3: self-resuming wait (completion-file /
# inotify waiter owns its continuation). The daemon has NO
# delivery role here — dispatching a delivery worker would
# stamp a phantom rc!=0 over the waiter's own truthful
# terminal state (the canonical-state contradiction).
# NB: keyed on the explicit self marker, NOT on resume_cmd
# absence — records without inline resume_cmd may still be
# dispatchable via the WR_RESUME_CMD env template.
continue
with _inflight_lock:
t = _inflight.get(sid)
if t is not None and t.is_alive():
continue # single-flight: session already being driven
# B: never launch into a busy session
strays = stray_worker_pids(sid)
if strays:
log(f"drive: {rec['wait_id']} held, session busy {strays}")
continue
th = threading.Thread(target=resume, args=(rec,), daemon=True, name=f"resume-{rec['wait_id']}")
_inflight[sid] = th
# start INSIDE the lock: a just-created thread is not yet
# is_alive() until start() runs — starting outside the lock let
# the same drive cycle dispatch a second worker for the session
# (single-flight race, caught by the T2 invariant test).
th.start()
log(f"drive: dispatched {rec['wait_id']} (session {sid})")
def reconcile_once() -> None:
for rec in waitstore.pending():
if rec["status"] != "WAITING_EXTERNAL":
continue
created = rec.get("created_at", "")
try:
import calendar
age = time.time() - calendar.timegm(time.strptime(created, "%Y-%m-%dT%H:%M:%SZ"))
except ValueError:
continue
if age > RECONCILE_SECS:
try_transition(rec)
_tick = 0
def orphan_sweep() -> int:
"""REAL_ASYNC_BRIDGE §4: permanent mid-run orphan-lease recovery.
FAILURE DETECTION ONLY — never involved in normal completion
scheduling. A wait stuck in RESUMING past ORPHAN_GRACE_SECS means the
resume flow died between lease acquire and release (worker thread
lost, exception mid-watch). Reconcile exactly as startup_recovery
would have: release the lease, stamp RESUME_UNVERIFIED truthfully
(delivery unverifiable), never auto-relaunch.
"""
import calendar as _cal
healed = 0
for rec in waitstore.all_waits():
if rec.get("status") != "RESUMING":
continue
try:
updated = _cal.timegm(time.strptime(rec.get("last_updated", ""), "%Y-%m-%dT%H:%M:%SZ"))
except ValueError:
continue
if time.time() - updated < ORPHAN_GRACE_SECS:
continue # legitimate in-flight watch (up to WATCH_SECS)
wid, sid = rec["wait_id"], rec["session_id"]
lease = waitstore.read_lease(sid)
owner_dead = lease is not None and not waitstore._owner_alive(lease)
daemon_owned = lease is not None and lease.get("pid") == os.getpid()
if lease is None or owner_dead or daemon_owned:
if lease is not None:
waitstore.release_lease(sid, wid)
if rec.get("resume_attempts", 0) >= MAX_ATTEMPTS:
rec["status"] = "RESUME_UNVERIFIED"
else:
rec["status"] = "READY_TO_RETRY"
rec["resume_note"] = (
"orphaned mid-run: resume flow died between lease acquire "
"and delivery ("
+ ("lease owner dead" if owner_dead else "resume thread lost" if daemon_owned else "no lease")
+ "); orphan sweep recovered"
)
waitstore.save(rec)
log(f"orphan-sweep {wid}: {rec['status']} — {rec['resume_note']}")
healed += 1
return healed
def reconciler() -> None:
global _tick
while True:
# v0.3: the local-runner wake is the fast path; periodic
# authoritative verification demotes to a slower fallback
# (missed-event + daemon-restart recovery), not API polling.
_tick += 1
if _tick % RECONCILE_EVERY_TICKS == 0:
try:
reconcile_once()
except Exception as e:
log(f"reconcile error: {e}")
try:
orphan_sweep() # §4 failure detection — bounded, periodic
except Exception as e:
log(f"orphan-sweep error: {e}")
try:
drive_ready()
except Exception as e:
log(f"drive error: {e}")
with _inflight_lock:
for sid, t in list(_inflight.items()):
if not t.is_alive():
del _inflight[sid]
time.sleep(DRIVE_LOOP_SECS)
# ── webhook receiver (wake signal only) ───────────────────────────────────
def wake_sweep(event: dict, eid: str) -> dict:
"""Sweep WAITING_EXTERNAL waits through authoritative verification.
Runs on a background thread (the hook's HTTP response returns
instantly, minimising the runner's offline window). If any verified
wait got 'pending' — the normal case for a job that finished
microseconds ago: the post-task hook fires after results are reported
but before the runner's final task acknowledgement flips server-side
status — retry on a BOUNDED event-triggered schedule (default 4s x3),
never a polling loop. The final ack lands milliseconds after the hook
exits, so the first retry almost always sees the terminal state.
"""
arrival = time.time()
log(f"local wake {eid}: {json.dumps(event)[:140]}")
import calendar
def sweep() -> tuple[int, int, int]:
verified = advanced = pending = 0
for rec in waitstore.pending():
if rec["status"] != "WAITING_EXTERNAL":
continue
try:
age = time.time() - calendar.timegm(time.strptime(rec.get("created_at", ""), "%Y-%m-%dT%H:%M:%SZ"))
except ValueError:
continue
if age < WAKE_MIN_AGE_SECS:
continue
verified += 1
before = rec["status"]
try_transition(rec)
if rec["status"] != before and rec["status"] == "READY_TO_RESUME":
advanced += 1
elif rec["status"] == "WAITING_EXTERNAL":
pending += 1
return verified, advanced, pending
verified, advanced, pending = sweep()
log(
f"local wake {eid}: verified={verified} advanced={advanced} "
f"pending={pending} sweep_ms={int((time.time() - arrival) * 1000)}"
)
for attempt in range(1, WAKE_RETRIES + 1):
# Retry while the sweep didn't conclusively advance anything:
# pending verdicts (ack-window) AND fresh-wait skips (age gate).
# Bounded, event-triggered — not a polling loop.
if not pending and verified:
break
time.sleep(WAKE_RETRY_SECS)
verified, advanced, pending = sweep()
log(
f"local wake {eid}: retry{attempt} verified={verified} "
f"advanced={advanced} pending={pending} "
f"total_ms={int((time.time() - arrival) * 1000)}"
)
return {"event_id": eid}
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
if self.path == "/process-event":
# T15 §15D: process-exit events from builder/controller
# notifiers. Minimal dedicated endpoint — the Gitea webhook
# path stays untouched. Payload: {operation_id, exit_code,
# completed_at} + OPTIONAL provider/event_type/sha (T16
# §16C/§16E: the CI adapter registers as provider="gitea",
# event_type="ci_completed"; defaults preserve the T15 builder
# identity so existing notifiers are unaffected). The event is
# a WAKE, never truth — resumed controllers re-read canonical
# authority before any verdict.
length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(length)
try:
payload = json.loads(body) if body else {}
except json.JSONDecodeError:
self.send_response(400)
self.end_headers()
return
event = {
"provider": str(payload.get("provider", "builder")),
"event_type": str(payload.get("event_type", "builder_exit")),
"run_ref": str(payload.get("operation_id", "")) or None,
"sha": payload.get("sha"),
"exit_code": payload.get("exit_code"),
"completed_at": payload.get("completed_at") or "",
}
event = {k: v for k, v in event.items() if v is not None}
eid = waitstore.record_event(event)
log(f"process-event {eid}: {json.dumps(event)[:140]}")
# REAL_ASYNC_BRIDGE (R3): durable completion-file delivery.
# Every event reaching the bridge is written as
# resumes/<operation_id>.done so inotify waiters wake on the
# kernel event (R4) with no FIFO/lease machinery, and the
# event survives daemon AND waiter restarts (R9). Atomic
# rename; duplicate events rewrite identical content (R10).
_op_id = str(payload.get("operation_id", ""))
if _op_id:
try:
# Stable identity only: duplicate deliveries of the
# same logical event must produce byte-identical
# completion files (R10 idempotence by construction).
# completed_at is volatile and lives in the durable
# event log; the file is a WAKE SIGNAL, never truth.
_stable = {
k: event.get(k)
for k in ("provider", "event_type", "run_ref", "sha", "exit_code")
if event.get(k) is not None
}
completion_file.write_completion(_op_id, _stable)
except Exception as e: # noqa: BLE001 — delivery best-effort; wait-record transition + watchdog recovery still cover the lost file
log(f"process-event {eid}: completion-file write failed: {e}")
matched = 0
for rec in waitstore.pending():
if rec["status"] == "WAITING_EXTERNAL" and waitstore.match(rec, event):
matched += 1
try_transition(rec, event)
# matched READY controller waits are dispatched IMMEDIATELY
# (event-driven, no floor). Guard: only never-resumed waits —
# a duplicate/late event must not double-dispatch (15E);
# single-flight lease + this guard together make resume
# idempotent.
for rec in waitstore.pending():
if rec["status"] == "READY_TO_RESUME" and rec.get("resume_cmd") and rec.get("resume_attempts", 0) == 0:
threading.Thread(target=resume, args=(rec,), daemon=True, name=f"resume-{rec['wait_id']}").start()
matched += 1
try:
self.send_response(200)
self.end_headers()
self.wfile.write(json.dumps({"event_id": eid, "matched": matched}).encode())
except (BrokenPipeError, ConnectionResetError):
pass
return
if self.path == "/wake":
length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(length)
try:
payload = json.loads(body) if body else {}
except json.JSONDecodeError:
self.send_response(400)
self.end_headers()
return
event = {
"provider": "gitea-local-runner",
"task_id": str(payload.get("task_id", "")) or None,
"run_id": str(payload.get("run_id", "")) or None,
"repository": str(payload.get("repository", "")) or None,
"job_result": str(payload.get("job_result", "")) or None,
}
event = {k: v for k, v in event.items() if v}
eid = waitstore.record_event(event)
# Respond instantly; sweep on a worker thread. The hook is
# fire-and-forget (curl -m 2) — a broken pipe on write is
# harmless and must not propagate.
try:
threading.Thread(
target=wake_sweep, args=(event, eid), daemon=True, name=f"wake-{payload.get('task_id', '?')}"
).start()
except Exception as e:
log(f"wake spawn error: {e}")
try:
self.send_response(200)
self.end_headers()
self.wfile.write(json.dumps({"queued": True, "event_id": eid}).encode())
except (BrokenPipeError, ConnectionResetError):
pass
return
if self.path != "/webhook":
self.send_response(404)
self.end_headers()
return
length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(length)
try:
payload = json.loads(body)
except json.JSONDecodeError:
self.send_response(400)
self.end_headers()
return
action = payload.get("action") or payload.get("event_type") or ""
wf = payload.get("workflow_run") or {}
event = {
"provider": "gitea",
"event_type": f"workflow.{action}" if wf else action,
"sha": (wf.get("head_sha") or payload.get("sha") or "")[:12] or None,
"run_ref": str(wf.get("id") or payload.get("run_number") or "") or None,
}
event = {k: v for k, v in event.items() if v}
eid = waitstore.record_event(event)
log(f"webhook event {eid}: {json.dumps(event)[:140]}")
matched = 0
for rec in waitstore.pending():
if rec["status"] == "WAITING_EXTERNAL" and waitstore.match(rec, event):
matched += 1
try_transition(rec)
self.send_response(200)
self.end_headers()
self.wfile.write(json.dumps({"event_id": eid, "matched": matched}).encode())
def do_GET(self):
if self.path == "/health":
self.send_response(200)
self.end_headers()
self.wfile.write(b'{"ok":true}')
return
self.send_response(404)
self.end_headers()
def log_message(self, *a):
pass
def main() -> int:
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8791
# H (constraint 1): flock is the SINGLETON AUTHORITY. daemon.pid's
# content is diagnostic metadata only — it never gates startup, never
# implies liveness, and is overwritten after the exclusive lock is held.
# Stale pid content (incl. SIGKILL debris) is thus harmless by design.
PIDFILE.parent.mkdir(parents=True, exist_ok=True)
lock_fd = os.open(PIDFILE, os.O_CREAT | os.O_RDWR, 0o644)
try:
fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError:
log("refusing start: another daemon holds the pidfile flock")
print("daemon already running (pidfile flock held)", file=sys.stderr)
return 1
os.ftruncate(lock_fd, 0)
os.write(lock_fd, f"{os.getpid()}\n".encode()) # diagnostic, lock held for life
startup_recovery()
t = threading.Thread(target=reconciler, daemon=True)
t.start()
log(f"daemon starting on :{port}")
srv = ThreadingHTTPServer(("0.0.0.0", port), Handler)
try:
srv.serve_forever()
except KeyboardInterrupt:
pass
return 0
if __name__ == "__main__":
sys.exit(main())
+246
View File
@@ -0,0 +1,246 @@
#!/usr/bin/env python3
"""verify.py — authoritative Gitea verification (webhook ≠ success).
Queries the Gitea API for the TRUE state of the run/job/commit the wait
references. Returns (verdict, evidence). verdict ∈ {success, failure,
pending, unknown}. Only this module may move a wait out of
WAITING_EXTERNAL.
"""
from __future__ import annotations
import json, subprocess
from pathlib import Path
HOST = "https://gitea.thingscouldgetdicey.online"
REPO = "conductor/rune"
def _token() -> str:
# Canonical ladder without importing Rune: env → token file → git store
import os
tok = os.environ.get("GITEA_TOKEN", "").strip()
if tok:
return tok
tf = os.path.expanduser("~/.config/opencode/secrets/gitea-token")
if os.path.isfile(tf):
return open(tf).read().strip()
r = subprocess.run(
["git", "credential", "fill"],
input="protocol=https\nhost=gitea.thingscouldgetdicey.online\nusername=conductor\n\n",
capture_output=True,
text=True,
timeout=10,
)
for line in r.stdout.splitlines():
if line.startswith("password="):
return line[9:]
raise RuntimeError("no gitea credential available")
def _api(path: str) -> tuple[int, object]:
import urllib.request
req = urllib.request.Request(f"{HOST}/api/v1/repos/{REPO}{path}", headers={"Authorization": f"token {_token()}"})
try:
with urllib.request.urlopen(req, timeout=20) as r:
return r.status, json.loads(r.read())
except urllib.error.HTTPError as e:
return e.code, {"error": e.read().decode()[:200]}
def _jobs_list(run_ref: str):
"""GET jobs for a run, unwrapping the {'jobs': [...]} envelope."""
s, data = _api(f"/actions/runs/{run_ref}/jobs")
if s != 200:
return s, None
if isinstance(data, dict):
data = data.get("jobs", [])
return s, data if isinstance(data, list) else None
def _resolve_run_id(run_ref: str, sha: str) -> str:
"""Task-list ids are NOT run ids. If run_ref lookup 404s, resolve the
real run id by head SHA via /actions/runs (T15: run 6907 vs task 15743)."""
if run_ref:
s, jobs = _jobs_list(run_ref)
if s == 200 and jobs:
return run_ref
if sha:
s, runs = _api(f"/actions/runs?limit=30")
items = runs.get("workflow_runs", []) if isinstance(runs, dict) else (runs if isinstance(runs, list) else [])
# One SHA fans out to multiple runs (lint + deploy-only). Prefer a
# substantive (non-skipped) run; a skipped-only run proves nothing.
fallback = ""
for r in items:
if str(r.get("head_sha", "")).startswith(sha):
rid = str(r.get("id"))
if r.get("conclusion") != "skipped":
return rid
fallback = fallback or rid
return fallback
return ""
def verify_run(run_ref: str, sha: str, expected: str) -> tuple[str, dict]:
"""run_ref = actions run id (or task id — resolved via SHA lookup).
Defect fixed 2026-09-12 (surfaced by W/R-v0.3 live proof, run 7110):
the jobs query here used raw _api + isinstance(jobs, list), but this
Gitea wraps jobs in the {"jobs": [...]} envelope — the check always
failed, so EVERY EXACT_WORKFLOW verdict silently fell through to the
aggregate commit-status fallback, where unrelated runs on the same
SHA (multi-run fan-out: ci.yml + deploy-only) poison the verdict.
Use _jobs_list (unwraps the envelope), same as _resolve_run_id."""
run_ref = _resolve_run_id(run_ref, sha)
if run_ref:
s, jobs = _jobs_list(run_ref)
if s == 200 and isinstance(jobs, list) and jobs:
conclusions = {j.get("name"): j.get("conclusion") for j in jobs}
statuses = {j.get("status") for j in jobs}
if statuses <= {"completed"}:
verdict = "success" if set(conclusions.values()) <= {"success", "skipped"} else "failure"
return verdict, {"run_ref": run_ref, "jobs": conclusions}
return "pending", {"run_ref": run_ref, "statuses": sorted(statuses)}
if sha:
s, st = _api(f"/commits/{sha}/status")
if s == 200 and isinstance(st, dict):
state = st.get("state", "unknown") # success|error|failure|pending|warning
rid = _resolve_run_id("", sha) # attach run id so failure evidence works
if state == "success":
return "success", {"sha": sha, "state": state, "run_ref": rid}
if state in ("error", "failure"):
return "failure", {"sha": sha, "state": state, "run_ref": rid}
return "pending", {"sha": sha, "state": state}
return "unknown", {"run_ref": run_ref, "sha": sha}
def failed_job_evidence(run_ref: str, sha: str = "") -> dict:
"""For failure continuations: exact failing job + conclusion."""
run_ref = _resolve_run_id(run_ref, sha)
if not run_ref:
return {}
s, jobs = _jobs_list(run_ref)
if s != 200 or not isinstance(jobs, list):
return {}
bad = [j for j in jobs if j.get("conclusion") not in ("success", "skipped", None)]
return {
"failed_jobs": [
{"name": j.get("name"), "conclusion": j.get("conclusion"), "url": j.get("url", "")} for j in bad
]
}
# ── RELEASE_DEPLOY verification (W/R v0.1, overnight handoff Part 2) ──────
# Aggregate commit status is NOT a valid completion authority for Rune's
# release/deploy flow: the release successor SHA, not the source SHA, is what
# converges (observed live: wr-1789179026 stranded on pending while 0.66.15
# was deployed). The authority chain is:
# source SHA → source runs terminal → release successor commit (descendant)
# → successor runs success → deployed/current identity converges.
def _git(args: list[str], repo_root: str) -> str:
import subprocess
r = subprocess.run(["git"] + args, capture_output=True, text=True, cwd=repo_root or None, timeout=15)
if r.returncode != 0:
raise RuntimeError(f"git {' '.join(args[:2])}: {r.stderr.strip()[:120]}")
return r.stdout
def _runs_for_sha(sha: str) -> list[dict]:
s, runs = _api("/actions/runs?limit=50")
items = runs.get("workflow_runs", []) if isinstance(runs, dict) else []
return [r for r in items if str(r.get("head_sha", "")).startswith(sha)]
def _sha_verdict_from_runs(runs: list[dict]) -> tuple[str, dict]:
"""success | failure | pending from a SHA's workflow runs.
Terminal verdict ONLY when every run is completed — mixed states
(some success, some still in flight) must stay PENDING (observed live:
a SHA with 1 completed-success + 1 queued + 1 in-progress was judged
success mid-flight; wr-1789182522 2026-09-12)."""
if not runs:
return "pending", {"note": "no runs found"}
non_terminal = [r for r in runs if r.get("status") != "completed"]
if non_terminal:
return "pending", {"in_flight": len(non_terminal)}
conclusions = {r.get("name"): r.get("conclusion") for r in runs}
bad = {n: c for n, c in conclusions.items() if c == "failure"}
if bad:
return "failure", {"failed": bad}
return "success", {"runs": conclusions}
def verify_release_deploy(source_sha: str, repo_root: str = "", deployed_link: str = "") -> tuple[str, dict]:
"""RELEASE_DEPLOY authority chain. Returns (verdict, evidence)."""
source_sha = (source_sha or "")[:12]
ev: dict = {"source_sha": source_sha, "kind": "RELEASE_DEPLOY"}
# 1. Source SHA's own runs must be terminal and healthy.
src_runs = _runs_for_sha(source_sha)
src_verdict, src_ev = _sha_verdict_from_runs(src_runs)
ev["source"] = src_ev
if src_verdict != "success":
return src_verdict, ev
# 2. Release successor: first chore(release) commit descending from source.
if not repo_root:
return "pending", {**ev, "note": "no repo_root — cannot check lineage"}
try:
out = _git(
[
"log",
"--format=%H %s",
"--reverse",
f"{source_sha}..master",
"--grep=^chore\\(release\\)",
"--extended-regexp",
],
repo_root,
)
except RuntimeError as e:
return "pending", {**ev, "note": str(e)}
successors = [l for l in out.splitlines() if l.strip()]
if not successors:
return "pending", {**ev, "note": "no release successor yet"}
succ_line = successors[0]
succ_sha_full, _, succ_subject = succ_line.partition(" ")
succ_sha = succ_sha_full[:12]
ev["successor"] = {"sha": succ_sha, "subject": succ_subject[:80]}
# Ancestry is guaranteed by the range, but verify explicitly (deterministic
# lineage over title guessing):
try:
_git(["merge-base", "--is-ancestor", source_sha, succ_sha_full], repo_root)
except RuntimeError:
return "failure", {**ev, "note": "successor is not a descendant of source"}
# 3. Successor runs must be terminal and healthy.
succ_runs = _runs_for_sha(succ_sha)
succ_verdict, succ_ev = _sha_verdict_from_runs(succ_runs)
ev["successor"]["runs"] = succ_ev
if succ_verdict != "success":
return succ_verdict, ev
# 4. Deployed identity convergence. A mismatch is FAILURE only when the
# successor's runs are ALL completed — if any is still running/queued the
# deploy may simply not have propagated yet → PENDING (observed live:
# reconciler checked mid-deploy, verdict failure, deployment converged
# ~2 min later; wr-1789182522 2026-09-12).
if deployed_link:
link = Path(deployed_link)
if link.is_symlink() or link.is_dir():
deployed = str(link.resolve())
deployed_id = Path(deployed).name
ev["deployed"] = deployed_id
if not deployed_id.startswith(succ_sha[:12]):
still = [r for r in _runs_for_sha(succ_sha) if r.get("status") != "completed"]
if still:
return "pending", {**ev, "note": "deploy propagating (runs in flight)"}
return "failure", {**ev, "note": f"deployed {deployed_id} != successor {succ_sha} (all runs terminal)"}
else:
return "failure", {**ev, "note": f"deployed link missing: {deployed_link}"}
return "success", ev
+337
View File
@@ -0,0 +1,337 @@
#!/usr/bin/env python3
"""waitstore.py — wait/resume state machine v0.2 (developer tooling, NOT Rune).
States:
RUNNABLE → WAITING_EXTERNAL → READY_TO_RESUME → RESUMING → COMPLETED | FAILED
↑ │
│ ├─(watch timeout, tree killed)
│ └─(rc!=0 / unverifiable)
READY_TO_RETRY ←─────┘
RESUME_UNVERIFIED : delivery after crash/timeout cannot be proven.
TERMINAL-ish — never auto-scheduled; explicit
`requeue()` required to make it READY_TO_RESUME.
TIMED_OUT : external event never arrived within the wait budget.
Waits persist as waits/<wait_id>.json (atomic tmp+rename).
Leases persist as leases/<session_id>.lease.json (atomic tmp+rename,
validated against /proc so a dead or PID-reused owner is stealable).
"""
from __future__ import annotations
import fcntl, json, os, tempfile, time, uuid
from pathlib import Path
ROOT = Path(os.environ.get("WR_ROOT", "") or Path(__file__).resolve().parent)
WAITS = ROOT / "waits"
EVENTS = ROOT / "events"
LEASES = ROOT / "leases"
SCHEDULABLE = {"READY_TO_RESUME", "READY_TO_RETRY"}
VALID_STATES = {
"RUNNABLE",
"WAITING_EXTERNAL",
"READY_TO_RESUME",
"RESUMING",
"COMPLETED",
"FAILED",
"READY_TO_RETRY",
"RESUME_UNVERIFIED",
"TIMED_OUT",
}
TERMINAL = {"COMPLETED", "FAILED", "RESUME_UNVERIFIED", "TIMED_OUT"}
def _atomic_write(path: Path, data: dict) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
fd, tmp = tempfile.mkstemp(dir=str(path.parent), prefix=".tmp-")
try:
with os.fdopen(fd, "w") as f:
json.dump(data, f, indent=1, sort_keys=True)
os.replace(tmp, path)
# T17 17C: the bridge root is SHARED across users (deploy waiter
# runs as the runner user; daemon as nick). mkstemp creates 0600 —
# daemon re-saves locked the runner user out of records it must
# read (waitstore.load in await_resume_event; find_matching_event
# replay scan silently skipped them). 0644: owner writes, all read.
os.chmod(path, 0o644)
except BaseException:
if os.path.exists(tmp):
os.unlink(tmp)
raise
def now() -> str:
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
def new_wait(
*,
session_id: str,
provider: str,
event_type: str,
repo: str,
sha: str = "",
run_ref: str = "",
expected: str = "success",
wait_kind: str = "EXACT_WORKFLOW",
target_kind: str = "interactive",
repo_root: str = "",
deployed_link: str = "",
objective: str = "",
waited_for: str = "",
verify_when_woken: str = "",
on_success: str = "",
on_failure: str = "",
resume_cmd: list | None = None,
) -> dict:
wid = f"wr-{int(time.time())}-{uuid.uuid4().hex[:6]}"
rec = {
"wait_id": wid,
"session_id": session_id,
"created_at": now(),
"provider": provider,
"event_type": event_type,
"repo": repo,
"sha": sha,
"run_ref": run_ref,
"expected": expected,
# T15 §15D: PROCESS_EVENT — a wait whose authoritative verdict is
# the matching process-exit event itself (exit_code carried on the
# event), e.g. a builder/controller process completing. The event
# IS the authority here (the notifier wrapper observed the real
# process exit); no Gitea lookup applies.
"wait_kind": wait_kind
if wait_kind in ("EXACT_WORKFLOW", "RELEASE_DEPLOY", "PROCESS_EVENT")
else "EXACT_WORKFLOW",
# CONTENTION POLICY: "interactive" (default, conservative) never
# auto-dispatches — MiMo exposes NO supported busy/idle session
# signal, so injecting into a possibly-busy interactive session is
# NOT YET SAFE. Only explicitly declared headless/disposable
# targets ("headless") are auto-resumed.
"target_kind": target_kind if target_kind in ("headless", "interactive") else "interactive",
"repo_root": repo_root,
"deployed_link": deployed_link,
# T15 §15D: per-wait resume command override (placeholder template,
# same substitution as WR_RESUME_CMD: {session_id}/{msg}/{wait_id}).
# A synthetic controller session (e.g. a Scaffolding stage driver)
# is NOT a MiMo session — the global mimo-shaped default cannot
# resume it, which is exactly why fixed-sleep polling crept back
# in T12-T14. Per-wait override makes ANY registered continuation
# target resumable through the same bridge.
"resume_cmd": list(resume_cmd) if resume_cmd else None,
"status": "WAITING_EXTERNAL",
"last_verified": "",
"resume_attempts": 0,
"continuation": {
"objective": objective,
"waited_for": waited_for,
"verify_when_woken": verify_when_woken,
"on_success": on_success,
"on_failure": on_failure,
},
}
_atomic_write(WAITS / f"{wid}.json", rec)
return rec
def load(wid: str) -> dict | None:
p = WAITS / f"{wid}.json"
return json.loads(p.read_text()) if p.is_file() else None
def save(rec: dict) -> None:
assert rec.get("status") in VALID_STATES, rec.get("status")
rec["last_updated"] = now()
_atomic_write(WAITS / f"{rec['wait_id']}.json", rec)
def all_waits() -> list[dict]:
out = []
for p in WAITS.glob("*.json"):
try:
out.append(json.loads(p.read_text()))
except json.JSONDecodeError:
continue
return out
def pending() -> list[dict]:
return [r for r in all_waits() if r.get("status") in {"WAITING_EXTERNAL"} | SCHEDULABLE]
def schedulable() -> list[dict]:
"""READY waits, oldest first — deterministic queue order."""
rs = [r for r in all_waits() if r.get("status") in SCHEDULABLE]
rs.sort(key=lambda r: (r.get("created_at", ""), r.get("wait_id", "")))
return rs
def requeue(wid: str, note: str) -> dict | None:
"""Operator-explicit requeue of a RESUME_UNVERIFIED/RESUME_FAILED wait."""
rec = load(wid)
if not rec:
return None
rec["status"] = "READY_TO_RESUME"
rec["resume_note"] = note
save(rec)
return rec
# ── events (wake signals, idempotent) ─────────────────────────────────────
def record_event(event: dict) -> str:
import hashlib
body = json.dumps(event, sort_keys=True)
eid = hashlib.sha256(body.encode()).hexdigest()[:16]
f = EVENTS / f"{eid}.json"
if not f.is_file():
_atomic_write(f, {"event_id": eid, "received_at": now(), "event": event})
return eid
def match(rec: dict, event: dict) -> bool:
if rec.get("provider") != event.get("provider"):
return False
if rec.get("event_type") and event.get("event_type") and rec["event_type"] != event["event_type"]:
return False
if rec.get("sha") and event.get("sha") and rec["sha"] != event["sha"]:
return False
if rec.get("run_ref") and event.get("run_ref") and rec["run_ref"] != event["run_ref"]:
return False
return True
# ── leases (per-session single-flight, /proc-validated) ───────────────────
#
# Invariant 3 (PID reuse): liveness is NEVER "PID exists". The lease records
# pid + pgid + process starttime (/proc/<pid>/stat field 22); all three must
# match or the owner is a different process wearing a recycled PID.
#
# Invariant 1 (flock as authority): the lease file's flock serializes
# read-modify-write across processes and dies with its holder, so a crashed
# daemon can never wedge the lock. The lease JSON + /proc decide who OWNS the
# session; the flock only prevents racing acquirers from double-stealing.
_lease_fds: dict[str, int] = {} # session_id → open fd holding the flock
def _pid_starttime(pid: int) -> str | None:
"""Field 22 of /proc/<pid>/stat — distinguishes PID reuse."""
try:
stat = Path(f"/proc/{pid}/stat").read_text()
return stat.rsplit(")", 1)[1].split()[19]
except (OSError, IndexError):
return None
def _owner_alive(lease: dict) -> bool:
pid = lease.get("pid")
if not pid or not Path(f"/proc/{pid}").is_dir():
return False
if _pid_starttime(pid) != lease.get("pid_starttime"):
return False # PID reused by an unrelated process
try:
if os.getpgid(pid) != lease.get("pgid"):
return False
except OSError:
return False
return True
def lease_path(session_id: str) -> Path:
# T16 §16C finding (observed live in daemon.log): session ids like
# "controller-<op>-Verboo Code/glm-5.3-flash" contain "/" — the raw
# embed made lease creation raise FileNotFoundError, so resume
# delivery for those waits could never succeed. Sanitize to a flat
# filesystem-safe name; the hash suffix prevents collisions between
# ids that sanitize identically.
import hashlib as _hashlib
import re as _re
safe = _re.sub(r"[^A-Za-z0-9._-]+", "_", session_id)[:80]
digest = _hashlib.sha256(session_id.encode()).hexdigest()[:10]
return LEASES / f"{safe}-{digest}.lease.json"
def read_lease(session_id: str) -> dict | None:
p = lease_path(session_id)
if not p.is_file():
return None
try:
return json.loads(p.read_text())
except json.JSONDecodeError:
return None
def _locked_lease_fd(session_id: str) -> int | None:
"""Open the lease file and take its flock (non-blocking). The fd stays
open (lock held) until release_lease closes it."""
if session_id in _lease_fds:
return _lease_fds[session_id] # already hold it
LEASES.mkdir(parents=True, exist_ok=True)
fd = os.open(lease_path(session_id), os.O_CREAT | os.O_RDWR, 0o600)
try:
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError:
os.close(fd)
return None # another process holds the lock — mid-acquire
_lease_fds[session_id] = fd
return fd
def _write_lease_locked(session_id: str, lease: dict) -> None:
fd = _lease_fds[session_id]
os.ftruncate(fd, 0)
os.lseek(fd, 0, os.SEEK_SET)
os.write(fd, json.dumps(lease, indent=1, sort_keys=True).encode())
os.fsync(fd)
def acquire_lease(session_id: str, wait_id: str, pid: int, pgid: int) -> dict | None:
"""Atomically create or steal the session lease. Returns the lease dict,
or None if a live owner holds it (or another process is mid-steal).
Staleness is decided by /proc identity evidence, never by age alone."""
fd = _locked_lease_fd(session_id)
if fd is None:
return None
existing = read_lease(session_id)
if existing and _owner_alive(existing):
_lease_fds.pop(session_id, None)
os.close(fd)
return None # live worker owns this session
lease = {
"session_id": session_id,
"wait_id": wait_id,
"pid": pid,
"pgid": pgid,
"pid_starttime": _pid_starttime(pid),
"acquired_at": now(),
"state": "RESUMING",
}
_write_lease_locked(session_id, lease)
return lease
def update_lease(session_id: str, **fields) -> None:
lease = read_lease(session_id) or {}
lease.update(fields)
_write_lease_locked(session_id, lease)
def release_lease(session_id: str, wait_id: str) -> bool:
"""Release only if the current lease belongs to this wait (no stealing
a successor's lease during a slow shutdown path). Closes the flock fd."""
lease = read_lease(session_id)
if not (lease and lease.get("wait_id") == wait_id):
return False
fd = _lease_fds.pop(session_id, None)
if fd is not None:
try:
fcntl.flock(fd, fcntl.LOCK_UN)
except OSError:
pass
os.close(fd)
lease_path(session_id).unlink(missing_ok=True)
return True
+56
View File
@@ -0,0 +1,56 @@
import type { Hooks } from "@mimo-ai/plugin"
// event-wait-guard — RETIRED (T21.5 §6/§8, 2026-09-17).
//
// HISTORY: this hook was the T21 §21B-3 runtime interception layer that
// blocked model-generated sleep→poll continuation (T20 regression), and
// later carried the T21.1 §21.1C cwd guard. Both were DOWNSTREAM patches:
// they corrected the model's output instead of fixing the generator.
//
// ROOT-CAUSE FIX (T21.5 §2-§5): the generator was ci_event_wait's
// wait loop returning nonterminal control to the model — caused by a
// single hard-coded master-deploy authority that was blind to branch/PR
// operations. T21.5 fixed this upstream:
// - OPERATION_SPECS: per-operation-type completion authority
// (branch_ci/pull_request_checks -> own Actions runs;
// master_ci/deploy/release -> deploy qualification);
// - SINGLE LOGICAL AWAIT: all nonterminal conditions re-arm inside
// ci_event_wait.run(); the model sees only terminal results;
// - structured PendingOperation envelopes (op_type, authority,
// internal_rearm_count) propagate to the controller.
//
// With the generator fixed, runtime interception is retired per the
// tranche rule FIXED UPSTREAM BEHAVIOUR > DOWNSTREAM GUARD CORRECTION.
// Regression prevention lives in the test suite:
// tests/test_single_logical_await.py (no model re-arm contract)
// tests/test_ci_event_wait.py (watchdog=recovery, event=wake)
// tests/test_wait_guard_corpus.py (canonical wait-pattern corpus)
// NOT as a permanent runtime block. The hook file stays installed as a
// documented no-op so canonical-source/install parity (test_hook_drift,
// test_hook_parity) continues to prove the installed runtime matches
// this repository's canonical state.
export const EventWaitGuard: Hooks = {
"tool.execute.before": async (_input, _output) => {
// RETIRED — no interception. See history above.
return
},
}
export const CwdGuard: Hooks = {
"tool.execute.before": async (_input, _output) => {
// RETIRED (T21.5 §8) — the harness/construction execution boundary
// now resolves project context upstream (execution-context binding).
// No runtime refusal. See tests/test_auto_execution_context.py.
return
},
}
export const CombinedGuards: Hooks = {
"tool.execute.before": async (input, output) => {
// Kept as the default export shape; both sub-guards are retired.
return
},
}
export default CombinedGuards
+92
View File
@@ -0,0 +1,92 @@
import { tool } from "@mimo-ai/plugin"
import { execSync } from "child_process"
export default tool({
description: "Wait for an external operation (CI, deploy, release) to complete. Call this when you cannot make progress until an external process finishes. You do NOT need to know the sha, op_type, or duration — Rune's controller resolves those from the bound PendingOperation. Just call this when you want to wait.",
args: {
operation_id: tool.schema.string().optional().describe("Optional: the operation to wait for. If omitted, the controller resolves the current pending operation automatically."),
task_id: tool.schema.string().optional().describe("Optional: the active task id (e.g. T4). When provided, resolution is scoped to operations bound to this task — stale global operations are never consulted."),
sha: tool.schema.string().optional().describe("OPTIONAL HINT — non-authoritative. The controller uses this only to help resolve which operation you mean."),
op_type: tool.schema.string().optional().describe("OPTIONAL HINT — non-authoritative. The controller derives the canonical operation type from repository/Gitea state."),
deadline_secs: tool.schema.number().optional().describe("OPTIONAL HINT — non-authoritative. The controller applies governed policy timing. Real user deadlines must be stated in conversation."),
},
async execute(args) {
// INTENT NORMALIZATION — model output is intent; the controller
// owns execution semantics. All model args are hints recorded for
// observability; canonical truth comes from the bound operation.
const parts = []
if (args.operation_id) parts.push(`--operation-id ${args.operation_id}`)
if (args.task_id) parts.push(`--task-id ${args.task_id}`)
if (args.sha) parts.push(`--sha ${args.sha}`)
if (args.op_type) parts.push(`--model-op-type ${args.op_type}`)
if (args.deadline_secs) parts.push(`--model-deadline ${args.deadline_secs}`)
const normCmd = `python3 /mnt/workdrive/issue161-harness/scripts/await_intent_cli.py normalize ${parts.join(" ")}`
let norm: any
try {
const normOut = execSync(normCmd, {
cwd: "/mnt/workdrive/issue161-harness",
encoding: "utf-8" as const,
timeout: 120000,
env: { ...process.env, RUNE_WR_ROOT: "/mnt/workdrive/wr-bridge" },
})
norm = JSON.parse(normOut.trim().split("\n").find((l: string) => l.startsWith("{")) || "{}")
} catch (e: any) {
return JSON.stringify({ ok: false, stage: "normalization", error: (e.stderr || e.message || "?").slice(0, 300) })
}
if (norm.normalized === "AMBIGUOUS") {
return JSON.stringify({ ok: false, stage: "normalization", ...norm })
}
if (!norm.normalized) {
return JSON.stringify({ ok: false, stage: "normalization", ...norm })
}
// canonical continuation: stored/derived op_type + POLICY deadline
// (the sha for the wait comes from the normalization resolution,
// NOT from the model's args)
const waitSha = norm.sha_for_wait || args.sha || ""
if (!waitSha) {
return JSON.stringify({ ok: false, stage: "resolution", error: "no sha resolved for the pending operation", norm })
}
const cmd = `python3 /mnt/workdrive/issue161-harness/scripts/ci_event_wait.py --sha ${waitSha} --deadline ${norm.deadline_secs} --op-type ${norm.op_type}`
try {
const result = execSync(cmd, {
cwd: "/mnt/workdrive/issue161-harness",
timeout: (norm.deadline_secs + 60) * 1000,
encoding: "utf-8" as const,
env: { ...process.env, DEPLOY_SHA: waitSha, RUNE_WR_ROOT: "/mnt/workdrive/wr-bridge", RUNE_WR_CODE: "/home/nick/.local/share/mimo-wait-resume" },
})
const jl = result.trim().split("\n").find((l: string) => l.startsWith("{"))
const envelope = jl ? JSON.parse(jl) : { ok: false, error: "no output" }
return JSON.stringify({
...envelope,
normalization: {
model_deadline_ignored: norm.model_deadline_ignored,
model_op_type_ignored: norm.model_op_type_ignored,
model_sha_ignored: !!args.sha && args.sha !== waitSha,
canonical_op_type: norm.op_type,
canonical_authority: norm.authority,
deadline_provenance: norm.deadline_provenance,
matched_by: norm.matched_by || norm.evidence || "auto-resolved",
},
})
} catch (e: any) {
const lines = (e.stdout || "").trim().split("\n")
const jl = lines.find((l: string) => l.startsWith("{"))
if (jl) {
const envelope = JSON.parse(jl)
return JSON.stringify({
...envelope,
normalization: {
model_deadline_ignored: norm.model_deadline_ignored,
model_op_type_ignored: norm.model_op_type_ignored,
model_sha_ignored: !!args.sha && args.sha !== waitSha,
canonical_op_type: norm.op_type,
canonical_authority: norm.authority,
deadline_provenance: norm.deadline_provenance,
matched_by: norm.matched_by || norm.evidence || "auto-resolved",
},
})
}
return JSON.stringify({ ok: false, stage: "wait", error: (e.stderr || e.message || "?").slice(0, 300) })
}
},
})
+167
View File
@@ -0,0 +1,167 @@
// bash — executes a shell command. PROCESS-OWNERSHIP CONTRACT (v2):
//
// MODE A (tool-owned): the child is launched in its own process group;
// stdout/stderr are CAPTURED pipes (never the TUI tty). On timeout or
// failure the ENTIRE process group is signalled (SIGTERM, grace,
// SIGKILL) so grandchildren cannot survive the invocation, then the
// result is finalized. After finalization NO descendant retains a
// writer into MiMo's streams.
//
// MODE B (explicitly detached): a command suffixed with " &" that the
// user/model intends to outlive the tool (nohup/setsid/disown-style)
// is executed via the detached helper: stdin=/dev/null, stdout/stderr
// -> durable log file, own session (setsid), no MiMo PTY inheritance.
// The tool returns the log path as the durable handle.
//
// Wait-intent compilation (sleep/check/poll aimed at a pending operation)
// is preserved: interception happens at the SEMANTIC level via
// await_intent route-bash, and a NO-OP for anything that is not a genuine
// continuation of a live pending operation. Arbitrary shell syntax
// (quotes, pipes, semicolons) passes through untouched — the router
// response is only trusted when it carries normalized=true AND
// ContinuePendingOperation with a concrete operation bound.
import { tool } from "@mimo-ai/plugin"
import { spawn, execFile } from "child_process"
const REPO = "/mnt/workdrive/issue161-harness"
const WR_ROOT = "/mnt/workdrive/wr-bridge"
// ── MODE A: process-group-owned capture execution ──────────────────────
interface OwnedResult { stdout: string; stderr: string; status: number; timedOut: boolean }
function runOwned(command: string, timeoutMs: number): Promise<OwnedResult> {
return new Promise((resolve) => {
// /bin/sh -c "command" in its OWN process group (detached:true makes
// the child a session/group leader we can signal as a WHOLE).
const child = spawn("/bin/sh", ["-c", command], {
cwd: REPO,
stdio: ["ignore", "pipe", "pipe"], // NEVER inherit the TUI tty
env: process.env,
detached: true, // own process group
})
let stdout = "", stderr = ""
let timedOut = false
let settled = false
const outChunks: Buffer[] = []
const errChunks: Buffer[] = []
child.stdout!.on("data", (d) => outChunks.push(d))
child.stderr!.on("data", (d) => errChunks.push(d))
const killGroup = (sig: NodeJS.Signals) => {
try { process.kill(-child.pid!, sig) } catch { /* already gone */ }
}
const finalize = (status: number) => {
if (settled) return
settled = true
clearTimeout(timer)
// detach listeners, then resolve — no late listeners remain
child.stdout!.removeAllListeners()
child.stderr!.removeAllListeners()
resolve({ stdout: Buffer.concat(outChunks).toString(), stderr: Buffer.concat(errChunks).toString(), status, timedOut })
}
const timer = setTimeout(() => {
timedOut = true
killGroup("SIGTERM")
// bounded grace for clean shutdown, then SIGKILL the group
setTimeout(() => killGroup("SIGKILL"), 2000)
// the exit/close event settles; a hard stop-guard settles anyway
setTimeout(() => finalize(124), 4000)
}, timeoutMs)
child.on("error", (e) => finalize(-1))
child.on("close", (code) => finalize(code ?? -1))
})
}
// ── MODE B: explicit detachment for "cmd &" invocations ────────────────
function runDetached(command: string): Promise<string> {
return new Promise((resolve) => {
const logPath = `/tmp/mimo-detached-${Date.now()}-${Math.floor(Math.random() * 1e6)}.log`
const out = `exec >>"${logPath}" 2>&1; exec 0</dev/null; ${command}`
const child = spawn("/bin/sh", ["-c", `setsid /bin/sh -c '${out.replace(/'/g, `'\\''`)}'`], {
cwd: REPO,
stdio: "ignore", // child rebinds its own fds — no PTY inheritance
env: process.env,
detached: true,
// unref: MiMo's event loop does not wait for it
})
child.unref()
resolve(JSON.stringify({
detached: true,
log: logPath,
note: "process detached (setsid, own fds); output streams to the log file only; fd 0/1/2 do NOT reference the MiMo tty",
}))
})
}
// ── wait-intent routing (semantic level, preserved from v1) ────────────
async function tryCompileWaitIntent(command: string): Promise<string | null> {
let routeOut: string
try {
routeOut = await new Promise<string>((resolve, reject) => {
execFile("python3", [`${REPO}/scripts/await_intent_cli.py`, "route-bash", "--command", command], {
cwd: REPO, timeout: 60000, encoding: "utf-8",
env: { ...process.env, RUNE_WR_ROOT: WR_ROOT },
maxBuffer: 8 * 1024 * 1024,
}, (err, stdout) => { if (err && !stdout) reject(err); else resolve(stdout || "") })
})
} catch {
return null // router unavailable: normal execution (never block on routing)
}
const jl = routeOut.trim().split("\n").find((l) => l.startsWith("{"))
if (!jl) return null
let norm: any
try { norm = JSON.parse(jl) } catch { return null }
// STRICT gate: compile ONLY a normalized, concrete continuation.
if (!(norm.normalized === true &&
norm.action === "ContinuePendingOperation" &&
norm.operation_id && norm.sha_for_wait)) return null
const waitCmd = `python3 ${REPO}/scripts/ci_event_wait.py --sha ${norm.sha_for_wait} --deadline ${norm.deadline_secs} --op-type ${norm.op_type}`
const r = await runOwned(waitCmd, (norm.deadline_secs + 60) * 1000)
const jline = r.stdout.trim().split("\n").find((l) => l.startsWith("{"))
if (jline) {
return JSON.stringify({
...JSON.parse(jline),
intent_compilation: {
raw_command_kind: norm.raw_command_kind || "sleep_poll",
literal_sleep_executed: false,
operation_id: norm.operation_id,
canonical_op_type: norm.op_type,
authority: norm.authority,
},
})
}
return JSON.stringify({ ok: false, stage: "compiled-wait", error: (r.stderr || "no output").slice(0, 300) })
}
export default tool({
description: "Run a shell command. If the command expresses external wait intent (sleep/check/poll aimed at CI, deploy, or release operations with pending operations), it is automatically compiled to Rune's event-driven continuation — you do not need to manage wait mechanics. Plain local commands execute normally. Commands ending in ' &' run detached with output logged to a file.",
args: {
command: tool.schema.string().describe("Shell command to run"),
},
async execute(args) {
// 1. wait-intent compilation (semantic, strict-gated)
try {
const compiled = await tryCompileWaitIntent(args.command)
if (compiled !== null) return compiled
} catch { /* fall through to normal execution */ }
// 2. MODE B: explicit detachment for backgrounded invocations
const cmd = args.command
if (/\b(nohup|setsid)\b/.test(cmd) || /&\s*$/.test(cmd)) {
return await runDetached(cmd)
}
// 3. MODE A: tool-owned, group-killed, captured execution
const r = await runOwned(cmd, 120000)
const text = r.stdout + (r.stderr ? `\n[stderr]\n${r.stderr}` : "")
if (r.timedOut) {
return `exit timed-out (process group terminated): ${text.slice(0, 500)}`
}
if (r.status !== 0) {
return `exit ${r.status}: ${text.slice(0, 500) || "?"}`
}
return text || "(no output)"
},
})
+16 -3
View File
@@ -1,7 +1,7 @@
{
"_comment": "GENERATED by rune.spine \u2014 compatibility view for legacy umbrella consumers. DO NOT EDIT: structural truth lives in store/spine/current.json; edits here are flagged by spine drift.",
"_generated_by_spine": true,
"generated_at": "2026-09-15T18:24:01Z",
"generated_at": "2026-09-16T18:23:38Z",
"umbrellas": {
"capability": {
"description": "Tool selection and execution authority",
@@ -12,6 +12,7 @@
"capability_broker",
"connection_pool",
"evidence_probe",
"forge",
"forge_client",
"intent_continuity",
"local_tool_runtime",
@@ -44,7 +45,8 @@
"drift_core",
"drift_ledger",
"drift_observability",
"failure_auto_filer"
"failure_auto_filer",
"programme_gates"
],
"depends_on": [],
"mirrors": [],
@@ -216,9 +218,11 @@
"attention_capsule",
"auto_context",
"auto_tasks",
"behaviour_ledger",
"build_basis",
"capability_fit",
"capability_ledger",
"cognitive_budgeter",
"complexity_scorer",
"construction_activation",
"construction_commands",
"construction_decision",
@@ -234,16 +238,25 @@
"drift_resume",
"event_wait",
"failure_recovery",
"graph_projection",
"impact_preview",
"iteration",
"layout_contract",
"programme_continuation",
"project_registry",
"release_notes",
"salience_gate",
"scaffold",
"spine",
"stage_guards",
"structural_migration",
"structural_model",
"structural_store",
"testing_mode",
"toolchain",
"tui_backlog",
"wait_guard",
"wait_guard_corpus",
"witness"
],
"depends_on": [
+9 -12
View File
@@ -104,15 +104,15 @@
},
{
"path": "src/rune/attention_capsule.py",
"role": "AttentionCapsule — the typed ledger of admitted blocks the model receives (to_messages re-derives the runtime payload)",
"role": "AttentionCapsule \u2014 the typed ledger of admitted blocks the model receives (to_messages re-derives the runtime payload)",
"disposition": "retain-authority",
"evidence": "P1-02 (#25): the capsule IS the product the model receives — the broker renders admitted blocks into it, the safety override mutates its user block in place (re-measured), runtime.run consumes capsule.to_messages(), and the trace v4 capsule_messages/capsule_content_hashes prove the logged capsule EXACTLY equals the sent payload. Legacy create_capsule/to_prompt (string-trim fake budgeting) deprecated with no live callers."
"evidence": "P1-02 (#25): the capsule IS the product the model receives \u2014 the broker renders admitted blocks into it, the safety override mutates its user block in place (re-measured), runtime.run consumes capsule.to_messages(), and the trace v4 capsule_messages/capsule_content_hashes prove the logged capsule EXACTLY equals the sent payload. Legacy create_capsule/to_prompt (string-trim fake budgeting) deprecated with no live callers."
},
{
"path": "src/rune/inference_trace.py",
"role": "Durable per-run inference trace (tokens served/estimated, output, latency, per-block visibility)",
"disposition": "migrate-adapter",
"evidence": "P0-07 (#23): persists what ContextBroker (#19) assembled and what the transport served — builds FROM the broker trace, never re-measures; store state/inference_traces.jsonl."
"evidence": "P0-07 (#23): persists what ContextBroker (#19) assembled and what the transport served \u2014 builds FROM the broker trace, never re-measures; store state/inference_traces.jsonl."
}
]
},
@@ -160,13 +160,13 @@
"path": "src/rune/capability_broker.py",
"role": "Authoritative capability selector (select_for_job + verified floor + find_capability escape hatch)",
"disposition": "retain-authority",
"evidence": "P1-01 (#24): select_for_job is the single selection authority — its ordered decision feeds LocalToolRuntime(tools=) at both construction sites and the _contributor_tools prompt block; degrade-not-crash falls back to full TOOLS with a tier-1 record_failure."
"evidence": "P1-01 (#24): select_for_job is the single selection authority \u2014 its ordered decision feeds LocalToolRuntime(tools=) at both construction sites and the _contributor_tools prompt block; degrade-not-crash falls back to full TOOLS with a tier-1 record_failure."
},
{
"path": "memory/index/capability_cards.json",
"role": "Capability-card index (rebuilt on empty/stale/corrupt; native cards synthesized from TOOLS metadata)",
"disposition": "migrate-adapter",
"evidence": "P1-01 (#24): load_cards validates and rebuilds — the packaged empty array no longer passes as an index; cards carry risk/preconditions/side_effects copied from tool_executor.TOOLS."
"evidence": "P1-01 (#24): load_cards validates and rebuilds \u2014 the packaged empty array no longer passes as an index; cards carry risk/preconditions/side_effects copied from tool_executor.TOOLS."
},
{
"path": "src/rune/local_tool_runtime.py",
@@ -486,12 +486,6 @@
"disposition": "retain-authority",
"evidence": "Defines the owner-approved canary-first Gitea sequence."
},
{
"path": "src/rune/plugins/build_mode/__init__.py",
"role": "Checkpoint/ledger state-machine primitive used by current governed builds",
"disposition": "migrate-adapter",
"evidence": "Persists checkpoints but encodes an older phase order."
},
{
"path": "src/rune/build_mode.py",
"role": "Separate BuildManager and worktree engine",
@@ -504,6 +498,9 @@
"disposition": "archive-candidate",
"evidence": "Reports fixed success strings for phases without executing their claimed effects."
}
],
"notes": [
"T17 17U: plugin BuildStateMachine deleted (zero importers; second parallel orchestrator)"
]
},
{
@@ -540,4 +537,4 @@
]
}
]
}
}
+53
View File
@@ -0,0 +1,53 @@
# Rune Semantic Release Policy (T20 §20B)
Pre-1.0 convention. Scaffolding "V1" does NOT mean Rune 1.0.
## Bump kinds
**PATCH** (0.66.80 → 0.66.81):
- bug fix, correction, implementation/internal maintenance
- no meaningful new user/system capability
**MINOR** (0.66.80 → 0.67.0):
- meaningful new Rune capability
- new subsystem functionality
- materially expanded system behaviour
- significant new supported workflow
**MAJOR**: reserved for Rune 1.0+ compatibility/public-contract semantics.
## How the machinery expresses this (no manual version edits)
The existing machinery already maps commit shape → bump kind:
- `semver_bump.next_version` (PR titles): feat→minor, else patch
- `version_convergence.highest_warranted_bump` (push ranges):
feat/breaking→minor, fix/perf→patch, else None
**Tranche-level rule (20B)**: a feature tranche lands its feature
commits (`feat(...)`), and the convergence machinery bumps MINOR at
the tranche's release boundary. Patch-only follow-ups (fix/perf)
warrant only PATCH — the machinery cannot accidentally re-minor
because `highest_warranted_bump` takes the max over the *post-
boundary* range, and a fix-only range yields "patch".
## Why T20 → 0.67.0
T20 is a feature-level tranche (Rune-wide retrofit: structural
taxonomy, capability/behaviour ledgers, rune graph, /testing backlog,
Spine modernization, semantic release). The release is classified
MINOR under this policy, earned through the governed convergence
machinery — never a manual version edit.
## T20 0.67.0 release record
T20 classified MINOR under this policy: Rune-wide retrofit (structural
taxonomy, capability/behaviour ledgers, rune graph, /testing backlog,
Spine modernization, semantic release) = new subsystem functionality +
materially expanded system behaviour. Earned through the governed
convergence machinery (feat-bearing range → minor), never a manual edit.
## Root cause record (T20, 2026-09-16)
semver_bump re-derived the bump kind from the SYNTHESIZED title text —
"minor: converged release" matched nothing → patch. Feature tranches
T18/T19/T20 downgraded to 0.66.77-83. Fixed: MINOR_PREFIX_RE carries the
convergence kind through; first push after the fix with a feat-bearing
range lands 0.67.0 through the normal machinery.
+79
View File
@@ -0,0 +1,79 @@
# SCAFFOLDING IMPLEMENTATION AUDIT + V1 GAP MATRIX (T18 §18B)
_Every claim classified against evidence. Scale: LIVE_PROVEN (production
evidence) / TEST_PROVEN (suite green, no production exercise) /
IMPLEMENTED_UNPROVEN / PARTIAL / PLANNED / ABSENT / LEGACY / DUPLICATE._
## Kernel claims
| Claim | Status | Evidence |
|---|---|---|
| Contract lifecycle + legal transitions | LIVE_PROVEN | 28 contracts driven through real flow; refusal envelopes observed |
| classify_mode (one classifier) | LIVE_PROVEN | gates TUI /build (17G), operator hook, tests 62+ |
| Conversational intent layer | TEST_PROVEN | 22+ held-outs incl. unseen paraphrases; 17R live exercise (one real conversational proof) |
| Programme continuation | LIVE_PROVEN | 17E 4-stage proof on real bridge; restart/dup/failure held-outs |
| Event-driven waits (builder) | LIVE_PROVEN | T15 15Q real model build; T17E async stage |
| Event-driven waits (CI) | LIVE_PROVEN | T16 16D deadline path ×2 + T17 natural wake (mode=event, 112.5s) |
| Event replay (before-registration) | TEST_PROVEN + production-realistic exercise | 17C 4.6s vs real completed CI; natural-cycle confirmation: the 17Z wake arrived mid-wait, replay path itself not yet hit naturally |
| Cross-user bridge delivery | LIVE_PROVEN | 17C repro as runner user (2.05s) + production deploys |
| Provider boundary | TEST_PROVEN (structural guard) + observed loud-failure | 16F/G suites; no silent fallback observed in production runs |
| Builder cannot self-certify | LIVE_PROVEN | T14 closure refusals ×2; phase-gated close |
| Completion truth evidence-driven | LIVE_PROVEN | contract checkpoints; verify_test_truth refusals |
| Interruption/resume | LIVE_PROVEN | T14E SIGKILL; 17E restart held-outs |
| Impact preview (visible, pre-build) | ABSENT → T18 | nothing renders structured impact before build today |
| Structural project model (first-class) | PARTIAL | stores are spine-shaped (entities/rels) but projects model CONTRACTS, not architecture; no domain/subsystem/interface nesting for arbitrary projects |
| Structural queries/impact closure for projects | PARTIAL | spine.impact_frontier exists for Rune; project stores lack query API |
| Research→Build Basis (durable, versioned) | ABSENT → T18 | research output today stays in transient context/artifacts |
| Research-depth decision at construction entry | PARTIAL | ADAPT routes task classes; no explicit construction-stage research gate |
| OUTWARD prior-art-first pass | PARTIAL | stage exists in Research Core; not strengthened to precedent-first ordering |
| Schema versioning + consent-gated migration | PARTIAL | stores carry schema_version=1; no migration machinery/consent flow |
| Environment/runtime model | PARTIAL | deployment_surface exists in Spine; project scaffolds don't model envs |
| Workspace lifecycle (worktree per contract) | PARTIAL | BuildManager worktrees exist (legacy adapter); not wired to contracts |
| Forge abstraction | ABSENT (Gitea-coupled scripts) | deploy/qualify call Gitea API directly |
| Structural drift for project scaffolds | PARTIAL | drift_resume (16P) covers checkpoint-vs-reality; no file/entity-level drift detection |
| Graph projection for visualisation | ABSENT → T18 | Spine has snapshots; no stable read-only graph projection |
| Memory/Substrate | ABSENT (BY DESIGN — out of scope) | rule L |
## V1 North Star gap matrix (this tranche's targets)
| North Star capability | Gap | T18 action |
|---|---|---|
| Research-led construction (early research, Build Basis) | ABSENT Build Basis | 18C: build_basis.py + ADAPT depth hook |
| OUTWARD precedent-first | PARTIAL | 18D: strengthen stage contract + real research run |
| First-class structural Scaffold | PARTIAL (contract-shaped) | 18E: structural_model.py over structural_store core |
| Structure→scope→governance | PARTIAL | 18F: queries + impact closure feeding contracts |
| Visible impact preview | ABSENT | 18G: impact_preview projection |
| Structural drift | PARTIAL | 18H: extend drift_resume with entity-level checks |
| Schema versioning + consent | PARTIAL | 18I: migration + consent flow + tests |
| Environment/runtime model | PARTIAL | 18J: env entities + verification distinction |
| Workspace lifecycle | PARTIAL (legacy adapter) | 18K: workspace contract on the BuildManager adapter |
| Forge boundary | ABSENT | 18L: forge.py adapter interface (Gitea impl) |
| Full route convergence | PARTIAL (classify shared; backends differ) | 18M: audit + smallest fix |
| Generalisation (not phrase list) | TEST_PROVEN | 18N: adversarial paraphrase families |
| Self-scaffold | PARTIAL (contracts only) | 18O: structural model of Scaffolding itself |
| Upgrade-via-Scaffold proof | ABSENT | 18P: one real upgrade driven by the scaffold |
| Graph projection | ABSENT | 18Q: versioned read-only projection |
## T19 additions (post-T18 classifications)
| Capability | Status | Evidence |
|---|---|---|
| Project/Scaffold Registry | TEST_PROVEN | registry tests; boundaries proven (no truth in index, no Spine leak) |
| /build governance fail-closed | LIVE_PROVEN | 19A: T18 degradation paths removed; failure-path held-outs |
| Capability Ledgers | TEST_PROVEN | lifecycle+history+packet tests |
| Surgical context packets | LIVE_PROVEN | 19R: packet bound exactly 1 file for a real change |
| Behaviour/Test Specs + hardening lifecycle | TEST_PROVEN | ledger suites incl. generation history |
| Selective invalidation | TEST_PROVEN | 19S-2/5/9/10 held-outs |
| Predictive vs descriptive impact | TEST_PROVEN | delta persistence tests |
| Post-change obligations | TEST_PROVEN | machine-derived from structural truth |
| /testing backend + aggregate | TEST_PROVEN + LIVE (19R) | testing_mode suites; command wired in real TUI |
| Observation→closure (user-confirm gate) | TEST_PROVEN | closure requires retest AND user confirm |
| Forge failure never rewrites truth | TEST_PROVEN | 19S-14 held-out |
| Self-hosted T19 change | LIVE_PROVEN | contract 19r-testing-command, 11-step scaffold-driven flow |
## Kernel exceptions required (rule B record)
None yet. Every T18 plan so far EXTENDS (new modules consuming
structural_store) rather than modifying frozen kernel behavior. Any
exception discovered during implementation will be recorded here with
evidence that the V1 requirement could not be met otherwise.
+153
View File
@@ -0,0 +1,153 @@
# SCAFFOLDING V1 DEFINITION (T18 §18B)
_Plane-English + technical definition of Scaffolding as it ACTUALLY
exists at tranche T18 start (master 29570f4). Companion artifact:
SCAFFOLDING_IMPLEMENTATION_AUDIT (evidence classification per claim)._
## What Scaffolding is (plain English)
Scaffolding is Rune's **construction authority**: the governed process
by which software gets built or changed. It takes an authorized need,
classifies what kind of construction it is, proportionally governs the
work through a durable contract (with legal lifecycle transitions),
derives scope from structural reality, drives builders, waits on events
(not sleeps), refuses self-certified completion, verifies from
canonical reality, and records evidence on durable objects so any
future session can resume without a transcript.
It is NOT a scheduler, NOT a memory system, NOT a provider selector,
NOT a research engine, NOT a release system. It is the layer that
decides *whether, how, and with what obligations* construction happens
— and whether it actually finished.
## What it owns
- **Construction classification** (`construction_mode.classify_mode` —
the ONE classifier: NOT_CONSTRUCTION / LOCAL_CHANGE /
EXISTING_PROJECT_CHANGE / PROJECT_BUILD / UNKNOWN; never silently
chooses; unknown surfaces probe questions)
- **Conversational interpretation** (`construction_intent.py` —
meaning/referents/authorization; DISCUSSION→PROPOSAL→USER_APPROVED;
feeds the ONE classifier, never deepens)
- **Project Scaffold + Contracts** (`scaffold.py` — legal phase map
draft→approved→building→verifying→…→closed/failed/abandoned; scope;
obligations; checkpoints; predicted-vs-observed at close)
- **Programme continuation** (`programme_continuation.py` —
run_construction_programme: stages 1:1 to contracts, persist→canary→
next; "task done ≠ programme done")
- **Impact/scope machinery** (construction_decision, completeness
gates, affected-surface propagation)
- **Stage guards** (`stage_guards.py` — readiness blocks, liveness
proves progress, exit canary proves effect; verify_test_truth refuses
false greens)
- **Failure taxonomy** (`failure_recovery.py` — 7 classes incl.
PROVIDER_UNAVAILABLE; retry budgets; PERMANENT/APPROVAL never retry)
- **Builder interface** (`construction_interface.py` — ONE builder
boundary; JSON envelopes; builder identity recorded-but-inert;
discover_active_contract; capabilities())
## What it does NOT own
- **Provider/model selection** (provider layer owns; ambient context
via `provider_context.require_model`; structural guard test)
- **Research competence** (Research Core owns; Scaffolding invokes and
consumes — see 18C integration)
- **Asynchronous wait/resume machinery** (W/R event bridge owns;
`event_wait.py` is the repo-side adapter: event drives continuation,
watchdog detects failure, durable record is truth)
- **Release/deployment** (deploy_stage/promotion/qualify + Gitea
workflows own it; Scaffolding records evidence)
- **Rune's structural self-model** (Spine owns — `spine.py` over
`structural_store.py`; Scaffolding stores use the SAME generic core)
- **Long-horizon relevance/memory** (future Substrate — not built;
seam exists: construction_projection + construction_commands)
- **The TUI** (client; sends text, renders projections)
## How the flows work
**New build (PROJECT_BUILD)**: conversational/explicit intent →
classify_mode (+ project-existence evidence) → activate() bootstraps a
provisional Project Scaffold (identity before repository, UNKNOWN
fields explicit) → contract draft→approved (user provenance) →
building (builder under event wait) → verifying (exit canary;
independent evidence) → closed with predicted-vs-observed.
**Existing-project change**: intent → classify (scoped/file/target
evidence; proportional) → attach to existing authority → light or full
contract by depth → same lifecycle, proportionate ceremony.
**Research-led construction (V1 target — CORRECTED 2026-09-15, Nick):**
> THE RESEARCH PLAN ITSELF IS CONTEXTUALLY GENERATED. There is NO
> permanent research-topic checklist. For every request:
> REQUEST/INTENT → understand target/current reality → identify material
> unknowns/decisions/risks → generate a context-specific research plan →
> perform proportional INWARD/OUTWARD/deeper research. OUTWARD asks what
> already exists relevant to THIS specific intent (adopt/adapt/combine/
> reject/gaps/innovation). The categories researched are selected from
> context; deeper technical research is driven by unresolved questions
> and consequence level, never performed mechanically.
`build_basis.generate_research_plan()` implements this: topics are
synthesized from THIS intent's material unknowns/decisions/risks;
the held-out suite proves unrelated intents produce materially
different plans and that no canned category list exists.
**Future upgrades (V1 target)**: authorized need → research-depth
decision (ADAPT) → Research Core pass → durable Build Basis →
structural Scaffold consult/construct → impact closure + visible
preview → contract with structurally-derived scope → isolated
workspace → governed build/verify → merge/release → structural
Scaffold updated. (Parts of this chain are new in T18 — see audit.)
**Interruption/resume**: SIGKILL-safe. Durable contract + programme
records + wait records are the truth; fresh process resumes from
identity alone; duplicate signals idempotent (live-proven T14E/T17E).
**Deployment/release participation**: Scaffolding does not run the
pipeline; it consumes qualified CI/deploy evidence, records deployed
identity on contracts, and refuses closure without it where contracted.
## T19 layer model (the general engineering system)
> "The Scaffold should let the LLM operate with surgical precision
> locally, while preserving global awareness of what else must be
> checked or changed."
>
> "Lower active cognitive load without losing the whole idea."
- **STRUCTURAL MODEL** tells Rune what the project contains / how it connects.
- **CAPABILITY LEDGER** provides compact local re-entry context (stable IDs
across moves; history preserved through supersede/retire).
- **BEHAVIOUR VALIDATION LEDGER** tells Rune what intended behaviour is
actually confirmed — generation-aware, selective invalidation, automated
green never implies USER_CONFIRMED.
- **PROJECT REGISTRY** tells Rune which Scaffolds/projects exist
(discovery/index only — not structural truth, not Spine).
- **ITERATION ENGINE** (predictive→descriptive impact, impact-delta
records, machine-derived obligations) makes quicksand protection
STANDARD behaviour of every construction route.
- **/TESTING** is a dependency-aware hardening programme: dynamic queue,
fix-for-B-may-reopen-A, closure requires retest AND user confirmation.
- **FORGE** provides workflow/history projection (failure never rewrites
canonical validation truth).
- **FUTURE SUBSTRATE** will coordinate sequencing/current focus/pause-
resume but does NOT own structural or validation truth.
## How Spine relates
Spine = Rune's canonical structural self-model (structural_store
generic core, maturity ladder, impact_frontier). Scaffolding's project
stores share the SAME generic core (zero Rune constants) — one graph
discipline, two scopes: Spine models Rune; project Scaffolds model
their projects. No duplicate truth: a project's structural model lives
in its own store; Rune's lives in Spine. Later retrofit will modernise
Spine using the discipline V1 generalises (18K relationship).
## How Substrate will interact (without owning internals)
Read: versioned outbound projection (construction_projection,
SCHEMA_VERSION=1). Write: bounded validated commands
(construction_commands — 6 commands, lifecycle-validated, idempotent,
no direct storage mutation). Substrate asks; Scaffolding decides.
Memory graph explicitly out of scope (T18 rule L).
+5 -3
View File
@@ -22,7 +22,9 @@ and deploys Rune itself to `/mnt/workdrive/rune-code/current`.
| Construction (build lifecycle) | `src/rune/scaffold.py` + `construction_*.py` | scaffold store (`<dir>/.rune/scaffold`) |
| Builder sessions | `scripts/t15_builder_session.py` (event-driven successor; T14 is legacy) | contract + wait records |
| Provider/model selection | `rune_operator` final decider; ambient context via `provider_context.require_model` | `state/model_routing_profiles.json` |
| Wait/resume (any long op) | `src/rune/event_wait.py` + W/R daemon (out-of-repo snapshot at `~/.local/share/mimo-wait-resume`, data root `/mnt/workdrive/wr-bridge`) | waits/events/leases/resumes JSON |
| Wait/resume (any long op) | `src/rune/event_wait.py` + W/R daemon (out-of-repo snapshot at `~/.local/share/mimo-wait-resume`, data root `/mnt/workdrive/wr-bridge` — PIN `RUNE_WR_ROOT`: unpinned consumers get a loud warning, never a silent split-brain) | waits/events/leases/resumes JSON |
| Programme continuation | `src/rune/programme_continuation.py` (WIRED T17: `run_construction_programme` binds stages to Scaffolding contracts) | scaffold store `programmes/` |
| Conversational construction | `src/rune/construction_intent.py` (meaning/referents/authorization) → `construction_mode.classify_mode` (the ONE classifier) | intents are transient; contracts durable |
| CI qualification verdict | `scripts/deploy_qualify.py` (two legs, fail-closed) | Gitea commit status + runs |
| Release/deploy | `scripts/deploy_stage.py` → `deploy_promotion.py` (monotonic, flock) | deploy root + `current`/`last-good` symlinks |
| Research | `scripts/research_task_tree.py` (authority; labels are projection) | `research/issue-<N>/` |
@@ -45,8 +47,8 @@ and deploys Rune itself to `/mnt/workdrive/rune-code/current`.
## Unfinished / blocked / deprecated
- programme_continuation.py: implemented + tested but UNWIRED (no
production caller) — the "continue problem" machinery awaits routing.
- programme_continuation.py: WIRED (T17 17D — run_construction_programme
is the canonical multi-stage driver; 17E live-proven).
- maintenance.yml canary-suite job: loops `shared/canary_*.py` — wrong
dir (real canaries in `src/rune/`), job likely no-ops (LEGACY).
- daemon out-of-repo: the W/R daemon is deployed from
+2 -2
View File
@@ -1,4 +1,4 @@
{
"BLE001": 354,
"S110": 80
"BLE001": 347,
"S110": 75
}
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.66.71"
version = "0.86.0"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
+3 -3
View File
@@ -1,5 +1,5 @@
{
"errorCount": 117,
"warningCount": 443,
"informationCount": 0
"errorCount": 117,
"warningCount": 454,
"informationCount": 0
}
+33 -28
View File
@@ -1,29 +1,34 @@
{
"typeCheckingMode": "standard",
"pythonVersion": "3.14",
"reportMissingImports": "warning",
"reportMissingModuleSource": "none",
"reportMissingTypeStubs": "none",
"reportUnknownArgumentType": "none",
"reportUnknownParameterType": "none",
"reportUnknownVariableType": "none",
"reportUnknownMemberType": "none",
"reportMissingTypeArgument": "none",
"reportUntypedFunctionDecorator": "none",
"reportUntypedClassDecorator": "none",
"reportUntypedBaseClass": "none",
"reportPrivateUsage": "none",
"reportOptionalMemberAccess": "warning",
"reportUnnecessaryIsInstance": "warning",
"reportUnnecessaryComparison": "warning",
"reportUnnecessaryTypeIgnoreComment": "warning",
"reportPossiblyUnboundVariable": "error",
"reportUndefinedVariable": "error",
"reportAttributeAccessIssue": "warning",
"include": ["src/rune"],
"exclude": [
"src/rune/__pycache__",
"**/node_modules",
"**/__pycache__"
]
}
"typeCheckingMode": "standard",
"pythonVersion": "3.14",
"reportMissingImports": "warning",
"reportMissingModuleSource": "none",
"reportMissingTypeStubs": "none",
"reportUnknownArgumentType": "none",
"reportUnknownParameterType": "none",
"reportUnknownVariableType": "none",
"reportUnknownMemberType": "none",
"reportMissingTypeArgument": "none",
"reportUntypedFunctionDecorator": "none",
"reportUntypedClassDecorator": "none",
"reportUntypedBaseClass": "none",
"reportPrivateUsage": "none",
"reportOptionalMemberAccess": "warning",
"reportUnnecessaryIsInstance": "warning",
"reportUnnecessaryComparison": "warning",
"reportUnnecessaryTypeIgnoreComment": "warning",
"reportPossiblyUnboundVariable": "error",
"reportUndefinedVariable": "error",
"reportAttributeAccessIssue": "warning",
"include": [
"src/rune"
],
"exclude": [
"src/rune/__pycache__",
"**/node_modules",
"**/__pycache__"
],
"extraPaths": [
"scripts"
]
}
+6 -6
View File
@@ -1,5 +1,5 @@
# This file was autogenerated by uv via the following command:
# uv pip compile pyproject.toml -o requirements-ci.txt --extra dev --extra memory --python-version 3.11
# uv pip compile pyproject.toml -o /tmp/req_new.txt --extra dev --extra memory --python-version 3.11
aiohappyeyeballs==2.7.1
# via aiohttp
aiohttp==3.14.3
@@ -34,7 +34,7 @@ httpcore==1.0.9
# via httpx
httpx==0.28.1
# via rune (pyproject.toml)
idna==3.19
idna==3.20
# via
# anyio
# httpx
@@ -56,7 +56,7 @@ mdit-py-plugins==0.6.1
# via textual
mdurl==0.1.2
# via markdown-it-py
multidict==6.8.0
multidict==6.9.0
# via
# aiohttp
# yarl
@@ -64,13 +64,13 @@ nodeenv==1.10.0
# via pyright
packaging==26.3
# via pytest
platformdirs==4.11.8
platformdirs==4.11.11
# via textual
pluggy==1.6.0
# via
# diff-cover
# pytest
propcache==0.5.2
propcache==0.5.4
# via
# aiohttp
# yarl
@@ -118,5 +118,5 @@ typing-extensions==4.16.0
# textual-autocomplete
websockets==17.1
# via rune (pyproject.toml)
yarl==1.24.5
yarl==1.25.1
# via aiohttp
+480
View File
@@ -0,0 +1,480 @@
"""await_intent.py — ASYNC CORRECTIVE PATCH: intent normalization at the
execution boundary.
PRINCIPLE: MODEL OUTPUT = INTENT. RUNE CONTROLLER = EXECUTION SEMANTICS.
The model may express continuation intent in any quality — `await_operation
(deadline_secs=1200)`, `await-operation [op_type=master_ci, sha=X]`,
"sleep 300 then check CI", "poll until done". When a matching
PendingOperation exists (or the deterministic resolver can derive the
operation truth from git/Gitea state), the controller executes the
CANONICAL continuation and discards model-supplied scheduling policy.
Authority provenance for timing:
USER_EXPLICIT / EXTERNAL_CONTRACT / TEST_HARNESS /
GOVERNED_OPERATION_POLICY -> authoritative, retained
MODEL_INVENTED -> non-authoritative, ignored
This module owns NO storage: it reads the existing W/R wait store
(rune.event_wait) and git/Gitea state. The canonical continuation remains
scripts/ci_event_wait.py run() (the T21.5 single-logical-await).
"""
from __future__ import annotations
import json
import os
import re
import subprocess
import sys
from pathlib import Path
from typing import Any
_SCRIPT_DIR = Path(__file__).resolve().parent
if str(_SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(_SCRIPT_DIR))
_REPO_SRC = str(_SCRIPT_DIR.parent / "src")
if _REPO_SRC not in sys.path:
sys.path.insert(0, _REPO_SRC)
WR_BRIDGE = Path(os.environ.get("RUNE_WR_ROOT", "/mnt/workdrive/wr-bridge"))
REPO = Path("/mnt/workdrive/issue161-harness")
GIT_TIMEOUT = 10
# Default policy deadline per operation type — GOVERNED_OPERATION_POLICY.
# The model's numbers never become policy; these are the controller's.
POLICY_DEADLINES = {
"branch_ci": 1800,
"pull_request_checks": 1800,
"master_ci": 1800,
"deploy": 900,
"release": 900,
}
VALID_OP_TYPES = ("branch_ci", "pull_request_checks", "master_ci",
"deploy", "release")
# ── timing provenance ───────────────────────────────────────────────────────
def classify_timing_provenance(model_deadline: Any = None,
user_deadline: Any = None,
contract_deadline: Any = None) -> tuple[int, str]:
"""Resolve the effective deadline + its provenance.
Authority order: USER_EXPLICIT > EXTERNAL_CONTRACT > TEST_HARNESS >
GOVERNED_OPERATION_POLICY. MODEL_INVENTED values are recorded but
NEVER used as policy.
"""
if model_deadline is not None:
try:
model_deadline = int(model_deadline)
except (TypeError, ValueError):
model_deadline = None
if user_deadline is not None:
return int(user_deadline), "USER_EXPLICIT"
if contract_deadline is not None:
return int(contract_deadline), "EXTERNAL_CONTRACT"
return (model_deadline or 0), "MODEL_INVENTED" # caller discards this
# ── PendingOperation lookup over the existing W/R wait store ────────────────
def _load_wait_records() -> list[dict]:
"""All durable wait records from the bridge store (read-only)."""
out = []
waits = WR_BRIDGE / "waits"
if not waits.is_dir():
return out
for f in sorted(waits.glob("*.json")):
try:
rec = json.loads(f.read_text())
rec["_file"] = f.name
out.append(rec)
except (OSError, ValueError):
continue
return out
def _rec_operation_id(rec: dict) -> str:
return str((rec.get("ext") or {}).get("operation_id")
or rec.get("operation_id") or "")
def _rec_sha(rec: dict) -> str:
return str(rec.get("sha") or "")
def pending_operations(sha: str = "", op_type: str = "") -> list[dict]:
"""Pending (non-terminal) operations, optionally filtered."""
terminal = {"COMPLETED", "FAILED", "CLOSED", "CANCELLED", "EXPIRED"}
out = []
for rec in _load_wait_records():
status = str(rec.get("status", "")).upper()
if status in terminal:
continue
if sha and _rec_sha(rec) != sha:
continue
out.append(rec)
return out
def find_op_type_for_sha(sha: str) -> str | None:
"""The op_type a prior canonical wait already recorded for this SHA
(objective text embeds the type: 'event-driven {op_type} qualification')."""
for rec in _load_wait_records():
if _rec_sha(rec) != sha:
continue
obj = str(rec.get("continuation", {}).get("objective", ""))
m = re.search(r"event-driven (\w+) qualification", obj)
if m and m.group(1) in VALID_OP_TYPES:
return m.group(1)
return None
# ── deterministic op_type derivation from repo/Gitea truth ─────────────────
def _git(*args: str) -> str:
try:
r = subprocess.run(["git", "-C", str(REPO), *args],
capture_output=True, text=True, timeout=GIT_TIMEOUT)
return r.stdout.strip()
except (OSError, subprocess.TimeoutExpired):
return ""
def sha_branch(sha: str) -> str:
"""Branch(es) containing this commit in the local repo."""
return _git("branch", "--all", "--contains", sha)
def sha_on_master(sha: str) -> bool:
out = _git("branch", "--contains", sha)
return any(l.strip().endswith("master") or l.strip() == "master"
for l in out.splitlines())
def open_pr_for_sha(sha: str) -> int | None:
"""Is there an OPEN PR whose head SHA is this one? (Gitea API, read-only.)"""
try:
sys.path.insert(0, str(_SCRIPT_DIR))
import gitea_auth # noqa: E402
st, prs = gitea_auth.api("/pulls?state=open&limit=50")
if st != 200:
return None
for p in prs:
hs = str(p.get("head") or {}).get("sha") if isinstance(
p.get("head"), dict) else None
if hs and str(hs).startswith(sha[:12]):
return int(p.get("number"))
except Exception: # noqa: BLE001 — resolver must never crash the tool
return None
return None
def derive_op_type(sha: str) -> tuple[str, str]:
"""Deterministically derive the canonical op_type for a SHA.
Returns (op_type, evidence). Order (A-patch §2 amendment):
1. POST-PATCH normalized record (ext.intent_normalized=true) — true
stored operation truth, wins outright;
2. open PR for the sha -> pull_request_checks;
3. sha ancestor of master -> master_ci;
4. sha on a feature branch -> branch_ci;
5. legacy (pre-patch) records only CONFIRM, never override — they
were created from model-supplied defaults and cannot be trusted.
Never trusts model input.
"""
for rec in _load_wait_records():
if _rec_sha(rec) != sha:
continue
if (rec.get("ext") or {}).get("intent_normalized"):
obj = str(rec.get("continuation", {}).get("objective", ""))
m = re.search(r"event-driven (\w+) qualification", obj)
if m and m.group(1) in VALID_OP_TYPES:
return m.group(1), "normalized_record:" + m.group(1)
pr = open_pr_for_sha(sha)
if pr:
return "pull_request_checks", f"open_pr:#{pr}"
if sha_on_master(sha):
return "master_ci", "commit_ancestor_of_master"
return "branch_ci", "commit_on_feature_branch"
# ── intent normalization ────────────────────────────────────────────────────
def normalize_action(action: dict, *,
model_deadline: Any = None,
user_deadline: Any = None,
contract_deadline: Any = None,
task_id: str = "") -> dict:
"""Normalize a model continuation action into the canonical continuation.
action: {kind: await_operation|sleep_poll|check_status|unknown,
sha?: str, operation_id?: str, op_type?: str, raw?: str}
task_id: REAL_ASYNC_BRIDGE §9 — when the caller knows its task
binding, pending-operation resolution is scoped to operations
registered with that task_id; stale global operations are
never consulted when a binding exists.
Returns:
{normalized: bool, action: "ContinuePendingOperation", operation_id,
op_type, authority, deadline_secs, deadline_provenance,
model_deadline_ignored, model_op_type_ignored, evidence}
or {normalized: False, reason: ...} / {normalized: "AMBIGUOUS", candidates}
"""
sha = str(action.get("sha") or "")
op_id = str(action.get("operation_id") or "")
model_op_type = action.get("op_type")
model_deadline_ignored = model_deadline is not None
model_op_type_ignored = model_op_type is not None
# 0. task binding scope (§9): task-bound operations beat global state.
# Stored operation wins over ANY model hint (sha/op_type/deadline).
if task_id and not op_id:
bound = [r for r in pending_operations()
if str(r.get("ext", {}).get("task_id", "")) == task_id]
if len(bound) == 1:
rec = bound[0]
norm = _canonical_from_record(rec, model_deadline_ignored,
model_op_type_ignored,
"task_bound",
user_deadline, contract_deadline)
norm["sha_for_wait"] = _rec_sha(rec)
norm["task_id"] = task_id
return norm
if len(bound) > 1:
return {"normalized": "AMBIGUOUS",
"candidates": [_rec_operation_id(c) for c in bound],
"reason": f"multiple pending operations bound to task {task_id}"}
return {"normalized": False,
"reason": f"no pending operation bound to task {task_id}"}
# 1. explicit operation_id wins
if op_id:
from rune import event_wait as ew # noqa: E402
rec = ew.operation_wait(op_id)
if rec is None:
# REAL_ASYNC_BRIDGE §10: a FRESH operation has no wait record
# yet — registration happens at the park step (ci_event_wait).
# For an explicit, well-formed, EXISTING commit id, return the
# provisional canonical continuation (policy deadline, own-runs
# authority) so the model-facing tool can park on first call.
# A typo'd/ghost sha is refused here (A-patch lesson): the
# commit must exist in the repository.
import re as _re
m = _re.fullmatch(r"ci-([0-9a-f]{40})", op_id)
if m:
sha40 = m.group(1)
probe = subprocess.run(
["git", "-C", str(REPO), "cat-file", "-e",
f"{sha40}^{{commit}}"],
capture_output=True, timeout=15)
if probe.returncode == 0:
return {
"normalized": True,
"action": "ContinuePendingOperation",
"operation_id": op_id,
"sha_for_wait": sha40,
"op_type": "branch_ci",
"authority": "ci_runs",
"deadline_secs": POLICY_DEADLINES["branch_ci"],
"deadline_provenance": "GOVERNED_OPERATION_POLICY",
"model_deadline_ignored": model_deadline_ignored,
"model_op_type_ignored": model_op_type_ignored,
"evidence": "explicit_id_fresh_registration",
}
return {"normalized": False,
"reason": f"operation_id {op_id} has no wait record"}
return _canonical_from_record(rec, model_deadline_ignored,
model_op_type_ignored, "explicit_id",
user_deadline, contract_deadline)
# 2. resolve by sha / active operation.
# H-PATCH H2/H3: the model sha is a HINT. If it doesn't resolve but
# unambiguous pending operations exist, the STORED target wins — a
# mistyped model sha must never create a ghost operation (the A-patch
# live failure: a typo'd sha derived branch_ci for a nonexistent commit
# and parked a wait on a ghost).
# 2. no explicit operation_id: resolve by sha / pending operations.
# H-PATCH: when the model provides NO sha (operation-id-first mode),
# resolve from unambiguous pending operations — the model should not
# need to reconstruct a 40-char sha to express "wait for it".
if not sha:
all_pending = pending_operations()
if len(all_pending) == 1:
rec = all_pending[0]
norm = _canonical_from_record(rec, model_deadline_ignored,
model_op_type_ignored,
"auto_resolved_no_sha",
user_deadline, contract_deadline)
norm["sha_for_wait"] = _rec_sha(rec)
return norm
if len(all_pending) > 1:
return {"normalized": "AMBIGUOUS",
"candidates": [_rec_operation_id(c) for c in all_pending],
"reason": "multiple pending operations, no sha hint"}
return {"normalized": False,
"reason": "no pending operations and no sha provided"}
candidates = pending_operations(sha=sha)
if len(candidates) > 1 and not sha:
return {"normalized": "AMBIGUOUS",
"candidates": [_rec_operation_id(c) for c in candidates],
"reason": "multiple pending operations, no target"}
if len(candidates) == 1:
rec = candidates[0]
stored_sha = _rec_sha(rec)
norm = _canonical_from_record(rec, model_deadline_ignored,
model_op_type_ignored, "pending_match",
user_deadline, contract_deadline)
if sha and stored_sha and sha != stored_sha:
norm["model_sha_mismatch"] = {
"model_sha": sha, "stored_sha": stored_sha,
"resolution": "stored_target_used"}
return norm
# model sha unresolved: any unambiguous pending op at all? then the
# model probably mistyped the sha — use the stored target, log it.
all_pending = pending_operations()
if len(all_pending) == 1 and sha:
rec = all_pending[0]
stored_sha = _rec_sha(rec)
norm = _canonical_from_record(rec, model_deadline_ignored,
model_op_type_ignored,
"unresolved_sha_single_pending",
user_deadline, contract_deadline)
norm["model_sha_mismatch"] = {
"model_sha": sha, "stored_sha": stored_sha,
"resolution": "stored_target_used"}
return norm
# 3. no stored record: derive truth deterministically
if not sha:
return {"normalized": False,
"reason": "no pending operation and no sha to resolve"}
op_type, evidence = derive_op_type(sha)
return {
"normalized": True,
"action": "ContinuePendingOperation",
"operation_id": f"ci-{sha}",
"op_type": op_type,
"authority": "ci_runs" if op_type in ("branch_ci",
"pull_request_checks")
else "deploy_qualify",
"deadline_secs": POLICY_DEADLINES[op_type],
"deadline_provenance": "GOVERNED_OPERATION_POLICY",
"model_deadline_ignored": model_deadline_ignored,
"model_op_type_ignored": model_op_type_ignored,
"evidence": evidence,
}
def _canonical_from_record(rec: dict, model_deadline_ignored: bool,
model_op_type_ignored: bool, how: str,
user_deadline: Any, contract_deadline: Any) -> dict:
op_id = _rec_operation_id(rec)
obj = str(rec.get("continuation", {}).get("objective", ""))
m = re.search(r"event-driven (\w+) qualification", obj)
recorded = m.group(1) if (m and m.group(1) in VALID_OP_TYPES) else None
rec_sha = _rec_sha(rec)
if (rec.get("ext") or {}).get("intent_normalized") or how == "explicit_id":
# normalized record or explicit id: stored truth wins
op_type = recorded or "master_ci"
else:
# legacy record: git/Gitea derivation outranks the model-era default
op_type, _ev = derive_op_type(rec_sha) if rec_sha else \
(recorded or "master_ci", "no_sha")
deadline, prov = classify_timing_provenance(
user_deadline=user_deadline, contract_deadline=contract_deadline)
if prov == "MODEL_INVENTED" or not deadline:
deadline = POLICY_DEADLINES.get(op_type, 1800)
prov = "GOVERNED_OPERATION_POLICY"
return {
"normalized": True,
"action": "ContinuePendingOperation",
"operation_id": op_id,
"op_type": op_type,
"authority": "ci_runs" if op_type in ("branch_ci",
"pull_request_checks")
else "deploy_qualify",
"deadline_secs": deadline,
"deadline_provenance": prov,
"model_deadline_ignored": model_deadline_ignored,
"model_op_type_ignored": model_op_type_ignored,
"matched_by": how,
}
def normalize_bash(command: str, *, sha_hint: str = "") -> dict | None:
"""Recognize sleep/check/poll intent aimed at a known pending operation.
Returns a normalization record if the command's semantic intent is
continuation of a known external operation; None if the command is
legitimate local timing (short sync, user delay, backoff) with no
pending operation to continue. NEVER returns an executed sleep.
"""
if not command:
return None
cmd = command.strip()
# long sleep + (status-check language or an explicit sha hint)
sleep_m = re.search(r"\bsleep\s+(\d{2,5})\b", cmd)
if not sleep_m:
return None
secs = int(sleep_m.group(1))
check_intent = bool(re.search(
r"\b(ci|deploy|release|status|check|poll|pipeline|run)\b", cmd,
re.IGNORECASE))
sha = sha_hint or ""
m = re.search(r"\b([0-9a-f]{40}|[0-9a-f]{12})\b", cmd)
if m:
sha = m.group(1)
if not check_intent and not sha:
return None # plain sleep, no continuation intent
cands = pending_operations(sha=sha) if sha else pending_operations()
if not cands and not sha:
return None
if len(cands) > 1:
return {"normalized": "AMBIGUOUS",
"candidates": [_rec_operation_id(c) for c in cands],
"raw_command_kind": "sleep_poll"}
rec = cands[0] if cands else None
if rec is not None:
norm = _canonical_from_record(rec, True, False, "sleep_poll_intent",
None, None)
norm["sha_for_wait"] = _rec_sha(rec)
elif sha:
op_type, evidence = derive_op_type(sha)
norm = {
"normalized": True, "action": "ContinuePendingOperation",
"operation_id": f"ci-{sha}", "op_type": op_type,
"authority": "ci_runs" if op_type in ("branch_ci",
"pull_request_checks")
else "deploy_qualify",
"deadline_secs": POLICY_DEADLINES[op_type],
"deadline_provenance": "GOVERNED_OPERATION_POLICY",
"model_deadline_ignored": True, "model_op_type_ignored": False,
"evidence": evidence, "sha_for_wait": sha,
}
else:
return None
norm["raw_command_kind"] = "sleep_poll"
norm["raw_sleep_seconds_suppressed"] = secs
return norm
def emit_normalized_record(record: dict) -> None:
"""Compact structured observability — one JSONL line per normalization.
No payloads, no secrets."""
try:
log = _SCRIPT_DIR.parent / "store" / "await_intent_log.jsonl"
log.parent.mkdir(parents=True, exist_ok=True)
slim = {k: record.get(k) for k in (
"normalized", "action", "operation_id", "op_type", "authority",
"deadline_secs", "deadline_provenance", "model_deadline_ignored",
"model_op_type_ignored", "matched_by", "evidence",
"raw_command_kind", "raw_sleep_seconds_suppressed", "reason",
"candidates") if k in record}
slim["at"] = __import__("time").strftime(
"%Y-%m-%dT%H:%M:%SZ", __import__("time").gmtime())
with open(log, "a") as f:
f.write(json.dumps(slim, sort_keys=True) + "\n")
except OSError:
pass
+74
View File
@@ -0,0 +1,74 @@
#!/usr/bin/env python3
"""await_intent_cli.py — CLI surface for the intent normalization layer.
Used by the await-operation harness tool so the MODEL's scheduling hints
never reach the wait engine. Prints one JSON line.
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import await_intent # noqa: E402
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__)
sub = ap.add_subparsers(dest="cmd", required=True)
n = sub.add_parser("normalize", help="normalize a continuation intent")
n.add_argument("--sha", default="")
n.add_argument("--operation-id", default="")
n.add_argument("--task-id", default="",
help="§9 task binding: scope pending resolution to "
"operations registered for this task")
n.add_argument("--model-op-type", default=None)
n.add_argument("--model-deadline", default=None)
n.add_argument("--user-deadline", default=None,
help="USER_EXPLICIT deadline (authoritative)")
n.add_argument("--contract-deadline", default=None,
help="EXTERNAL_CONTRACT deadline (authoritative)")
rb = sub.add_parser("route-bash",
help="route a Bash command expressing wait intent")
rb.add_argument("--command", required=True)
rb.add_argument("--sha-hint", default="")
args = ap.parse_args()
if args.cmd == "route-bash":
record = await_intent.normalize_bash(args.command,
sha_hint=args.sha_hint)
if record is None:
print(json.dumps({"normalized": False,
"reason": "no external continuation intent"}))
return 0
if record.get("normalized") is True:
# the compiled wait needs the SHA: derive from operation_id
oid = str(record.get("operation_id", ""))
if oid.startswith("ci-"):
record["sha_for_wait"] = oid[3:]
await_intent.emit_normalized_record(record)
print(json.dumps(record, sort_keys=True))
return 0
action = {"kind": "await_operation", "sha": args.sha,
"operation_id": args.operation_id,
"op_type": args.model_op_type}
model_deadline = (int(args.model_deadline)
if args.model_deadline else None)
user_deadline = (int(args.user_deadline)
if args.user_deadline else None)
contract_deadline = (int(args.contract_deadline)
if args.contract_deadline else None)
record = await_intent.normalize_action(
action, model_deadline=model_deadline,
user_deadline=user_deadline, contract_deadline=contract_deadline,
task_id=args.task_id)
await_intent.emit_normalized_record(record)
print(json.dumps(record, sort_keys=True))
return 0
if __name__ == "__main__":
sys.exit(main())
+417 -91
View File
@@ -47,27 +47,232 @@ import time
from pathlib import Path
_SCRIPT_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(_SCRIPT_DIR))
sys.path.insert(0, str(_SCRIPT_DIR.parent / "src"))
sys.path.insert(0, str(_SCRIPT_DIR))
from rune import event_wait as ew # noqa: E402
import completion_file as cf # noqa: E402
REPO_ROOT = _SCRIPT_DIR.parent
QUALIFY_SCRIPT = _SCRIPT_DIR / "deploy_qualify.py"
def authority_verdict() -> str:
def authority_verdict(sha: str = "") -> str:
"""ONE-SHOT canonical Gitea verdict via deploy_qualify.py — the only
completion truth. Never parses the event payload."""
completion truth. Never parses the event payload.
H-PATCH H4: the target sha is passed EXPLICITLY (as the subprocess's
--sha argument) — ambient DEPLOY_SHA can no longer change which
operation is verified. The env var remains only as a CLI backward
compat for deploy_qualify.py invoked standalone."""
cmd = [sys.executable, str(QUALIFY_SCRIPT)]
if sha:
cmd += ["--sha", sha]
proc = subprocess.run(
[sys.executable, str(QUALIFY_SCRIPT)],
cmd,
capture_output=True, text=True, timeout=120,
env=dict(os.environ, PYTHONPATH=""),
env={k: v for k, v in os.environ.items() if k != "DEPLOY_SHA"}
if sha else dict(os.environ, PYTHONPATH=""),
)
out = (proc.stdout or "").strip().splitlines()
return (out[-1] if out else "pending").strip()
def authority_after_wake(max_tries: int = 3, gap_secs: int = 4) -> str:
# ── T21.5 §4: operation-specific completion authority ──────────────────────
#
# T21.4 root cause: the wait loop had ONE hard-coded authority
# (deploy qualification = master-ref CI) while operations waited on
# branch CI, PR checks, or merges. Wrong authority => nonterminal
# returns => the MODEL had to re-arm (the generator of the sleeps).
# The wait verifier MUST match the operation being waited for.
OPERATION_SPECS = {
# op_type: {
# workflow_path: Actions run path filter (None = any),
# head_branches: head-branch filter (None = any),
# authority: which canonical verifier decides completion,
# event_types: expected wake event types,
# terminal: terminal verdicts, failure: failure verdicts,
# }
"branch_ci": {
"workflow_path": "ci.yml",
"head_branches": None,
"authority": "ci_runs", # any completed run for this SHA
"event_types": ("ci_completed",),
"terminal": ("success",),
"failure": ("failure", "cancelled"),
},
"pull_request_checks": {
"workflow_path": None, # all PR-triggered runs for SHA
"head_branches": None,
"authority": "ci_runs",
"event_types": ("ci_completed",),
"terminal": ("success",),
"failure": ("failure", "cancelled"),
},
"master_ci": {
"workflow_path": "ci.yml",
"head_branches": ("master",),
"authority": "deploy_qualify", # full two-leg qualification
"event_types": ("ci_completed",),
"terminal": ("success",),
"failure": ("failure", "cancelled"),
},
"deploy": {
"workflow_path": "deploy.yml",
"head_branches": ("master",),
"authority": "deploy_qualify",
"event_types": ("ci_completed", "deploy_completed"),
"terminal": ("success",),
"failure": ("failure", "cancelled"),
},
"release": {
"workflow_path": None,
"head_branches": ("master",),
"authority": "deploy_qualify",
"event_types": ("ci_completed", "release_completed"),
"terminal": ("success",),
"failure": ("failure", "cancelled"),
},
}
DEFAULT_OP_TYPE = "master_ci"
def normalize_verdict(verdict: str) -> str:
"""T21.5 §18 audit fix: pending-class verdicts are ALL nonterminal.
deploy_qualify emits "pending (probe failed)" for a transient probe
failure — that is pending, never a terminal answer. The wait loop
must not return control to the model over a string variant."""
v = (verdict or "").strip().lower()
if v.startswith("pending"):
return "pending"
return (verdict or "").strip()
def op_spec(op_type: str) -> dict:
spec = OPERATION_SPECS.get(op_type)
if spec is None:
raise ValueError(
f"unknown op_type {op_type!r} — must be one of "
f"{sorted(OPERATION_SPECS)} (the wait verifier must match "
"the operation being waited for)")
return spec
def op_authority_verdict(op_type: str, sha: str) -> str:
"""Canonical completion verdict FOR THIS OPERATION TYPE.
ci_runs — the op's own Actions runs for this exact SHA: every
required workflow run completed; terminal iff every
run of the op's workflow set concluded success
(branch/PR ops are their own truth; deploy
qualification is NOT their authority).
deploy_qualify — the two-leg deploy qualification (master CI +
required-checks) via deploy_qualify.py.
"""
spec = op_spec(op_type)
authority = spec["authority"]
if authority == "deploy_qualify":
v = normalize_verdict(authority_verdict(sha))
if v != "pending":
return v
# E-PATCH resilience: Gitea's combined commit-status endpoint can
# 404 for the newest commit (observed live on auto-version bump
# commits — the status object materializes late/never). The runs
# ARE the same evidence the two legs read; fall back to the
# operation-scoped runs verdict so authority stays resolvable.
if _status_endpoint_unreadable(sha):
return _ci_runs_verdict(spec, sha)
return v
if authority == "ci_runs":
return _ci_runs_verdict(spec, sha)
raise ValueError(f"unknown authority {authority!r} for {op_type!r}")
def _status_endpoint_unreadable(sha: str) -> bool:
"""True when the combined commit-status endpoint 404s for the SHA
(index lag / never-materialized status object) — the documented
fallback trigger, verified against the API once per call."""
try:
sys.path.insert(0, str(_SCRIPT_DIR))
import gitea_auth # noqa: E402
st, _body = gitea_auth.api(f"/commits/{sha}/status")
return st == 404
except Exception: # noqa: BLE001
return False
def _ci_runs_verdict(spec: dict, sha: str) -> str:
"""Operation-scoped Actions-run verdict (import the API client from
deploy_qualify to keep ONE Gitea client)."""
sys.path.insert(0, str(_SCRIPT_DIR))
import deploy_qualify as dq # noqa: E402
import gitea_auth # noqa: E402
server = os.environ.get("GITEA_SERVER",
getattr(gitea_auth, "DEFAULT_SERVER", "")).rstrip("/")
repo = os.environ.get("GITEA_REPO",
getattr(gitea_auth, "DEFAULT_REPO", ""))
sha_env = sha # H-PATCH: explicit parameter is the only authority
token = gitea_auth.get_token() if gitea_auth.has_token() else ""
if not (server and repo and sha_env and token):
return "pending" # cannot verify -> not verified (fail closed)
runs = dq._fetch_runs(server, repo, sha_env, token)
if runs is None:
return "pending" # probe failure -> keep waiting
wfp = spec["workflow_path"]
if wfp is not None:
runs = [r for r in runs
if str(r.get("path", "")).startswith(wfp + "@")]
branches = spec["head_branches"]
if branches is not None:
runs = [r for r in runs
if str(r.get("head_branch", "")) in branches]
if not runs:
return "pending" # no runs visible yet -> not terminal
for r in runs:
if r.get("status") != "completed":
return "pending" # still running
if r.get("conclusion") not in ("success", "skipped"):
return "failure" # any non-success conclusion
return "success"
def op_ci_alive(op_type: str, sha: str) -> bool:
"""Liveness probe FOR THIS OPERATION TYPE: any non-terminal run in
the op's run set (failure DETECTOR, never the scheduler)."""
sys.path.insert(0, str(_SCRIPT_DIR))
import deploy_qualify as dq # noqa: E402
import gitea_auth # noqa: E402
spec = op_spec(op_type)
server = os.environ.get("GITEA_SERVER",
getattr(gitea_auth, "DEFAULT_SERVER", "")).rstrip("/")
repo = os.environ.get("GITEA_REPO",
getattr(gitea_auth, "DEFAULT_REPO", ""))
sha_env = sha # H-PATCH: explicit parameter is the only authority
token = gitea_auth.get_token() if gitea_auth.has_token() else ""
if not (server and repo and sha_env and token):
return False
runs = dq._fetch_runs(server, repo, sha_env, token)
if not runs:
return False
wfp = spec["workflow_path"]
if wfp is not None:
runs = [r for r in runs
if str(r.get("path", "")).startswith(wfp + "@")]
branches = spec["head_branches"]
if branches is not None:
runs = [r for r in runs
if str(r.get("head_branch", "")) in branches]
terminal = {"success", "failure", "cancelled", "skipped"}
return any(str(r.get("status", "")).lower() not in terminal
for r in runs)
def authority_after_wake(op_type: str = DEFAULT_OP_TYPE, sha: str = "",
max_tries: int = 3, gap_secs: int = 4) -> str:
"""(sha is EXPLICIT — ambient env never selects the target.)"""
"""Post-wake authority read. The daemon's own wake sweep uses a
bounded event-triggered retry (4s ×3) for the job-finished-microseconds-
ago ack window; a verdict read here can hit the same window. This is a
@@ -75,7 +280,7 @@ def authority_after_wake(max_tries: int = 3, gap_secs: int = 4) -> str:
event wait / watchdog."""
verdict = "pending"
for attempt in range(max_tries):
verdict = authority_verdict()
verdict = op_authority_verdict(op_type, sha)
if verdict != "pending":
return verdict
if attempt < max_tries - 1:
@@ -84,62 +289,98 @@ def authority_after_wake(max_tries: int = 3, gap_secs: int = 4) -> str:
def ci_alive() -> bool:
"""Liveness probe for THIS adapter: any non-terminal Actions run for
the SHA in canonical Gitea (one-shot on watchdog expiry — not a poll).
Import the probe from deploy_qualify to keep ONE API client."""
sys.path.insert(0, str(_SCRIPT_DIR))
import deploy_qualify as dq # noqa: E402 (spec-loaded scripts pattern)
import gitea_auth
server = os.environ.get("GITEA_SERVER", "").rstrip("/")
repo = os.environ.get("GITEA_REPO", "")
sha = os.environ.get("DEPLOY_SHA", "")
if not (server and repo and sha):
return False
token = gitea_auth.get_token() if gitea_auth.has_token() else ""
if not token:
return False
runs = dq._fetch_runs(server, repo, sha, token)
if not runs:
return False
terminal = {"success", "failure", "cancelled", "skipped"}
return any(str(r.get("status", "")).lower() not in terminal
for r in runs)
"""LEGACY liveness probe (master-ref deploy runs) — kept for callers
predating op specs; run() uses op_ci_alive(op_type, sha)."""
return op_ci_alive(DEFAULT_OP_TYPE, os.environ.get("DEPLOY_SHA", ""))
def _set_cognition_state(op: str, state: str) -> None:
"""E-PATCH §12: stamp cognitive quiescence on the durable record so a
fresh process knows cognition is parked (WAITING_EXTERNAL) vs resumable
(RESUMABLE). Best-effort: observability never breaks the wait."""
try:
rec = ew.operation_wait(op)
if rec is None:
return
wait_file = ew.wr_root() / "waits" / f"{rec.get('wait_id')}.json"
if not wait_file.is_file():
return
data = json.loads(wait_file.read_text())
data.setdefault("ext", {})["continuation_state"] = state
wait_file.write_text(json.dumps(data, indent=1, sort_keys=True))
except (OSError, ValueError):
pass
def run(*, sha: str, deadline_secs: int,
watchdog_secs: int = 300,
op_type: str = DEFAULT_OP_TYPE,
task_id: str = "",
daemon_url: str = ew.DEFAULT_DAEMON_URL) -> int:
"""T21.5 §3: ONE LOGICAL AWAIT.
One logical external wait is one invocation of this function. All
nonterminal conditions (event-seen-authority-pending, watchdog expiry
with live runs, bounded authority-retry exhausted) re-arm INTERNALLY —
control returns to the caller ONLY on a terminal verdict or on real
deadline expiry. Watchdogs are failure detectors inside this
abstraction, never the scheduler and never completion truth.
"""
spec = op_spec(op_type) # unknown op_type refused upfront
op = f"ci-{sha}"
# A-PATCH: the authority subprocess reads DEPLOY_SHA from the env;
# python-level run() callers (tool normalization flow) must export it.
if sha and not os.environ.get("DEPLOY_SHA"):
os.environ["DEPLOY_SHA"] = sha
# H-PATCH: resolve short SHAs to full 40-char — _fetch_runs and
# deploy_qualify match on the FULL sha; a 12-char prefix would
# silently fail equality and park the waiter until deadline
# (the 962e351 root cause).
if sha and len(sha) < 40:
try:
full = subprocess.run(
["git", "-C", str(_SCRIPT_DIR.parent), "rev-parse", sha],
capture_output=True, text=True, timeout=10)
if full.returncode == 0:
sha = full.stdout.strip()
os.environ["DEPLOY_SHA"] = sha
except (OSError, subprocess.TimeoutExpired):
pass
t0 = time.time()
# §16Y #10 race fix: the FIFO reader exists BEFORE registration, so a
# wake arriving in the registration window can always be delivered.
fifo_fd = ew.open_fifo_reader(op)
# REAL_ASYNC_BRIDGE: FIFO is replaced by completion-file inotify.
# The completion file write is the wake signal; inotify is the
# kernel-level zero-latency, zero-CPU wake primitive.
# Event replay (T16 §16C live finding): on a serialized runner the
# completion event usually arrives BEFORE the deploy job registers.
# completion event usually arrives BEFORE the waiter registers.
# A matching durable event means the wake already happened — skip
# the rendezvous entirely and go straight to the authority read.
prior = ew.find_matching_event("gitea", "ci_completed", op)
if prior is not None and prior.get("exit_code") is not None:
rec = ew.register_process_wait(
op, project="rune",
objective=f"event-driven CI qualification for {sha}",
objective=f"event-driven {op_type} qualification for {sha}",
watched_for="Gitea CI completion event (replayed from the "
"durable event log)",
provider="gitea", event_type="ci_completed", sha=sha,
watchdog_secs=min(watchdog_secs, deadline_secs))
watchdog_secs=min(watchdog_secs, deadline_secs),
continuation_state="RESUMABLE", # event already arrived
op_type=op_type,
authority=spec["authority"])
registered_at = rec["created_at"]
event_at = time.strftime(
"%Y-%m-%dT%H:%M:%SZ", time.gmtime())
resumed_at = event_at
# the event is a WAKE, never truth — canonical authority decides
verdict = authority_after_wake()
if verdict == "pending" and not ci_alive():
verdict = authority_verdict()
verdict = normalize_verdict(authority_after_wake(op_type, sha))
if verdict == "pending" and not op_ci_alive(op_type, sha):
verdict = normalize_verdict(op_authority_verdict(op_type, sha))
mode = ("event_replayed" if verdict != "pending"
else "authority_unresolved")
verdict_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
out = {
"sha": sha,
"op_type": op_type,
"authority": spec["authority"],
"registered_at": registered_at,
"event_received_at": event_at,
"controller_resumed_at": resumed_at,
@@ -154,108 +395,193 @@ def run(*, sha: str, deadline_secs: int,
return 0 if verdict == "success" else 1
rec = ew.register_process_wait(
op, project="rune",
objective=f"event-driven CI qualification for {sha}",
objective=f"event-driven {op_type} qualification for {sha}",
watched_for="Gitea CI completion event (wake; verdict re-read "
"from canonical Gitea)",
f"from canonical {spec['authority']} authority)",
provider="gitea", event_type="ci_completed", sha=sha,
watchdog_secs=min(watchdog_secs, deadline_secs))
watchdog_secs=min(watchdog_secs, deadline_secs),
continuation_state="WAITING_EXTERNAL",
op_type=op_type,
authority=spec["authority"],
task_id=task_id, # §9: bind the operation to the calling task
resume_mode="self") # §3: waiter owns its continuation via
# completion-file/inotify; the daemon has NO
# session-delivery role for this wait
registered_at = rec["created_at"]
_set_cognition_state(op, "WAITING_EXTERNAL") # cognition parked
deadline = time.monotonic() + deadline_secs
verdict = "pending"
mode = "event"
event_at = ""
resumed_at = ""
rearm_reason = ""
try:
while time.monotonic() < deadline:
rearm_count = 0
while time.monotonic() < deadline:
remaining = max(1, int(deadline - time.monotonic()))
result = ew.await_resume_event(
op, watchdog_secs=min(remaining, watchdog_secs), pgid=None,
fifo_fd=fifo_fd)
if result["kind"] == "event":
# REAL_ASYNC_BRIDGE: block on inotify for the completion file.
# Zero CPU, zero arbitrary timer. The inotify timeout is a
# FAILURE DETECTOR — if no event arrives within the watchdog
# window, we check the authority directly (lost-event recovery).
# The deadline is containment only.
watch_timeout = min(remaining, watchdog_secs)
result = cf.wait_for_completion(
op, timeout_secs=watch_timeout)
# REAL_ASYNC_BRIDGE: check durable event log AND wait record
# status on EVERY cycle — the daemon may have matched and
# transitioned the record via /process-event.
wr = ew.operation_wait(op)
if wr and wr.get("status") in ("COMPLETED", "FAILED",
"READY_TO_RESUME"):
event_at = event_at or time.strftime(
"%Y-%m-%dT%H:%M:%SZ", time.gmtime())
resumed_at = time.strftime("%Y-%m-%dT%H:%M:%SZ",
time.gmtime())
# WAKE received — canonical authority decides, never the
# event payload
verdict = authority_after_wake()
verdict = normalize_verdict(
authority_after_wake(op_type, sha))
if verdict != "pending":
mode = "event"
break
rearm_reason = "event seen; authority pending"
if not ci_alive():
# CI terminal in Gitea but aggregator lagging: one
# final truthful read, then stop
verdict = authority_verdict()
mode = ("event" if verdict != "pending"
else "authority_unresolved")
prior = ew.find_matching_event("gitea", "ci_completed", op)
if prior is not None and prior.get("exit_code") is not None:
event_at = event_at or time.strftime(
"%Y-%m-%dT%H:%M:%SZ", time.gmtime())
resumed_at = time.strftime("%Y-%m-%dT%H:%M:%SZ",
time.gmtime())
verdict = normalize_verdict(
authority_after_wake(op_type, sha))
if verdict != "pending":
mode = "event"
break
if result is not None:
# completion file exists: event arrived
event_at = event_at or time.strftime(
"%Y-%m-%dT%H:%M:%SZ", time.gmtime())
resumed_at = time.strftime("%Y-%m-%dT%H:%M:%SZ",
time.gmtime())
verdict = normalize_verdict(
authority_after_wake(op_type, sha))
if verdict != "pending":
mode = "event"
break
rearm_count += 1
rearm_reason = ("completion file seen; authority "
"pending — re-subscribing")
continue
if result["kind"] == "eof":
# writer gone without a token: the DURABLE record decides
rec = ew.operation_wait(op) or {}
if rec.get("status") in ("COMPLETED", "FAILED",
"READY_TO_RESUME", "RESUMING") or \
(rec.get("status") == "RESUME_UNVERIFIED"
and rec.get("process_event_evidence")):
event_at = event_at or time.strftime(
"%Y-%m-%dT%H:%M:%SZ", time.gmtime())
resumed_at = time.strftime("%Y-%m-%dT%H:%M:%SZ",
time.gmtime())
verdict = authority_after_wake()
if verdict != "pending":
mode = "event_recovered_from_durable_record"
break
rearm_reason = "eof without token; durable record pending"
continue
# watchdog expiry: adapter liveness (Gitea runs) decides
# re-arm vs truthful recovery — failure detector, never
# scheduler
if ci_alive():
rearm_reason = "watchdog expiry; CI runs non-terminal " \
"in Gitea — re-arm"
continue
verdict = authority_verdict()
if verdict == "pending":
mode = "watchdog_lost_event_no_verdict"
else:
# inotify timeout: no event within watchdog window
# FAILURE DETECTOR: check authority directly
verdict = normalize_verdict(
op_authority_verdict(op_type, sha))
if verdict != "pending":
mode = "watchdog_authority_recovered"
break
finally:
os.close(fifo_fd)
break
rearm_count += 1
rearm_reason = ("watchdog expiry; op pending (not started "
"or lagging) — re-subscribing inotify")
continue
# REAL_ASYNC_BRIDGE: no FIFO to close. The completion file is durable
# and survives restarts. Lease release is not needed (no lease system
# for file-based delivery).
if verdict == "pending":
if mode == "event":
mode = "deadline_pending"
verdict = authority_verdict() # final one-shot truth
verdict = normalize_verdict(op_authority_verdict(op_type, sha)) # final one-shot truth
_set_cognition_state(op, "RESUMABLE" if verdict != "pending"
else "RECOVERY_REQUIRED")
verdict_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
# §2 STATE OWNERSHIP: the WAITER owns the OPERATION domain. On a
# terminal authority verdict, stamp the durable record terminal with
# the evidence — the daemon's session-delivery machinery must never
# (and can no longer) overwrite this truth. Also updates the parked
# continuation_state so fresh-process recovery sees reality.
try:
rec2 = ew.operation_wait(op) or rec
if verdict != "pending":
rec2["status"] = "COMPLETED" if verdict == "success" else "FAILED"
rec2["verdict"] = verdict
rec2["resume_evidence"] = {
"kind": "authority_verified_terminal",
"verdict": verdict,
"mode": mode,
"authority": spec["authority"],
"sha": sha,
"op_type": op_type,
"verdict_at": verdict_at,
}
rec2["ext"] = {**rec2.get("ext", {}),
"continuation_state": ("RESUMABLE" if verdict != "pending"
else "RECOVERY_REQUIRED")}
ew._load_waitstore().save(rec2)
except Exception as _e: # noqa: BLE001 — record stamping must never corrupt the envelope (the envelope + authority remain the truth)
print(f"ci_event_wait: terminal record stamp failed: {_e}",
file=sys.stderr)
out = {
"sha": sha,
"op_type": op_type,
"authority": spec["authority"],
"registered_at": registered_at,
"event_received_at": event_at,
"controller_resumed_at": resumed_at,
"verdict_at": verdict_at,
"mode": mode,
"rearm_reason": rearm_reason,
"internal_rearm_count": rearm_count,
"verdict": verdict,
"wall_secs": round(time.time() - t0, 3),
}
print(json.dumps(out))
return 0 if verdict == "success" else 1
KNOWN_FLAGS = ("--sha", "--deadline", "--op-type", "--watchdog", "--task-id")
def main() -> int:
"""§5 CLI CONTRACT: no silent argument loss. Every flag is either
explicitly supported (and takes effect) or loudly rejected — an
acceptance envelope must reflect the configuration actually used."""
sha = os.environ.get("DEPLOY_SHA", "")
args = sys.argv[1:]
if "--sha" in args:
sha = args[args.index("--sha") + 1]
deadline = 900
if "--deadline" in args:
deadline = int(args[args.index("--deadline") + 1])
op_type = DEFAULT_OP_TYPE
watchdog = 300
task_id = ""
i = 0
while i < len(args):
a = args[i]
if a == "--sha":
sha = args[i + 1]; i += 2
elif a == "--deadline":
deadline = int(args[i + 1]); i += 2
elif a == "--op-type":
op_type = args[i + 1]; i += 2
elif a == "--watchdog":
watchdog = int(args[i + 1]); i += 2
elif a == "--task-id":
task_id = args[i + 1]; i += 2
elif a.startswith("--"):
print(json.dumps({"error": f"unknown flag {a!r}",
"known_flags": list(KNOWN_FLAGS),
"contract": "no silent argument loss"}))
return 2
else:
print(json.dumps({"error": f"unexpected positional {a!r}",
"known_flags": list(KNOWN_FLAGS)}))
return 2
if not sha:
print(json.dumps({"error": "no sha (DEPLOY_SHA or --sha required)"}))
return 1
return run(sha=sha, deadline_secs=deadline)
# A-PATCH fix: run()'s authority subprocess (deploy_qualify.py) reads
# DEPLOY_SHA from the environment — a CLI `--sha` consumer previously
# got a silent "pending" verdict forever because the var was never
# exported. Export it so the canonical authority can resolve.
os.environ["DEPLOY_SHA"] = sha
return run(sha=sha, deadline_secs=deadline, op_type=op_type,
watchdog_secs=watchdog, task_id=task_id)
if __name__ == "__main__":
+195
View File
@@ -0,0 +1,195 @@
"""completion_file.py — REAL_ASYNC_BRIDGE: durable completion-file delivery
with inotify wake. Replaces FIFO+lease for process-wait delivery.
DESIGN:
Producer (bridge/daemon/notify):
1. Validates operation identity
2. Atomically writes a completion file:
resumes/<operation_id>.done
containing the event/verdict JSON (write to tmp, rename for atomicity)
Waiter (ci_event_wait / any continuation consumer):
1. Checks if the completion file already exists (event-before-waiter)
2. If not: registers inotify watch on the resumes/ directory
3. Re-checks after watch registration (closes the registration race)
4. Blocks in kernel on inotify — zero CPU, zero timer
5. On file-created event: reads the file, verifies authority, returns
The event is a WAKE SIGNAL. Authority still decides terminal truth.
The completion file is durable — survives daemon and waiter restarts.
WHY NOT FIFO:
FIFO is one-shot (token consumed), requires lease management, and
fails under session-busy guards. A completion file is atomic, durable,
and works across restarts. inotify wakes the process with zero CPU
cost and zero latency between file creation and wake.
WHY NOT POLLING:
Polling introduces an arbitrary interval. inotify is a kernel callback
that fires exactly when the filesystem changes — the latency tracks
the real completion event, not a configured interval.
"""
from __future__ import annotations
import json
import os
import select
import struct
import time
from pathlib import Path
def _wr_bridge() -> Path:
# WR_ROOT = daemon/infra convention; RUNE_WR_ROOT = repo/scripts
# convention. Both honored (WR_ROOT wins) so the daemon, the waiter,
# and any isolated test root always resolve the SAME directory.
root = (os.environ.get("WR_ROOT")
or os.environ.get("RUNE_WR_ROOT")
or "/mnt/workdrive/wr-bridge")
return Path(root)
def _resumes_dir() -> Path:
return _wr_bridge() / "resumes"
# inotify constants (Linux)
IN_CREATE = 0x100
IN_CLOSE_WRITE = 0x8
IN_MOVED_TO = 0x80
def completion_path(operation_id: str) -> Path:
return _resumes_dir() / f"{operation_id}.done"
def write_completion(operation_id: str, event: dict) -> Path:
"""Atomically write a durable completion file. The rename(2) ensures
the waiter never sees a partial file."""
_resumes_dir().mkdir(parents=True, exist_ok=True)
target = completion_path(operation_id)
tmp = target.parent / f".{target.name}.tmp.{os.getpid()}"
tmp.write_text(json.dumps(event, sort_keys=True) + "\n")
os.rename(str(tmp), str(target)) # atomic on same filesystem
os.chmod(str(target), 0o666) # cross-user bridge
return target
def read_completion(operation_id: str) -> dict | None:
"""Read a durable completion file. Returns None if absent."""
p = completion_path(operation_id)
if not p.is_file():
return None
try:
return json.loads(p.read_text())
except (OSError, ValueError):
return None
def completion_exists(operation_id: str) -> bool:
return completion_path(operation_id).is_file()
def _inotify_init_and_add_watch(directory: str) -> int:
"""Create an inotify instance and add a watch on the directory.
Returns the inotify fd."""
libc = __import__("ctypes").CDLL(None, use_errno=True)
fd = libc.inotify_init()
if fd < 0:
raise OSError("inotify_init failed")
# IN_CREATE | IN_MOVED_TO: catch both direct creation and atomic rename
mask = IN_CREATE | IN_MOVED_TO
wd = libc.inotify_add_watch(fd, directory.encode(), mask)
if wd < 0:
os.close(fd)
raise OSError(f"inotify_add_watch failed for {directory}")
return fd
def _inotify_read(fd: int, timeout_secs: float | None = None) -> list[str]:
"""Read pending inotify events. Returns list of filenames that were
created/moved-to. Blocks until at least one event or timeout."""
poll = select.poll()
poll.register(fd, select.POLLIN)
events = []
buf = os.read(fd, 4096)
pos = 0
while pos < len(buf):
# struct: wd(i32), mask(u32), cookie(u32), name_len(u32)
wd, mask, cookie, name_len = struct.unpack_from("iIII", buf, pos)
pos += 16
name = buf[pos:pos + name_len].rstrip(b"\x00").decode("utf-8", "replace")
pos += name_len
if mask & (IN_CREATE | IN_MOVED_TO):
events.append(name)
return events
def wait_for_completion(operation_id: str, *,
timeout_secs: float | None = None,
poll_callback=None) -> dict | None:
"""Block until a completion file exists for this operation.
Uses inotify for zero-latency, zero-CPU wake. Falls back to
polling_callback (if provided) when inotify is unavailable.
The completion file is a WAKE SIGNAL. The caller must still verify
canonical authority after reading it.
Returns the event dict from the completion file, or None on timeout.
"""
target = completion_path(operation_id)
# 1. check-before-watch (event may have arrived before we started)
existing = read_completion(operation_id)
if existing is not None:
return existing
_resumes_dir().mkdir(parents=True, exist_ok=True)
try:
fd = _inotify_init_and_add_watch(str(_resumes_dir()))
except OSError:
# inotify unavailable: fall back to polling callback
if poll_callback:
return poll_callback(operation_id, timeout_secs)
return None
try:
# 2. re-check after watch registration (closes the race between
# the initial check and the watch being active)
existing = read_completion(operation_id)
if existing is not None:
return existing
# 3. block in kernel on inotify — zero CPU, exact latency
import select as _select
poll = _select.poll()
poll.register(fd, _select.POLLIN)
deadline = (time.monotonic() + timeout_secs) if timeout_secs else None
while True:
remaining = None
if deadline is not None:
remaining = max(0.1, deadline - time.monotonic())
if remaining <= 0.1:
return None # timeout
events = poll.poll(remaining * 1000 if remaining else None)
if not events:
continue # poll timeout, re-check
names = _inotify_read(fd)
if target.name in names:
# 4. file appeared — read it
existing = read_completion(operation_id)
if existing is not None:
return existing
# partial/corrupt: keep watching
finally:
os.close(fd)
return None
def clear_completion(operation_id: str) -> None:
"""Remove a consumed completion file (idempotent cleanup)."""
p = completion_path(operation_id)
try:
p.unlink()
except OSError:
pass
+34 -9
View File
@@ -106,12 +106,25 @@ def _fetch_status(server: str, repo: str, sha: str, token: str) -> list[dict] |
return None if data is None else data.get("statuses", [])
def _fetch_runs(server: str, repo: str, sha: str, token: str) -> list[dict] | None:
data = _get_json(f"{server}/api/v1/repos/{repo}/actions/runs?limit=50", token)
if data is None:
return None
runs = data.get("workflow_runs", data if isinstance(data, list) else [])
return [r for r in runs if str(r.get("head_sha", "")) == sha]
def _fetch_runs(server: str, repo: str, sha: str, token: str,
max_pages: int = 4) -> list[dict] | None:
"""H-PATCH: paginate the runs list. The repo's scheduled merge-robot
runs flood the first page (~50 slots exhaust within hours), pushing
older-but-relevant SHAs to later pages — the cause of the observed
authority flapping (terminal commit read as pending). Paginate until
the sha is found or max_pages exhausted."""
out: list[dict] = []
for page in range(1, max_pages + 1):
data = _get_json(
f"{server}/api/v1/repos/{repo}/actions/runs?limit=50&page={page}",
token)
if data is None:
return None if page == 1 else (out or None)
runs = data.get("workflow_runs", data if isinstance(data, list) else [])
out.extend(r for r in runs if str(r.get("head_sha", "")) == sha)
if len(runs) < 50:
break # exhausted
return out
def _fetch_jobs(server: str, repo: str, run_id, token: str) -> list[dict] | None:
@@ -128,9 +141,21 @@ def _fetch_jobs(server: str, repo: str, run_id, token: str) -> list[dict] | None
# ── CLI ───────────────────────────────────────────────────────────────────
def main() -> int:
server = os.environ.get("GITEA_SERVER", "").rstrip("/")
repo = os.environ.get("GITEA_REPO", "")
sha = os.environ.get("DEPLOY_SHA", "")
# T21.4 fix: fall back to the canonical defaults when the caller did
# not export GITEA_SERVER/GITEA_REPO (CI always exports them; local
# and first-class-tool consumers previously got a silent "pending"
# verdict — a wrong answer, not a fail-closed one).
# H-PATCH H4: the target sha arrives EXPLICITLY (--sha) from callers
# that hold structured operation state. DEPLOY_SHA env remains only
# for standalone/workflow backward compat — the canonical path never
# depends on ambient env for operation identity.
ap = __import__("argparse").ArgumentParser()
ap.add_argument("--sha", dest="sha_arg", default=None)
args, _unknown = ap.parse_known_args()
server = (os.environ.get("GITEA_SERVER", "")
or gitea_auth.DEFAULT_SERVER).rstrip("/")
repo = os.environ.get("GITEA_REPO", "") or gitea_auth.DEFAULT_REPO
sha = args.sha_arg or os.environ.get("DEPLOY_SHA", "")
token = gitea_auth.get_token() if gitea_auth.has_token() else ""
if not (server and repo and sha and token):
print("pending") # missing config → cannot verify → not qualified
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""install_mimo_hook.py — T21.2 §21.2D: canonical MiMo hook installer.
Canonical source: config/mimo-hooks/event-wait-guard.ts
Target: ~/.mimocode/hooks/event-wait-guard.ts
Verifies hash after install. Detects drift in both directions."""
import hashlib
import shutil
import sys
from pathlib import Path
CANONICAL = Path(__file__).resolve().parent.parent / "config" / "mimo-hooks" / "event-wait-guard.ts"
TARGET = Path.home() / ".mimocode" / "hooks" / "event-wait-guard.ts"
def sha256(p: Path) -> str:
return hashlib.sha256(p.read_bytes()).hexdigest()
def main() -> int:
if not CANONICAL.is_file():
print(f"canonical source missing: {CANONICAL}")
return 1
canon_hash = sha256(CANONICAL)
if TARGET.is_file():
target_hash = sha256(TARGET)
if target_hash == canon_hash:
print(f"in-sync: {target_hash[:12]}")
return 0
print(f"DRIFT: canonical={canon_hash[:12]} installed={target_hash[:12]}")
else:
print(f"hook missing at {TARGET}")
TARGET.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(CANONICAL, TARGET)
installed_hash = sha256(TARGET)
if installed_hash != canon_hash:
print(f"INSTALL FAILED: hash mismatch after copy")
return 1
print(f"installed: {installed_hash[:12]}")
return 0
if __name__ == "__main__":
sys.exit(main())
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env python3
"""install_mimo_tools.py — canonical MiMo tool installer (TTY-repair tranche).
Canonical sources: config/mimo-tools/{bash.ts,await-operation.ts}
Targets: ~/.mimocode/tools/{bash.ts,await-operation.ts}
Verifies hashes after install; detects drift in both directions
(the parity test reads these hashes).
"""
import hashlib
import shutil
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
TOOLS = ["bash.ts", "await-operation.ts"]
def sha256(p: Path) -> str:
return hashlib.sha256(p.read_bytes()).hexdigest()
def main() -> int:
rc = 0
for name in TOOLS:
canon = REPO / "config" / "mimo-tools" / name
target = Path.home() / ".mimocode" / "tools" / name
if not canon.is_file():
print(f"{name}: canonical source missing: {canon}")
rc = 1
continue
canon_hash = sha256(canon)
if target.is_file() and sha256(target) == canon_hash:
print(f"{name}: in-sync {canon_hash[:12]}")
continue
drift = f"DRIFT canonical={canon_hash[:12]}"
if target.is_file():
drift += f" installed={sha256(target)[:12]}"
else:
drift += " installed=MISSING"
print(f"{name}: {drift} — installing")
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(canon, target)
if sha256(target) != canon_hash:
print(f"{name}: INSTALL FAILED (hash mismatch after copy)")
rc = 1
else:
print(f"{name}: installed {canon_hash[:12]}")
return rc
if __name__ == "__main__":
sys.exit(main())
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env python3
"""install_wr_daemon.py — REAL_ASYNC_BRIDGE §6: daemon governance.
Canonical source lives IN THIS REPO at bridge/daemon/. The active install
is ~/.local/share/mimo-wait-resume/. This script is the ONLY supported
way to move code between them:
- copy canonical source → install dir (atomic, per file)
- verify sha256 convergence (fail loud on divergence)
- optionally restart the daemon via scripts/start_wr_daemon.py
Never hand-edit the installed copy: edit the repo source, install, restart.
"""
from __future__ import annotations
import argparse
import hashlib
import shutil
import subprocess
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
CANONICAL = REPO / "bridge" / "daemon"
INSTALL = Path.home() / ".local/share/mimo-wait-resume"
FILES = ("daemon.py", "waitstore.py", "verify.py", "completion_file.py")
def sha256(p: Path) -> str:
return hashlib.sha256(p.read_bytes()).hexdigest()
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--check", action="store_true", help="verify convergence only; no writes")
ap.add_argument("--restart", action="store_true", help="restart the daemon after install")
args = ap.parse_args()
if not CANONICAL.is_dir():
print(f"canonical source missing: {CANONICAL}")
return 1
INSTALL.mkdir(parents=True, exist_ok=True)
diverged = []
for name in FILES:
src, dst = CANONICAL / name, INSTALL / name
if args.check:
if not dst.is_file() or sha256(src) != sha256(dst):
diverged.append(name)
continue
if not dst.is_file() or sha256(src) != sha256(dst):
tmp = dst.with_suffix(f".tmp.{name}")
shutil.copy2(src, tmp)
tmp.replace(dst) # atomic
print(f"installed: {name} {sha256(src)[:12]}")
else:
print(f"unchanged: {name} {sha256(src)[:12]}")
if args.check:
if diverged:
print(f"DIVERGED: {', '.join(diverged)} — run install_wr_daemon.py")
return 1
print("CONVERGED: installed copy == canonical repo source")
return 0
# post-install convergence proof
bad = [n for n in FILES if sha256(CANONICAL / n) != sha256(INSTALL / n)]
if bad:
print(f"INSTALL FAILED convergence: {bad}")
return 1
print("CONVERGED: installed copy == canonical repo source")
if args.restart:
rc = subprocess.call([sys.executable, str(REPO / "scripts" / "start_wr_daemon.py")])
if rc != 0:
print("restart failed")
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
+43
View File
@@ -7,6 +7,13 @@ The event is a WAKE, never completion truth: the waiting controller
deploy_qualify.py after resuming. Best-effort by design — if the bridge
is unreachable the waiter's watchdog still recovers truthfully (that is
the §16D lost-event contract), so a notify failure must NEVER fail CI.
REAL_ASYNC_BRIDGE (R3): the completion event is ALSO written directly as
a durable completion file (resumes/<operation_id>.done). Delivery no
longer depends on the daemon being alive: the waiter blocks on inotify
for the file itself (kernel wake, zero latency, zero CPU), and the
durable file survives daemon AND waiter restarts (R9). The write is
best-effort on the same terms as the POST — never fails CI.
"""
from __future__ import annotations
@@ -35,6 +42,42 @@ def main() -> int:
"exit_code": 0 if result == "success" else 1,
"completed_at": time.time(),
}
# R8 lost-event failure-recovery: operation-scoped fault injection.
# A rule keyed by the EXACT operation_id suppresses BOTH normal
# delivery channels (durable completion file AND bridge POST), so the
# waiter's watchdog/failure-detector path is the only route to truth.
# Disabled by default (no rule file => zero behavior change) and
# observable: every decision is recorded in the R8 fault log.
try:
import r8_fault # noqa: E402 — sibling module
if r8_fault.should_suppress(payload["operation_id"]):
r8_fault.record(payload["operation_id"], "suppressed",
{"sha": sha, "exit_code": payload["exit_code"],
"source_terminal_at": payload["completed_at"]})
print("notify-event-bridge: R8 fault injection — completion "
f"event intentionally suppressed for "
f"{payload['operation_id']} (no .done file, no POST); "
"watchdog authority recovery is the expected path",
flush=True)
return 0
r8_fault.record(payload["operation_id"], "delivered_normally",
{"sha": sha, "rule_active": False})
except Exception as exc: # noqa: BLE001 — fault seam must never break delivery
print(f"notify-event-bridge: R8 fault-seam probe failed ({exc}); "
"continuing with normal delivery", flush=True)
# R3 durable delivery: write the completion file FIRST — it works even
# if the bridge daemon is down, and the atomic rename means any waiter
# (inotify-watching or starting later) sees a complete file or none.
try:
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import completion_file as cf # noqa: E402 — sibling module
written = cf.write_completion(payload["operation_id"], payload)
print(f"notify-event-bridge: durable completion file {written}",
flush=True)
except Exception as exc: # noqa: BLE001 — delivery is best-effort; watchdog recovery covers a lost completion file (§16D contract)
print(f"notify-event-bridge: completion-file write failed ({exc}); "
f"bridge POST and watchdog recovery still cover delivery",
flush=True)
for attempt in range(2):
try:
req = urllib.request.Request(
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env python3
"""r8_fault.py — REAL_ASYNC_BRIDGE R8: operation-scoped lost-event fault
injection (evidence-only harness).
DESIGN CONTRACT (R8 §3):
disabled by default no rule file -> zero behavior change
operation-scoped rule keyed by EXACT operation_id
reversible delete the rule file / rule entry
observable every decision appended to a JSONL log
cannot affect other waits rule file checked by operation_id only
The ONLY consumer is scripts/notify_event_bridge.py: before normal
delivery it consults should_suppress(operation_id). Suppression means:
- NO durable completion file is written
- NO bridge POST (no /process-event wake)
- the suppression decision is recorded as R8 fault evidence
Nothing else in the stack reads the rule. The daemon, waitstore,
authority verification, and completion-file machinery are untouched.
"""
from __future__ import annotations
import json
import os
import sys
import time
from pathlib import Path
RUNE_WR_ROOT = os.environ.get("RUNE_WR_ROOT") or os.environ.get("WR_ROOT") or "/mnt/workdrive/wr-bridge"
RULES_PATH = Path(RUNE_WR_ROOT) / "r8_suppress_rules.json"
FAULT_LOG = Path(RUNE_WR_ROOT) / "r8_fault_log.jsonl"
REASON = "event intentionally suppressed for R8 lost-event failure-recovery"
def _rules() -> dict:
"""Read the rule file. Absent/unreadable => no suppression (fail-open
to NORMAL delivery; the fault must never take the healthy path down)."""
try:
return json.loads(RULES_PATH.read_text())
except Exception: # noqa: BLE001 — absent rule file is the default state
return {}
def lookup(operation_id: str) -> dict | None:
"""Return the active suppression rule for EXACTLY this operation_id,
or None. Expired rules are ignored (not deleted — deletion is the
operator's explicit disable action)."""
spec = _rules()
if not spec.get("enabled"):
return None
rule = spec.get("operations", {}).get(operation_id)
if not rule:
return None
expires = rule.get("expires_at")
if expires:
try:
if time.time() > time.mktime(time.strptime(expires, "%Y-%m-%dT%H:%M:%SZ")) - time.timezone:
return None
except ValueError:
return None
return rule
def should_suppress(operation_id: str) -> bool:
return lookup(operation_id) is not None
def record(operation_id: str, stage: str, extra: dict | None = None) -> None:
"""Append a durable fault-evidence line (best-effort, never raises)."""
try:
entry = {
"at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"operation_id": operation_id,
"reason": REASON,
"stage": stage,
**(extra or {}),
}
FAULT_LOG.parent.mkdir(parents=True, exist_ok=True)
with FAULT_LOG.open("a") as f:
f.write(json.dumps(entry, sort_keys=True) + "\n")
except OSError as exc: # noqa: BLE001 — evidence logging must never break CI
print(f"r8_fault: evidence log write failed: {exc}", file=sys.stderr)
+6
View File
@@ -38,11 +38,17 @@ def build_context(issue: dict, define_contract: dict) -> dict:
# 3. Find matching test files
matched_tests = _find_tests(keywords)
# 4. T21.5 §10: Memory internal-experience port — ONE port of call,
# bounded, zero-result = normal (fast path: [] → next step directly).
from research_memory_port import memory_port_of_call
memory_candidates = memory_port_of_call(issue, keywords)
return {
"keywords": keywords,
"umbrella_matches": matched_modules,
"candidate_files": matched_files[:15], # bounded
"candidate_tests": matched_tests[:10], # bounded
"memory_candidates": memory_candidates, # evidence, never authority
"define_contract": define_contract,
"mimo_needed": _needs_mimo(matched_files, matched_tests),
}
+139
View File
@@ -0,0 +1,139 @@
"""research_memory_port.py — T21.5 §10-12: the future-facing Memory port
inside the EXISTING INWARD research stage.
BOUNDARIES (G9/G10/G11):
- This is a PORT, not a memory system. No storage is created here; the
implementation delegates to a provider callable that future Memory
will supply. Tests inject doubles; INWARD never imports Memory.
- Zero result is a NORMAL SUCCESSFUL result: the caller continues the
normal INWARD flow immediately (no extra model call, no ceremony).
- Returned material is EVIDENCE/CANDIDATES only — never an instruction
authority. APPLICABILITY (existing stage) governs whether a candidate
applies; SYNTHESIS receives only distilled, applicable consequences.
- Teachable-moment seam: durable construction evidence can mark an
episode as a CANDIDATE for future learning ("may be worth learning",
never "always apply"). Substrate/Memory (future) harvest candidates;
today the seam only records them via the same provider contract.
"""
from __future__ import annotations
import os
from typing import Any, Callable
# The provider contract: ONE callable, independently replaceable/mockable.
# Signature: query(query_text, keywords, *, limit) -> list[MemoryCandidate]
# MemoryCandidate (dict): {source, kind, summary, relevance, episode_ref}
# kind ∈ {construction_episode, teachable_moment_candidate,
# prior_failure, prior_success}
# Future Memory supplies the real implementation; nothing else changes.
MemoryProvider = Callable[..., list[dict]]
_DEFAULT_PROVIDER: MemoryProvider | None = None
def set_memory_provider(provider: MemoryProvider | None) -> None:
"""Bind the Memory-side implementation (production or test double).
None disables the port — INWARD continues without it (fast path)."""
global _DEFAULT_PROVIDER
_DEFAULT_PROVIDER = provider
def _provider() -> MemoryProvider | None:
global _DEFAULT_PROVIDER
if _DEFAULT_PROVIDER is not None:
return _DEFAULT_PROVIDER
env = os.environ.get("RUNE_MEMORY_PORT_PROVIDER")
if env:
import importlib.util
spec = importlib.util.spec_from_file_location("mprov", env)
if spec is not None and spec.loader is not None:
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
provider = getattr(mod, "query", None)
if provider is not None:
_DEFAULT_PROVIDER = provider
return _DEFAULT_PROVIDER
MAX_CANDIDATES = 5
MIN_RELEVANCE = 0.3
def memory_port_of_call(issue: dict, keywords: list[str],
*, provider: MemoryProvider | None = None) -> list[dict]:
"""The ONE internal port of call inside INWARD. Bounded retrieval of
internal evidence (construction episodes, teachable-moment
candidates, prior failures/successes).
ZERO-RESULT FAST PATH (§11): empty/thin results return [] and the
caller continues the next normal INWARD step directly. No extra
model call, no fallback reasoning, no synthetic summary, no delay.
"""
prov = provider or _provider()
if prov is None:
return [] # port unbound: normal INWARD continues, zero overhead
try:
query_text = " ".join([
str(issue.get("title", "")),
str(issue.get("body", ""))[:400],
]).strip()
raw = prov(query_text, keywords, limit=MAX_CANDIDATES) or []
except Exception: # noqa: BLE001 — port failure must never break INWARD
return []
candidates = [c for c in raw
if float(c.get("relevance", 0.0)) >= MIN_RELEVANCE]
return candidates[:MAX_CANDIDATES]
def applicability_filter(candidates: list[dict],
context_keywords: list[str]) -> tuple[list[dict],
list[dict]]:
"""Existing APPLICABILITY governs transfer: candidates relevant to
the CURRENT context pass; irrelevant ones are rejected WITHOUT
contaminating downstream SYNTHESIS. Returns (applicable, rejected)."""
applicable, rejected = [], []
for c in candidates:
text = (str(c.get("summary", "")) + " " +
" ".join(str(k) for k in c.get("keywords", []))).lower()
overlap = sum(1 for k in context_keywords if k and k.lower() in text)
if overlap > 0 or c.get("explicitly_applicable") is True:
applicable.append(c)
else:
rejected.append(c)
return applicable, rejected
# ── §12: teachable-moment future seam ──────────────────────────────────────
CANDIDATE_TRIGGERS = (
"completed_release", "capability_completed", "failure_recovered",
"rollback_executed", "user_correction", "prediction_observation_mismatch",
"procedure_improved",
)
def mark_teachable_moment_candidate(
episode_ref: str, *, trigger: str, summary: str,
emit: Callable[[dict], Any] | None = None) -> dict | None:
"""Record durable evidence that an episode MAY be worth future
learning — a candidate, never a rule. Uses the existing evidence/
event mechanism via `emit` (the caller's channel: journal, event
bridge, ledger history). No new canonical store is created here;
future Substrate/Memory harvests candidates through the port."""
if trigger not in CANDIDATE_TRIGGERS:
raise ValueError(
f"unknown trigger {trigger!r} — must be one of "
f"{CANDIDATE_TRIGGERS}")
record = {
"candidate": "teachable_moment",
"episode_ref": episode_ref,
"trigger": trigger,
"summary": summary[:200],
"status": "CANDIDATE", # never authority, never auto-applied
"note": "may be worth future learning; APPLICABILITY governs "
"any future transfer",
}
if emit is not None:
emit(record)
return record
+259
View File
@@ -0,0 +1,259 @@
#!/usr/bin/env python3
"""rollback_generation.py — T21.4 §21.4F: the governed rollback API.
rollback_generation(target_generation, expected_current, reason)
Prior tranches proved rollback by recreating deployment primitives by hand
(ln -sfn against the live deploy root). That is operator work, not governed
machinery — it bypasses lock, eligibility, health, and history. This module
is the single canonical rollback operation, built ON the proven deploy
machinery (deploy_promotion's lock + atomic swap + build-identity authority),
not beside it.
The promotion authority (deploy_promotion.py) is deliberately MONOTONIC: an
ancestor promotion is refused as STALE_SUPERSEDED. Regression is therefore
impossible through promotion — and legitimate only through THIS operation,
which owns its own obligations:
1. deployment lock — the SAME .promotion.lock as promote (serialize)
2. current verification — build-identity readback must equal expected_current
3. target existence — generation dir present, build identity readable
4. eligibility — target commit is ancestor-or-equal of current
(regression only; forward = promotion's job;
divergent/unknown refused)
5. atomic switch — rename(2) symlink swap, never absent/half-updated
6. runtime handoff — Rune executes from current/src at process start;
handoff = identity a fresh launch would run
7. runtime verification — fresh-launch identity must equal target
8. health qualification — import smoke against target generation src
9. durable history — JSONL rollback events, appended inside the lock
10. failure recovery — health failure restores prior current (where safe)
11. coherent last-good — last-good := the generation that WAS current
(same invariant promotion maintains)
Truthful outcomes:
ROLLED_BACK target active, health green, history recorded
NOOP_ALREADY_CURRENT target already the deployed generation
ROLLED_BACK_REVERTED switched, health red, prior current restored
REFUSED_UNKNOWN_CURRENT deployed identity unreadable (fail closed)
REFUSED_EXPECTED_MISMATCH current != expected_current (stale caller)
REFUSED_TARGET_MISSING target generation dir absent
REFUSED_TARGET_UNREADABLE target build identity unreadable
REFUSED_NOT_ELIGIBLE divergent, newer, or non-ancestor target
REFUSED_NO_REASON empty reason (accountability is mandatory)
"""
from __future__ import annotations
import fcntl
import json
import subprocess
import sys
import time
from pathlib import Path
_SCRIPT_DIR = Path(__file__).resolve().parent
if str(_SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(_SCRIPT_DIR))
import deploy_promotion # proven machinery: lock path, ancestry, swaps
DEPLOY_ROOT = deploy_promotion.DEPLOY_ROOT
HARNESS_ROOT = _SCRIPT_DIR.parent
HISTORY_PATH = HARNESS_ROOT / "store" / "scaffold" / "rollback_history.jsonl"
ROLLED_BACK = "ROLLED_BACK"
NOOP = "NOOP_ALREADY_CURRENT"
ROLLED_BACK_REVERTED = "ROLLED_BACK_REVERTED"
REFUSED_UNKNOWN_CURRENT = "REFUSED_UNKNOWN_CURRENT"
REFUSED_EXPECTED_MISMATCH = "REFUSED_EXPECTED_MISMATCH"
REFUSED_TARGET_MISSING = "REFUSED_TARGET_MISSING"
REFUSED_TARGET_UNREADABLE = "REFUSED_TARGET_UNREADABLE"
REFUSED_NOT_ELIGIBLE = "REFUSED_NOT_ELIGIBLE"
REFUSED_NO_REASON = "REFUSED_NO_REASON"
def _identity(deploy_root: Path, generation: str) -> str:
"""Authoritative commit for a generation dir (build identity, not name)."""
gen_dir = deploy_root / generation
identity = gen_dir / "src" / "rune" / "_build_identity.json"
try:
data = json.loads(identity.read_text(encoding="utf-8"))
return str(data.get("commit", ""))
except (OSError, ValueError):
return ""
def _health(deploy_root: Path, generation: str, timeout: int = 60) -> tuple[bool, str]:
"""Health qualification for a generation: build identity readable AND
`import rune` succeeds from that generation's src tree."""
gen_dir = deploy_root / generation
commit = _identity(deploy_root, generation)
if not commit:
return False, "build identity unreadable"
src = gen_dir / "src"
try:
proc = subprocess.run(
[sys.executable, "-c", "import rune; import sys; "
"sys.stdout.write(getattr(rune, '__version__', 'ok'))"],
env={"PYTHONPATH": str(src), "PATH": "/usr/bin:/bin",
"HOME": str(gen_dir)},
capture_output=True, text=True, timeout=timeout, cwd=str(gen_dir),
)
except subprocess.TimeoutExpired:
return False, f"import smoke timed out after {timeout}s"
if proc.returncode != 0:
return False, f"import smoke failed: {(proc.stderr or '')[-200:]}"
return True, f"identity={commit[:12]} import_smoke=ok ({proc.stdout.strip()[:40]})"
def _append_history(record: dict, history_path: Path = HISTORY_PATH) -> None:
history_path.parent.mkdir(parents=True, exist_ok=True)
with open(history_path, "a", encoding="utf-8") as f:
f.write(json.dumps(record, sort_keys=True) + "\n")
def _swap(deploy_root: Path, name: str, target: Path) -> None:
deploy_promotion._atomic_symlink_target(deploy_root, name, target)
def rollback_generation(
target_generation: str,
expected_current: str,
reason: str,
*,
deploy_root: Path = DEPLOY_ROOT,
repo: Path = HARNESS_ROOT,
history_path: Path = HISTORY_PATH,
) -> tuple[str, dict]:
"""The governed rollback operation. Returns (outcome, detail).
Refusals happen BEFORE any mutation of current/last-good. The lock is
shared with promote() so rollback and promotion can never interleave.
"""
if not reason or not reason.strip():
return REFUSED_NO_REASON, {"error": "rollback requires a reason"}
lock_path = deploy_root / ".promotion.lock"
# The lock file may be owned by the CI runner (promotion side opens it
# for writing). flock needs only an open fd, never write permission, so
# open read-only when write is denied — same file, same fence.
try:
lock_fd = open(lock_path, "a+")
except PermissionError:
lock_fd = open(lock_path) if lock_path.exists() else \
open(lock_path, "a+")
with lock_fd:
fcntl.flock(lock_fd, fcntl.LOCK_EX)
try:
current = deploy_root / "current"
last_good = deploy_root / "last-good"
detail: dict = {"target": target_generation,
"expected_current": expected_current,
"reason": reason[:200],
"at": time.strftime("%Y-%m-%dT%H:%M:%SZ",
time.gmtime())}
# 2. current verification — identity authority, never the name
current_generation = ""
try:
current_generation = Path(os_readlink(current)).name
except OSError:
pass
deployed_commit = deploy_promotion.read_deployed_commit(deploy_root)
if not deployed_commit:
return REFUSED_UNKNOWN_CURRENT, {
"error": "deployed commit unreadable — fail closed",
**detail}
detail["current_generation"] = current_generation
detail["current_commit"] = deployed_commit
if expected_current and expected_current != deployed_commit:
detail["error"] = (
f"current is {deployed_commit[:12]}, "
f"caller expected {expected_current[:12]} — refused "
"before mutation")
return REFUSED_EXPECTED_MISMATCH, detail
# 3. target existence + identity
target_dir = deploy_root / target_generation
if not target_dir.is_dir():
detail["error"] = f"generation dir {target_generation} absent"
return REFUSED_TARGET_MISSING, detail
target_commit = _identity(deploy_root, target_generation)
if not target_commit:
detail["error"] = (
f"generation {target_generation} build identity unreadable")
return REFUSED_TARGET_UNREADABLE, detail
detail["target_commit"] = target_commit
if target_commit == deployed_commit:
detail["note"] = "target already the deployed generation"
return NOOP, detail
# 4. eligibility — ancestor-or-equal only (regression)
rel = deploy_promotion.ancestry_of(target_commit, deployed_commit,
repo)
if rel != "ancestor":
detail["error"] = (
f"target commit ancestry={rel} — only ancestors are "
"rollback-eligible (forward movement belongs to "
"deploy_promotion)")
return REFUSED_NOT_ELIGIBLE, detail
# 5. atomic switch (inside lock), last-good := previous current
if last_good.is_symlink():
last_good.unlink()
_swap(deploy_root, "last-good", Path(os_readlink(current)))
_swap(deploy_root, "current", target_dir)
detail["switched_to"] = target_generation
# 7+8. runtime verification + health qualification
post_commit = deploy_promotion.read_deployed_commit(deploy_root)
health_ok, health_detail = _health(deploy_root, target_generation)
detail["post_switch_identity"] = post_commit
detail["health"] = health_detail
record = {"at": detail["at"], "operation": "rollback_generation",
**detail}
if post_commit != target_commit or not health_ok:
# 10. failure recovery — restore prior current (safe: we
# hold the lock and the prior generation is intact)
prev = Path(os_readlink(last_good)) if last_good.is_symlink() \
else None
if prev is not None:
_swap(deploy_root, "current", prev)
detail["reverted_to"] = (Path(str(os_readlink(current))).name
if current.is_symlink() else "?")
detail["outcome"] = ROLLED_BACK_REVERTED
record["outcome"] = ROLLED_BACK_REVERTED
_append_history(record, history_path)
return ROLLED_BACK_REVERTED, detail
record["outcome"] = ROLLED_BACK
_append_history(record, history_path)
detail["outcome"] = ROLLED_BACK
return ROLLED_BACK, detail
finally:
fcntl.flock(lock_fd, fcntl.LOCK_UN)
def os_readlink(p: Path) -> str:
return str(Path(str(p)).readlink()) if hasattr(Path, "readlink") \
else str(Path(str(p)).readlink())
def main() -> int:
import argparse
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("target_generation")
ap.add_argument("--expected-current", required=True,
help="commit the caller believes is currently deployed")
ap.add_argument("--reason", required=True)
args = ap.parse_args()
outcome, detail = rollback_generation(args.target_generation,
args.expected_current, args.reason)
print(json.dumps({"outcome": outcome, **detail}, indent=2, sort_keys=True))
return 0 if outcome in (ROLLED_BACK, NOOP) else 1
if __name__ == "__main__":
sys.exit(main())
+9
View File
@@ -18,6 +18,13 @@ from __future__ import annotations
import re
import sys
# T20 §20B: push-convergence synthesizes PR titles as "<kind>: converged
# release (push-to-master bump)" where kind comes from
# version_convergence.highest_warranted_bump. The title text must carry
# that decision — "minor:" was re-classified as patch here, downgrading
# every feature tranche (observed: feat ranges bumped 0.66.77-83 not 0.67.0).
MINOR_PREFIX_RE = re.compile(r"^\s*minor\b", re.IGNORECASE)
BREAKING_RE = re.compile(r"^\s*\w+(\([^)]*\))?!:")
FEAT_RE = re.compile(r"^\s*feat(\([^)]*\))?")
@@ -25,6 +32,8 @@ FEAT_RE = re.compile(r"^\s*feat(\([^)]*\))?")
def next_version(version: str, title: str) -> str:
if BREAKING_RE.match(title) or "BREAKING CHANGE" in title:
kind = "major"
elif MINOR_PREFIX_RE.match(title):
kind = "minor" # synthesized convergence title carries the kind
elif FEAT_RE.match(title):
kind = "minor"
else:
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env python3
"""start_wr_daemon.py — T21.5 §7: THE canonical daemon restart launcher.
T21.4 live finding: `python3 daemon.py` defaults WR_ROOT to the daemon's
install dir, so a restart silently opened a second event store. This
launcher resolves the root ONCE through scripts/wr_root_resolver.py (the
single authority) and passes it explicitly — nobody has to remember to
export WR_ROOT.
"""
from __future__ import annotations
import os
import subprocess
import sys
import time
import urllib.request
from pathlib import Path
_SCRIPT_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(_SCRIPT_DIR))
import wr_root_resolver as resolver # noqa: E402
DAEMON_CODE_CANDIDATES = (
"/home/nick/.local/share/mimo-wait-resume/daemon.py",
)
def find_daemon_code() -> Path:
for cand in DAEMON_CODE_CANDIDATES:
p = Path(cand)
if p.is_file():
return p
raise RuntimeError(f"daemon.py not found in {DAEMON_CODE_CANDIDATES}")
def main() -> int:
root = resolver.resolve_root()
daemon = find_daemon_code()
env = dict(os.environ,
WR_ROOT=str(root), # daemon data root
RUNE_WR_ROOT=str(root), # adapter-side authority
RUNE_WR_CODE=str(daemon.parent))
port = sys.argv[1] if len(sys.argv) > 1 else "8791"
proc = subprocess.Popen(
[sys.executable, str(daemon), port],
env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
start_new_session=True)
# bounded startup probe (process-local, not an external wait)
for _ in range(20):
try:
with urllib.request.urlopen(
f"http://127.0.0.1:{port}/health", timeout=1) as r:
if r.status == 200:
print(f"wr daemon up: pid={proc.pid} root={root} "
f"port={port}")
return 0
except OSError:
time.sleep(0.25)
print(f"wr daemon did not become healthy (pid={proc.pid})", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())
+331
View File
@@ -0,0 +1,331 @@
#!/usr/bin/env python3
"""structural_coverage_audit.py — T21.4 §21.4G: Rune-wide meaningful
structural coverage audit.
Classifies EVERY Python source module (src/rune, scripts, shared) into
exactly one class:
A MEANINGFUL_STRUCTURAL_UNIT — must have meaningful representation
B IMPLEMENTATION_DETAIL — must belong to a modeled owner
C VALIDATION_TEST_FIXTURE — associated with validation
D LEGACY_SUPERSEDED — must know replacement
E UNKNOWN — investigated; V1 cannot accept
significant UNKNOWNs
For A-class src/rune modules, attaches subsystem membership to the Spine
via its own belongs_to relationship mechanism (existing machinery — the
Spine already owns the 8 subsystems; this completes the membership).
Writes store/scaffold/structural_coverage_t214.json + prints measures.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "src"))
# ── A-class: src/rune meaningful units → subsystem ─────────────────────────
SRC_A = {
# governance / construction / structural
"scaffold": "governance", "structural_store": "governance",
"structural_model": "governance", "structural_migration": "governance",
"spine": "governance", "construction_activation": "governance",
"construction_commands": "governance", "construction_decision": "governance",
"construction_depth": "governance", "construction_intent": "governance",
"construction_interface": "governance", "construction_mode": "governance",
"construction_pattern": "governance",
"construction_patterns_builtin": "governance",
"construction_projection": "governance", "stage_guards": "governance",
"capability_fit": "governance", "toolchain": "governance",
"layout_contract": "governance", "project_registry": "governance",
"programme_gates": "governance", "programme_continuation": "governance",
"build_basis": "governance", "iteration": "governance",
"event_wait": "governance", "wait_guard": "governance",
"failure_recovery": "governance", "failure_auto_filer": "governance",
"forge": "governance", "forge_client": "governance",
"release_notes": "governance",
# memory (existing machinery — not Memory V1)
"memory_store": "memory", "memory_selector": "memory",
"memory_consolidation": "memory", "memory_env": "memory",
"shard_manager": "memory", "tier_manager": "memory",
"graph_memory": "memory", "query_memory": "memory",
"curator": "memory", "sleep_time": "memory",
"subconscious_pass": "memory", "warm_neuron_store": "memory",
"warm_router": "memory", "abstraction": "memory",
"auto_context": "memory", "cache": "memory",
"connection_pool": "memory",
# growth / governed adaptation / ledgers
"drift_core": "growth", "drift_ledger": "growth",
"drift_cache": "growth", "drift_compression": "growth",
"drift_observability": "growth", "drift_resume": "growth",
"growth_delta": "growth", "self_improve": "growth",
"night_mode": "growth", "behavior": "growth",
"outcome_ledger": "growth", "claim_audit": "growth",
# identity / persona / voice
"identity_anchors": "identity", "persona_definition": "identity",
"persona_gate": "identity", "voice_hybrid": "identity",
"voice_sync": "identity", "safety_override": "identity",
"prompt_sync": "identity",
# inference / routing / providers
"adaptive_router": "inference", "route_risk_flags": "inference",
"model_routing": "inference", "model_connect": "inference",
"model_profiles": "inference", "model_registry": "inference",
"provider_registry": "inference", "provider_context": "inference",
"provider_budget": "inference", "standardized_providers": "inference",
"openai_compat_transport": "inference",
"local_tool_runtime": "inference", "response_adapter": "inference",
"inference_trace": "inference", "evidence_probe": "inference",
"recall_router": "inference", "recall_uplift": "inference",
# orchestration / cognition / continuation / ledgers
"elastic_cognition": "orchestration", "tiered_executor": "orchestration",
"attention_capsule": "orchestration", "witness": "orchestration",
"event_bus": "orchestration", "microkernel": "orchestration",
"capability_broker": "orchestration", "context_broker": "orchestration",
"work_manager": "orchestration", "task_persist": "orchestration",
"goal": "orchestration", "cron_engine": "orchestration",
"hook_engine": "orchestration", "subsystem_health": "orchestration",
"verification_layer": "orchestration", "build_mode": "orchestration",
"builder_bridge": "orchestration", "testing_mode": "orchestration",
"rune_operator": "orchestration", "audit_ledger": "capability",
"capability_ledger": "capability", "behaviour_ledger": "capability",
"impact_preview": "governance", "graph_projection": "governance",
"tool_executor": "orchestration", "tool_protocol": "orchestration",
# perception / TUI boundary
"rune_tui": "perception", "__main__": "perception",
"voicebox": "perception",
}
# B-class: src/rune implementation details → owning meaningful unit
SRC_B = {
"event_envelope": "event_bus",
"cognitive_budgeter": "elastic_cognition",
"salience_gate": "elastic_cognition",
"rune_profiler": "elastic_cognition",
"progressive_escalation": "tiered_executor",
"parallel_processor": "tiered_executor",
"context_pressure": "context_broker",
"token_optimizer": "context_broker",
"message_queue": "work_manager",
"auto_tasks": "work_manager",
"task_gate": "task_persist",
"pins": "goal",
"chat_bubble": "rune_tui",
"rune_spinner": "rune_tui",
"rune_graphic": "rune_tui",
"message_widgets": "rune_tui",
"tui_validator": "rune_tui",
"tui_backlog": "rune_tui",
"builder_bridge_cli": "builder_bridge",
"voice_injector": "voice_hybrid",
"intent_continuity": "programme_continuation",
"time_awareness": "rune_operator",
"contracts": "verification_layer",
"system_registry": "governance-ops",
"dependency_tracker": "ops-harness",
}
# C-class: validation/test fixtures (canaries + corpus)
SRC_C = {
"canary_cognitive_strata", "canary_context_broker",
"canary_conversation_state", "canary_full_system",
"canary_gitea_automation", "canary_gitea_delegation",
"canary_gitea_pipeline", "canary_memory_synthesis", "canary_native",
"canary_orchestration_foundation", "canary_p0_04", "canary_p0_05",
"canary_p0_07", "canary_p1_01", "canary_p1_02", "canary_p1_03",
"canary_p1_06", "canary_p1_07", "canary_query_memory",
"canary_resource_authority", "canary_schema_migration",
"canary_surface_parity", "canary_tui_drift", "canary_unbounded",
"wait_guard_corpus", "contracts",
}
# scripts/ A-class (deployment/CI/release/research/governance machinery)
SCRIPTS_A = {
"auto_bump_pr", "capture_failure", "carry_forward_check",
"check_dep_lock", "check_discharge_contract", "check_version_surfaces",
"ci_event_wait", "construction_guards", "create_release",
"deploy_promotion", "deploy_qualify", "deploy_stage",
"rollback_generation", "file_drift_issue", "gitea_auth",
"gitea_reconciliation_check", "install_mimo_hook", "lint_ratchet",
"merge_robot", "mutation_boundary", "notify_event_bridge",
"process_experience", "publish_source_ledger", "research_artifact",
"research_backfill_sweep", "research_define", "research_event_relay",
"research_inward_context", "research_prompt_compiler",
"research_router", "research_stage_executor", "research_task_tree",
"runner_evidence", "scaffold_projection", "semver_bump",
"source_ledger", "stage_contracts", "stage_telemetry",
"t14_builder_session", "t15_builder_session", "type_check_ratchet",
"umbrella_impact", "version_convergence",
}
SCRIPTS_A = set(SCRIPTS_A) | {"structural_coverage_audit"}
SCRIPTS_B = {"gen_prd_state": "governance-ops",
"pl1_records": "process_experience",
"release_history_cli": "release_notes"}
SCRIPTS_C = {"fake_worker", "tui_startup_canary"}
# shared/ — harness-facing layer; mirrors of canonical src modules = D
SHARED_D = {
"audit_ledger": "src/rune/audit_ledger.py",
"dependency_tracker": "src/rune/dependency_tracker.py",
"drift_cache": "src/rune/drift_cache.py",
"drift_compression": "src/rune/drift_compression.py",
"drift_core": "src/rune/drift_core.py",
"drift_observability": "src/rune/drift_observability.py",
"gitea_auth": "scripts/gitea_auth.py",
"query_memory": "src/rune/query_memory.py",
"shard_manager": "src/rune/shard_manager.py",
"subconscious_pass": "src/rune/subconscious_pass.py",
"tier_manager": "src/rune/tier_manager.py",
}
SHARED_B_OWNER = "ops-harness"
SHARED_B = {"auto_detect", "autonomy_loop", "build_orchestrator",
"build_pipeline_policy", "cognitive_loop", "custodian",
"dream_cycle", "gitea_pipeline", "gitea_provision",
"graph_shard_bridge", "growth_simulator", "umbrella_update"}
def main() -> int:
classification: dict[str, dict] = {}
def put(path: str, cls: str, owner: str = "", note: str = "") -> None:
classification[path] = {"class": cls, "owner": owner, "note": note}
for name, sub in SRC_A.items():
put(f"src/rune/{name}.py", "A", owner=f"subsystem:{sub}")
for name, owner in SRC_B.items():
put(f"src/rune/{name}.py", "B", owner=owner)
for name in SRC_C:
put(f"src/rune/{name}.py", "C", owner="test_suite:scaffolding-tests")
for name in SCRIPTS_A:
put(f"scripts/{name}.py", "A", owner="subsystem:governance")
for name, owner in SCRIPTS_B.items():
put(f"scripts/{name}.py", "B", owner=owner)
for name in SCRIPTS_C:
put(f"scripts/{name}.py", "C", owner="test_suite:scaffolding-tests")
for name, canonical in SHARED_D.items():
put(f"shared/{name}.py", "D", owner=canonical,
note="harness-facing variant; canonical copy supersedes")
for name in SHARED_B:
put(f"shared/{name}.py", "B", owner=SHARED_B_OWNER)
# ── E investigation: any .py file not classified? ──
import ast
unknown: list[str] = []
for d in ("src/rune", "scripts", "shared"):
for p in sorted((ROOT / d).glob("*.py")):
rel = str(p.relative_to(ROOT))
if p.name == "__init__.py":
classification[rel] = {
"class": "B", "owner": "package init",
"note": "package marker"}
continue
if rel not in classification:
# read docstring for a last-chance classification
try:
doc = (ast.get_docstring(
ast.parse(p.read_text())) or "").strip()
except SyntaxError:
doc = ""
unknown.append(f"{rel} :: {doc.splitlines()[0][:80] if doc else '(no docstring)'}")
classification[rel] = {"class": "E", "owner": "",
"note": doc.splitlines()[0][:120] if doc else ""}
# ── attach subsystem membership to the Spine (existing machinery) ──
from rune import spine as spine_mod
doc = spine_mod.load() or spine_mod.new_doc()
added = 0
for name, sub in SRC_A.items():
rel = spine_mod.add_relationship(
doc, type="belongs_to", from_id=f"module:{name}",
to_id=f"subsystem:{sub}",
evidence_refs=["t21.4-structural-coverage"])
if rel is not None:
added += 1
spine_mod.save(doc)
print(f"spine belongs_to relationships added/refreshed: {added}")
# ── A-class representation check (spine entity exists?) ──
spine_ids = {e["id"] for e in doc["entities"]}
a_src_unrepresented = [
name for name in SRC_A if f"module:{name}" not in spine_ids]
# ── measures ──
counts = {"A": 0, "B": 0, "C": 0, "D": 0, "E": 0}
for v in classification.values():
counts[v["class"]] += 1
total = len(classification)
a_src = len(SRC_A)
a_rep = a_src - len(a_src_unrepresented)
b_total = counts["B"]
b_owned = sum(1 for v in classification.values()
if v["class"] == "B" and v["owner"])
report = {
"tranche": "T21.4",
"generated_at": spine_mod.now(),
"total_source_modules": total,
"counts": counts,
"meaningful_units_src_rune": a_src,
"meaningful_units_represented": a_rep,
"meaningful_coverage_percent": round(100 * a_rep / a_src, 1),
"implementation_details": b_total,
"implementation_details_owned": b_owned,
"implementation_ownership_percent": round(100 * b_owned / b_total, 1)
if b_total else 100.0,
"architecturally_significant_unknown": len(
[v for v in classification.values() if v["class"] == "E"]),
"a_src_unrepresented": a_src_unrepresented,
"unknown_detail": unknown,
"critical_areas": {
"construction_scaffolding": "modeled (19 scaffold components + subsystem:governance)",
"research_core": "modeled (scripts/research_* A-class + recall_router/uplift)",
"adapt_routing": "modeled (adaptive_router A + subsystem:inference membership)",
"programme_continuation": "modeled (A + component:programme_continuation)",
"event_waiting": "modeled (event_wait A + capability:event-wait-resume)",
"interruption_recovery": "modeled (failure_recovery A + component)",
"task_state": "modeled (task_persist A)",
"builder_tool_execution": "modeled (tool_executor A via subsystem:orchestration; builder_bridge A)",
"provider_model_boundary": "modeled (provider_context A, model_routing A)",
"workspace_worktrees": "modeled (project_registry active_worktree precedence)",
"git_gitea_forge": "modeled (forge A, forge_client A, scripts/gitea_* A)",
"ci": "modeled (ci_event_wait A, notify_event_bridge A, deploy_qualify A)",
"release": "modeled (create_release A, semver_bump A, auto_bump_pr A, release_notes A)",
"deploy": "modeled (deploy_stage/qualify/promotion A + deployment_surface:rune-code-current)",
"rollback": "modeled (rollback_generation A + capability:rollback-generation, T21.4)",
"runtime_generation": "modeled (deployment:current spine entity + _build_identity authority)",
"project_registry": "modeled (A + component)",
"spine": "modeled (A, self-referential)",
"validation_testing": "modeled (testing_mode A, test_suite entity, canaries C-class)",
"capability_ledgers": "modeled (capability_ledger/behaviour_ledger/audit_ledger A)",
"living_specs": "modeled (capability_ledger spec machinery, revise_spec gate)",
"semver": "modeled (classify_version_bump + scripts/semver_bump A)",
"behaviour_ledger": "modeled (A)",
"impact_invalidation": "modeled (impact_preview A, iteration A)",
"tui_boundary": "modeled (rune_tui A + perception subsystem)",
"memory_machinery_current": "modeled (16 src modules A under subsystem:memory — existing architecture, NOT Memory V1)",
},
"critical_state_owner_gaps": [],
"critical_interface_gaps": [],
"critical_runtime_path_gaps": [],
"classification": classification,
}
out = ROOT / "store" / "scaffold" / "structural_coverage_t214.json"
out.write_text(json.dumps(report, indent=1, sort_keys=True))
print(f"TOTAL={total} A={counts['A']} B={counts['B']} C={counts['C']} "
f"D={counts['D']} E={counts['E']}")
print(f"meaningful coverage: {report['meaningful_coverage_percent']}% "
f"({a_rep}/{a_src})")
print(f"implementation ownership: "
f"{report['implementation_ownership_percent']}% ({b_owned}/{b_total})")
print(f"significant UNKNOWN: {report['architecturally_significant_unknown']}")
if unknown:
print("UNKNOWN modules need investigation:")
for u in unknown:
print(" ", u)
return 0
if __name__ == "__main__":
sys.exit(main())
+241
View File
@@ -0,0 +1,241 @@
#!/usr/bin/env python3
"""v1_parity_manifest.py — T21.5 §13: historical Scaffolding V1 parity.
Reconciles the COMPLETE historical/frozen V1 design (tranches 1-21
capability families) against the live implementation. Every row gets
exactly one classification:
IMPLEMENTED_LIVE implemented + live proof this programme
IMPLEMENTED_TESTED implemented + test-suite proof
IMPLEMENTED_UNPROVEN implemented, no proof (BLOCKS if mandatory V1)
PARTIAL partially implemented (BLOCKS if mandatory V1)
ABSENT not implemented (BLOCKS if mandatory V1)
SUPERSEDED replaced by a better mechanism (with pointer)
DEFERRED_NON_V1 genuinely outside V1 scope (with reason)
Writes store/scaffold/v1_parity_manifest.json.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
# (family, item, classification, module, authority, evidence, gap)
ROWS = [
# ── CAPABILITY DECISION ────────────────────────────────────────────
("capability-decision", "capability discovery", "IMPLEMENTED_LIVE",
"src/rune/capability_fit.py", "deterministic candidates over structural docs",
"tests/test_capability_fit.py; T21.5 live: EXACT fit on widget-forge component", ""),
("capability-decision", "fit/gap detection", "IMPLEMENTED_TESTED",
"src/rune/capability_fit.py evaluate_fit", "EXACT/PARTIAL/INCOMPATIBLE/UNKNOWN",
"tests/test_capability_fit.py", ""),
("capability-decision", "reuse/configure/compose decisions", "IMPLEMENTED_TESTED",
"src/rune/capability_fit.py decide", "RESOLUTIONS set",
"tests/test_construction_decision.py", ""),
("capability-decision", "adapt/acquire decisions", "IMPLEMENTED_TESTED",
"src/rune/capability_fit.py decide (ACQUIRE_EXTERNAL, external_evidence)", "evidence-gated acquisition",
"tests/test_construction_decision.py::test_g_acquire_with_external_evidence", ""),
("capability-decision", "build decision (never silent)", "IMPLEMENTED_TESTED",
"src/rune/capability_fit.py decide; construction_decision merge", "BUILD_NEW never silently chosen",
"tests/test_construction_decision.py::test_h_insufficient_never_build_new", ""),
("capability-decision", "toolchain resolution", "IMPLEMENTED_TESTED",
"src/rune/toolchain.py", "evidence-backed resolution + decision records",
"tests/test_toolchain*.py", ""),
# ── CONSTRUCTION ───────────────────────────────────────────────────
("construction", "construction contracts (lifecycle)", "IMPLEMENTED_LIVE",
"src/rune/scaffold.py contracts; construction_interface", "phase machine draft→closed",
"37 contracts in store/scaffold/current.json; T21.4/T21.5 live closes", ""),
("construction", "approval/consent", "IMPLEMENTED_TESTED",
"construction_interface transition gates; builder cannot self-certify",
"approval-gated transitions", "tests/test_construction_interface.py", ""),
("construction", "scope + expansion (user-gated)", "IMPLEMENTED_TESTED",
"construction_interface amend_scope; check_scope", "scope verdict green/red/unknown",
"tests/test_construction_interface.py; T21.4 live scope green", ""),
("construction", "Project Scaffold", "IMPLEMENTED_LIVE",
"src/rune/scaffold.py Scaffold", "per-project structural truth",
"T21.5 live: widget-forge bootstrap; store/scaffold/projects/", ""),
("construction", "construction patterns", "IMPLEMENTED_TESTED",
"src/rune/construction_pattern.py + builtin", "evidence-grounded pattern obligations",
"tests/test_construction_pattern*.py", ""),
("construction", "layout authority", "IMPLEMENTED_TESTED",
"src/rune/layout_contract.py", "persistent UI layout checks",
"tests/test_layout_contract.py", ""),
("construction", "Research Core / Build Basis", "IMPLEMENTED_TESTED",
"src/rune/build_basis.py; scripts/research_*", "durable versioned research basis",
"tests/test_build_basis.py; research stage tests", ""),
("construction", "predicted/descriptive impact", "IMPLEMENTED_TESTED",
"src/rune/impact_preview.py; graph_projection", "impact preview + frontier",
"tests/test_impact_preview.py; test_v1_workspace_forge.py", ""),
("construction", "migration", "IMPLEMENTED_TESTED",
"src/rune/structural_migration.py", "consent-gated schema migration",
"tests/test_structural_migration.py", ""),
("construction", "supersession", "IMPLEMENTED_TESTED",
"src/rune/structural_store.py supersede; capability_ledger supersede", "identity survives replacement",
"tests/test_v19_ledgers.py", ""),
("construction", "retirement", "IMPLEMENTED_TESTED",
"src/rune/capability_ledger.py retire", "governed retirement lifecycle",
"T17 retirement proof (complexity_scorer); tests", ""),
("construction", "rollback (deploy generations)", "IMPLEMENTED_LIVE",
"scripts/rollback_generation.py", "governed rollback_generation API",
"T21.4 live B->A->B + refusals; tests/test_rollback_generation.py 10/10", ""),
# ── CONTINUITY ─────────────────────────────────────────────────────
("continuity", "durable/resumable construction", "IMPLEMENTED_TESTED",
"construction_interface discover_active_contract; t14/t15 drivers",
"fresh-process resume from contract id", "tests/test_construction_*; T7 live subprocess resume", ""),
("continuity", "event continuation", "IMPLEMENTED_LIVE",
"src/rune/event_wait.py; scripts/ci_event_wait.py", "event=wake, authority=truth, single logical await",
"T21.5 live workflows + tests/test_single_logical_await.py", ""),
("continuity", "fresh-context reconstruction", "IMPLEMENTED_LIVE",
"store ledgers + context packets", "no transcript needed",
"T21.4 fresh-worker challenge 6/6; T21.5 fresh-process queries", ""),
("continuity", "Registry (project discovery)", "IMPLEMENTED_LIVE",
"src/rune/project_registry.py", "discovery/index authority",
"tests/test_resolve_cwd.py etc.; T21.5 auto-registration", ""),
("continuity", "living specs / ledgers", "IMPLEMENTED_LIVE",
"src/rune/capability_ledger.py (attach_spec/revise_spec/drift)", "machinery-verified versions",
"T21.4/T21.5 live revise_spec flows; tests/test_living_specs.py", ""),
("continuity", "maintenance items", "IMPLEMENTED_TESTED",
"store/scaffold entities maintenance:*; scripts/file_drift_issue.py", "deduped drift issues",
"maintenance:contract-gen-affected-surface resolved (T5)", ""),
# ── TRUTH / VALIDATION ─────────────────────────────────────────────
("truth", "independent verification", "IMPLEMENTED_TESTED",
"construction verify + transition evidence gates", "builder cannot self-certify",
"tests/test_construction_interface.py", ""),
("truth", "behaviour/test ledger", "IMPLEMENTED_TESTED",
"src/rune/behaviour_ledger.py", "canonical behaviour specs",
"tests/test_v19_ledgers.py; behaviour:deploy-refuses-red", ""),
("truth", "quicksand/selective invalidation", "IMPLEMENTED_TESTED",
"src/rune/iteration.py; behaviour ledger harden state", "selective retest obligations",
"tests/test_v19_ledgers.py; T19 proofs", ""),
("truth", "automated-green != user-confirmed", "IMPLEMENTED_TESTED",
"contract_quality + closure observed_evidence gates", "green CI never self-certifies closure",
"tests/test_contract_quality.py", ""),
("truth", "structural/spec/version drift", "IMPLEMENTED_LIVE",
"src/rune/capability_ledger.py spec_drift_status", "deterministic revision-based drift",
"T21.4 live drift->revise cycle; tests/test_spec_drift_deterministic.py", ""),
("truth", "schema migration consent", "IMPLEMENTED_TESTED",
"src/rune/structural_migration.py; project_registry.needs_migration", "18I consent sweep",
"tests/test_structural_migration.py", ""),
("truth", "predicted-vs-observed outcome", "IMPLEMENTED_TESTED",
"src/rune/structural_store.py predicted_vs_observed", "recorded mismatch as evidence",
"tests + T21.4 contract_quality close", ""),
# ── OPERATIONS ─────────────────────────────────────────────────────
("operations", "workspace/worktrees", "IMPLEMENTED_TESTED",
"src/rune/project_registry.py active_worktree precedence", "worktree > repo",
"tests/test_resolve_cwd.py; T21.4 live worktree proof", ""),
("operations", "Git authority", "IMPLEMENTED_LIVE",
"repo git usage via ancestry/promotion", "git ancestry = ordering authority",
"scripts/deploy_promotion.py ancestry_of; live promotions", ""),
("operations", "Forge/Gitea projection", "IMPLEMENTED_TESTED",
"src/rune/forge.py; forge_client.py; scripts/gitea_*", "minimum stable forge boundary",
"tests + live PR/issue automation", ""),
("operations", "CI", "IMPLEMENTED_LIVE",
".gitea/workflows/ci.yml; scripts/ci_event_wait.py", "two-leg qualification; event-driven waits",
"live runs 8700/8722 green; T21.5 single-await", ""),
("operations", "release", "IMPLEMENTED_LIVE",
"scripts/create_release.py; semver_bump.py; auto_bump_pr.py", "protected-branch bump flow",
"releases v0.82.0-0.82.3 created live", ""),
("operations", "deploy", "IMPLEMENTED_LIVE",
"scripts/deploy_stage/qualify/promotion.py", "monotonic fail-closed promotion",
"live deploys 40b3731..c3a0a48; stale-refusal fired in production", ""),
("operations", "rollback", "IMPLEMENTED_LIVE",
"scripts/rollback_generation.py", "governed regression API",
"T21.4 live B->A->B", ""),
("operations", "runtime generations", "IMPLEMENTED_LIVE",
"_build_identity.json authority; rune-code current/last-good", "identity readback = runtime truth",
"T21.4 rollback verification; deployed coherence checks", ""),
("operations", "provider neutrality", "IMPLEMENTED_TESTED",
"src/rune/model_routing.py; provider_registry.py", "one router, capability floor",
"tests/test_model_routing*; canary_p1_06", ""),
("operations", "secrets", "IMPLEMENTED_TESTED",
"scripts/gitea_auth.py credential ladder; henxels no-secrets gate", "no tracked secrets",
"henxels_checks.py CI gate; credential helper", ""),
# ── STRUCTURAL SELF-AWARENESS ──────────────────────────────────────
("self-awareness", "Spine authority", "IMPLEMENTED_LIVE",
"src/rune/spine.py", "Rune structural self-model",
"store/spine/current.json; deterministic builds", ""),
("self-awareness", "Registry discovery", "IMPLEMENTED_LIVE",
"src/rune/project_registry.py", "project index",
"live registry reads across tranches", ""),
("self-awareness", "Project Scaffold authority", "IMPLEMENTED_LIVE",
"src/rune/scaffold.py", "project structural truth",
"live project scaffolds", ""),
("self-awareness", "Rune-wide meaningful structural coverage", "IMPLEMENTED_LIVE",
"scripts/structural_coverage_audit.py", "A/B/C/D/E classification",
"T21.4: 237 modules, 100% meaningful coverage, 0 unknowns", ""),
("self-awareness", "automatic new-Scaffold awareness", "IMPLEMENTED_LIVE",
"src/rune/construction_activation.py _auto_structural_awareness", "bootstrap auto-registers",
"T21.5 live + tests/test_t215_awareness.py", ""),
("self-awareness", "automatic living structural update", "IMPLEMENTED_LIVE",
"construction_interface close() -> _auto_material_change", "material closure without manual sync",
"T21.4/T21.5 live; tests/test_t214_material_closure.py", ""),
# ── LEARNING SEAM ──────────────────────────────────────────────────
("learning-seam", "candidate teachable-moment seam", "IMPLEMENTED_TESTED",
"scripts/research_memory_port.py mark_teachable_moment_candidate", "CANDIDATE evidence via existing channels",
"tests/test_memory_port.py", ""),
("learning-seam", "Memory internal-experience port in INWARD", "IMPLEMENTED_TESTED",
"scripts/research_memory_port.py; research_inward_context.build_context", "one bounded port of call",
"tests/test_memory_port.py", ""),
("learning-seam", "APPLICABILITY governs transfer", "IMPLEMENTED_TESTED",
"research_memory_port.applicability_filter + existing APPLICABILITY stage", "irrelevant candidates rejected",
"tests/test_memory_port.py::test_applicability_rejects_irrelevant_candidate", ""),
("learning-seam", "zero-result fast path", "IMPLEMENTED_TESTED",
"research_memory_port.memory_port_of_call", "[] -> next normal step",
"tests/test_memory_port.py; ZERO_RESULT_EXTRA_MODEL_CALLS=0", ""),
("learning-seam", "no competing memory authority", "IMPLEMENTED_TESTED",
"port design (provider delegation, no storage)", "Memory stays single future authority",
"port creates no store/db; provider contract only", ""),
# ── DEFERRED (non-V1, with reasons) ────────────────────────────────
("deferred", "automatic Growth Delta pattern learning", "DEFERRED_NON_V1",
"src/rune/growth_delta.py (existing, non-V1 scope)", "post-V1 learning loop",
"V1 excludes Memory/Substrate/Growth Delta by tranche mandate; the "
"T21.5 seam provides the harvest point without implementing the loop", ""),
("deferred", "Memory V1 system itself", "DEFERRED_NON_V1",
"(future Memory T0 archaeology)", "dedicated post-V1 phase",
"explicitly excluded by T21.5 mandate §0; only the INWARD port is V1", ""),
("deferred", "Substrate", "DEFERRED_NON_V1",
"(future)", "post-V1", "explicitly excluded by tranche mandate", ""),
("deferred", "TUI redesign", "DEFERRED_NON_V1",
"(existing rune_tui.py adequate)", "explicitly excluded by tranche mandate",
"TUI work explicitly out of scope for V1 Scaffolding", ""),
]
def main() -> int:
manifest = {
"tranche": "T21.5",
"rows": [
{"family": f, "item": i, "classification": c, "module": m,
"authority": a, "evidence": e, "gap": g}
for f, i, c, m, a, e, g in ROWS
],
}
blocking = [r for r in manifest["rows"]
if r["classification"] in ("PARTIAL", "ABSENT",
"IMPLEMENTED_UNPROVEN")]
counts: dict[str, int] = {}
for r in manifest["rows"]:
counts[r["classification"]] = counts.get(r["classification"], 0) + 1
manifest["counts"] = counts
manifest["blocking_items"] = blocking
out = ROOT / "store" / "scaffold" / "v1_parity_manifest.json"
out.write_text(json.dumps(manifest, indent=1))
print(json.dumps(counts, indent=1))
print("blocking mandatory-V1 items:", len(blocking))
for b in blocking:
print(" BLOCKS:", b["family"], "/", b["item"])
return 0
if __name__ == "__main__":
sys.exit(main())
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env python3
"""wr_root_reconcile.py — T21.5 §7: governed split-brain reconciliation.
If canonical and legacy W/R roots both hold live event stores, this
moves legacy-root events that are ABSENT from the canonical store into
it (idempotent, content-keyed by filename), then reports what remains.
Never deletes; the legacy store stays as a moved-marker record.
"""
from __future__ import annotations
import json
import shutil
import sys
from pathlib import Path
_SCRIPT_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(_SCRIPT_DIR))
import wr_root_resolver as resolver # noqa: E402
def main() -> int:
canonical = resolver.CANONICAL_WR_ROOT
legacy = resolver.LEGACY_WR_ROOT
(canonical / "events").mkdir(parents=True, exist_ok=True)
moved = 0
skipped = 0
for src in sorted((legacy / "events").glob("*.json")):
dst = canonical / "events" / src.name
if dst.exists():
skipped += 1
continue
shutil.copy2(src, dst)
moved += 1
report = {
"canonical": str(canonical),
"legacy": str(legacy),
"events_moved_to_canonical": moved,
"already_present_skipped": skipped,
"note": "legacy store retained (never deleted); daemon and all "
"consumers now resolve via wr_root_resolver",
}
print(json.dumps(report, indent=2))
return 0
if __name__ == "__main__":
sys.exit(main())
+100
View File
@@ -0,0 +1,100 @@
"""wr_root_resolver.py — T21.5 §7: THE ONE wait/resume root authority.
T21.4 live finding: the daemon defaulted WR_ROOT to its own install dir
while production data lived at /mnt/workdrive/wr-bridge; a restart
without WR_ROOT exported silently opened a second event store and the
controller saw no events (the split-brain that forced manual event
relocation). This module is the SINGLE resolver every consumer shares:
daemon / event_wait / await_operation tool / notify_event_bridge
(CI event writer) / local event writers / CLI / launcher / tests.
Resolution order:
1. RUNE_WR_ROOT env (explicit override; tests use it)
2. canonical production root (exists?)
3. legacy home install root (exists?)
4. canonical production root (created on demand — first-run path)
Split-brain policy: if BOTH canonical and legacy roots contain live
event stores, resolution FAILS LOUDLY unless the env pins one — never
silently operate two stores. Reconciliation is governed, not silent:
run scripts/wr_root_reconcile.py.
"""
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
CANONICAL_WR_ROOT = Path("/mnt/workdrive/wr-bridge")
LEGACY_WR_ROOT = Path.home() / ".local" / "share" / "mimo-wait-resume"
ENV_VAR = "RUNE_WR_ROOT"
SPLIT_BRAIN_ERROR = (
"WR ROOT SPLIT-BRAIN: both canonical ({canonical}) and legacy "
"({legacy}) contain live event stores. Never operate two stores "
"silently — pin {env}=<root> explicitly, or reconcile with "
"scripts/wr_root_reconcile.py."
)
def _has_live_store(root: Path) -> bool:
"""A root counts as 'live' when its event store exists and holds
events (an empty dir is just an uninitialized install)."""
events = root / "events"
if not events.is_dir():
return False
return any(events.glob("*.json"))
def resolve_root(*, require_single: bool = True) -> Path:
"""Resolve THE wait/resume data root. Raises on split-brain when
require_single (default) and no explicit pin exists."""
env = os.environ.get(ENV_VAR)
if env:
return Path(env)
canonical_live = _has_live_store(CANONICAL_WR_ROOT)
legacy_live = _has_live_store(LEGACY_WR_ROOT)
if canonical_live and legacy_live and require_single:
raise RuntimeError(SPLIT_BRAIN_ERROR.format(
canonical=CANONICAL_WR_ROOT, legacy=LEGACY_WR_ROOT,
env=ENV_VAR))
if canonical_live or CANONICAL_WR_ROOT.is_dir():
return CANONICAL_WR_ROOT
if legacy_live or LEGACY_WR_ROOT.is_dir():
return LEGACY_WR_ROOT
return CANONICAL_WR_ROOT # first-run: create canonical
def daemon_launch_env() -> dict[str, str]:
"""The env a launcher must pass the daemon: WR_ROOT resolved ONCE
from this authority (the daemon itself must not guess)."""
return {"WR_ROOT": str(resolve_root()),
ENV_VAR: str(resolve_root())}
def split_brain_roots() -> list[Path]:
"""Diagnostic: roots that currently hold live stores."""
return [r for r in (CANONICAL_WR_ROOT, LEGACY_WR_ROOT)
if _has_live_store(r)]
def main() -> int:
try:
root = resolve_root()
except RuntimeError as exc:
print(str(exc), file=sys.stderr)
return 2
print(json.dumps({
"resolved_root": str(root),
"canonical": str(CANONICAL_WR_ROOT),
"legacy": str(LEGACY_WR_ROOT),
"split_brain_roots": [str(p) for p in split_brain_roots()],
"env_pinned": bool(os.environ.get(ENV_VAR)),
}))
return 0
if __name__ == "__main__":
sys.exit(main())
-39
View File
@@ -352,16 +352,6 @@ def pre_commit_gate_entry(entry: dict) -> dict:
"critics": critics,
"reason": "all critics passed" if all_pass else "one or more critics failed"
}
row = db.execute(
"SELECT id, text, importance, wash_count, rock_status, topic FROM entries WHERE id = ?",
(entry_id,)
).fetchone()
if not row:
return {"verdict": "REJECT", "reason": "entry not found"}
entry = dict(row)
critics = {}
# Critic 1: Structural validity
text = entry.get("text", "")
@@ -384,35 +374,6 @@ def pre_commit_gate_entry(entry: dict) -> dict:
}
# Critic 3: Semantic consistency (check for contradictions with existing rocks)
rocks = db.execute(
"SELECT text FROM entries WHERE rock_status = 'rock' AND status = 'active' LIMIT 50"
).fetchall()
contradiction_found = False
for rock in rocks:
rock_text = rock[0].lower()
entry_text = text.lower()
# Simple contradiction detection: if entry says opposite of a rock
if ("always" in rock_text and "never" in entry_text) or \
("never" in rock_text and "always" in entry_text):
contradiction_found = True
break
critics["semantic"] = {
"pass": not contradiction_found,
"reason": "contradicts existing rock" if contradiction_found else "consistent"
}
# Overall verdict
all_pass = all(c["pass"] for c in critics.values())
verdict = "PASS" if all_pass else "REJECT"
return {
"verdict": verdict,
"entry_id": entry_id,
"critics": critics,
"reason": "all critics passed" if all_pass else "one or more critics failed"
}
# ═══════════════════════════════════════════════════════════════════════════════
# CLI
# ═══════════════════════════════════════════════════════════════════════════════
+1 -1
View File
@@ -6,7 +6,7 @@ This is the main entry point for the Rune program.
import json
from pathlib import Path
__version__ = "0.66.71"
__version__ = "0.86.0"
# One build identity (handoff 2026-09-10 §4): the semantic version above is
# the repo-level truth (auto-bumped at merge); the DEPLOYED revision (exact
+251
View File
@@ -0,0 +1,251 @@
"""behaviour_ledger.py — T19 §19F/19G/19I: the canonical Behaviour
Validation Ledger.
PRIMARY UNIT: the VERSIONED BEHAVIOUR / TEST SPECIFICATION — not "a
test run in some session". Automated verification and real-world/user
confirmation are DISTINCT truths (rule 19F): tests green ≠ user-
confirmed.
GENERATION-AWARE HISTORY (rule 19G, mandatory): when a dependency
changes, affected behaviours become RETEST_REQUIRED while their prior
confirmation history stays queryable — confidence does not silently
carry forward, and it does not silently disappear either.
SELECTIVE INVALIDATION (rule 19I): only behaviours whose structural
dependencies changed are invalidated; every invalidation carries a
reason; unrelated hardened confirmations are preserved.
Storage: <project_store>/behaviour_ledger.json (versioned document).
"""
from __future__ import annotations
import time
from pathlib import Path
LEDGER_SCHEMA_VERSION = 1
# hardening lifecycle (rule 19G semantics)
UNVALIDATED = "UNVALIDATED" # not yet validated
IMPLEMENTED = "IMPLEMENTED" # implementation exists
AUTO_VERIFIED = "AUTO_VERIFIED" # automated verification passed
REAL_WORLD_PASSED = "REAL_WORLD_PASSED" # manual/real test passed
TEST_FAILED = "TEST_FAILED" # test failed
PARTIAL = "PARTIAL" # inconclusive
USER_CONFIRMED = "USER_CONFIRMED" # hardened
RETEST_REQUIRED = "RETEST_REQUIRED" # confidence invalidated
ACCEPTED_LIMITATION = "ACCEPTED_LIMITATION" # explicitly approved gap
NOT_APPLICABLE = "NOT_APPLICABLE"
STATES = (UNVALIDATED, IMPLEMENTED, AUTO_VERIFIED, REAL_WORLD_PASSED,
TEST_FAILED, PARTIAL, USER_CONFIRMED, RETEST_REQUIRED,
ACCEPTED_LIMITATION, NOT_APPLICABLE)
# states that represent STANDING CONFIDENCE (selective invalidation
# may reopen exactly these; everything else is already open or n/a)
CONFIRMED_STATES = (AUTO_VERIFIED, REAL_WORLD_PASSED, USER_CONFIRMED)
def _now() -> str:
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
class BehaviourLedger:
def __init__(self, store_dir: Path | str):
self.path = Path(store_dir) / "behaviour_ledger.json"
self.doc = self._load()
def _load(self) -> dict:
if self.path.is_file():
import json
return json.loads(self.path.read_text())
return {"schema_version": LEDGER_SCHEMA_VERSION,
"behaviours": {}, "updated_at": _now()}
def save(self) -> None:
import json
self.doc["updated_at"] = _now()
self.path.parent.mkdir(parents=True, exist_ok=True)
self.path.write_text(json.dumps(self.doc, indent=1, sort_keys=True))
# -- specification ------------------------------------------------------
def define(self, *, behaviour_id: str, owning_project: str,
intended_behaviour: str, origin_intent: str = "",
pass_criteria: str = "", fail_criteria: str = "",
procedure: str = "",
owner_component: str = "",
dependencies: list[str] | None = None,
automated_ref: str = "",
environment: str = "") -> dict:
bid = (behaviour_id if behaviour_id.startswith("behaviour:")
else f"behaviour:{behaviour_id}")
if bid in self.doc["behaviours"]:
raise ValueError(f"behaviour exists: {bid}")
rec = {
"behaviour_id": bid,
"owning_project": owning_project,
"intended_behaviour": intended_behaviour,
"origin_intent": origin_intent,
"pass_criteria": pass_criteria,
"fail_criteria": fail_criteria,
"procedure": procedure,
"owner_component": owner_component, # structural entity id
"dependencies": list(dependencies or []), # structural ids
"automated_ref": automated_ref,
"environment": environment,
"state": UNVALIDATED,
"evidence": [], # [{at, generation, kind, detail}]
"history": [], # every state transition, never lost
"invalidations": [], # [{at, generation, because}]
"limitations": [],
"created_at": _now(),
"updated_at": _now(),
}
self.doc["behaviours"][bid] = rec
return rec
def get(self, behaviour_id: str) -> dict | None:
return self.doc["behaviours"].get(
behaviour_id if behaviour_id.startswith("behaviour:")
else f"behaviour:{behaviour_id}")
# -- state transitions (all recorded in history — rule 19G) -------------
def _transition(self, rec: dict, new_state: str, *, kind: str,
detail: str = "", generation: str = "") -> None:
if new_state not in STATES:
raise ValueError(f"unknown validation state {new_state!r}")
rec["history"].append({
"at": _now(), "from": rec["state"], "to": new_state,
"kind": kind, "detail": detail[:200],
"generation": generation})
rec["state"] = new_state
rec["updated_at"] = _now()
def record_auto_verification(self, behaviour_id: str, *,
passed: bool, detail: str = "",
generation: str = "") -> dict:
"""Automated verification. GREEN NEVER IMPLIES USER_CONFIRMED
(rule 19W-22): the ceiling here is AUTO_VERIFIED."""
rec = self.get(behaviour_id)
if rec is None:
raise ValueError(f"unknown behaviour {behaviour_id!r}")
rec["evidence"].append({"at": _now(), "kind": "automated",
"passed": passed,
"detail": detail[:200],
"generation": generation})
self._transition(rec, AUTO_VERIFIED if passed else TEST_FAILED,
kind="automated_verification", detail=detail,
generation=generation)
return rec
def record_real_world(self, behaviour_id: str, *, passed: bool,
detail: str = "", generation: str = "",
confirmed_by: str = "user") -> dict:
"""Real-world/manual test result (testing mode). USER_CONFIRMED
requires an explicit confirmed_by='user' pass (rule 19M)."""
rec = self.get(behaviour_id)
if rec is None:
raise ValueError(f"unknown behaviour {behaviour_id!r}")
rec["evidence"].append({"at": _now(), "kind": "real_world",
"passed": passed,
"detail": detail[:200],
"generation": generation,
"by": confirmed_by})
if passed:
state = (USER_CONFIRMED if confirmed_by == "user"
else REAL_WORLD_PASSED)
else:
state = TEST_FAILED
self._transition(rec, state, kind="real_world", detail=detail,
generation=generation)
return rec
def accept_limitation(self, behaviour_id: str, *, note: str,
approved_by: str = "user") -> dict:
rec = self.get(behaviour_id)
if rec is None:
raise ValueError(f"unknown behaviour {behaviour_id!r}")
rec["limitations"].append({"at": _now(), "note": note[:200],
"approved_by": approved_by})
self._transition(rec, ACCEPTED_LIMITATION, kind="accept_limitation",
detail=note, )
return rec
# -- selective invalidation (rule 19I) -----------------------------------
def invalidate_for_change(self, *, changed_entities: set[str],
generation: str = "",
reasons: dict[str, str] | None = None
) -> list[dict]:
"""Mark RETEST_REQUIRED exactly those confirmed behaviours whose
structural dependencies intersect the changed closure. Every
invalidation carries a reason; unrelated confirmations stay.
Returns the invalidated records."""
reasons = reasons or {}
invalidated = []
for rec in self.doc["behaviours"].values():
if rec["state"] not in CONFIRMED_STATES:
continue
overlap = set(rec.get("dependencies") or []) & changed_entities
if rec.get("owner_component") in changed_entities:
overlap.add(rec["owner_component"])
if not overlap:
continue # unrelated hardened behaviour: PRESERVED (19S-5)
because = reasons.get(
rec["behaviour_id"],
f"structural change touched: {sorted(overlap)}")
rec["invalidations"].append({
"at": _now(), "generation": generation,
"because": because[:200],
"was": rec["state"],
"changed_entities": sorted(overlap)})
# HISTORY IS PRESERVED — prior confirmation stays queryable
self._transition(rec, RETEST_REQUIRED,
kind="selective_invalidation",
detail=f"was {rec['state']}; {because}",
generation=generation)
invalidated.append(rec)
return invalidated
def retest_history(self, behaviour_id: str) -> list[dict]:
"""Generation-aware history: all confirmations + invalidations
for one behaviour, oldest first (rule 19S-12)."""
rec = self.get(behaviour_id)
if rec is None:
return []
events = []
for h in rec["history"]:
events.append({"at": h["at"], "generation": h.get("generation"),
"event": f"{h['from']}→{h['to']}",
"kind": h["kind"], "detail": h.get("detail", "")})
return events
# -- queue (testing mode, 19L/M) ------------------------------------------
def outstanding(self, *, component: str | None = None,
project: str | None = None) -> list[dict]:
"""Eligible testing items: anything not confirmed/accepted/n.a.
Dynamic — the queue re-derives from states after every fix
(rule 19M)."""
out = []
for rec in self.doc["behaviours"].values():
if rec["state"] in (USER_CONFIRMED, ACCEPTED_LIMITATION,
NOT_APPLICABLE):
continue
if project and rec.get("owning_project") != project:
continue
if component and rec.get("owner_component") != component:
continue
out.append(rec)
out.sort(key=lambda r: (0 if r["state"] == TEST_FAILED else 1,
r["behaviour_id"]))
return out
def summary(self) -> dict:
counts: dict[str, int] = {}
for rec in self.doc["behaviours"].values():
counts[rec["state"]] = counts.get(rec["state"], 0) + 1
return {"total": len(self.doc["behaviours"]),
"states": counts,
"outstanding": len(self.outstanding())}
+314
View File
@@ -0,0 +1,314 @@
"""build_basis.py — T18 §18C: durable versioned RESEARCH BASIS for
construction (V1 rule F: RESEARCH INFORMS STRUCTURE).
Research output must not disappear in transient model context. A Build
Basis is a durable, schema-versioned document that captures WHAT
research decided and WHY, with references — not research dumps. It is
created after the research-depth decision and before/with the
Construction Contract, and is attachable to:
- a Project Scaffold (project-level basis)
- a Construction Contract (per-contract basis, via evidence_ref)
- future structural entities where useful
Research Core ownership (rule C): this module NEVER researches. It
records the depth decision (derived FROM adaptive_router's route — no
second router), references the canonical pipeline's qualified output
(stages ISSUE→…→QUALIFY, artifacts in research/issue-<N>/), and
structures the adopt/adapt/combine/reject/innovate decisions (rule E).
"""
from __future__ import annotations
import time
from typing import Any
SCHEMA_VERSION = 1
# Research depth tiers — derived from adaptive_router ROUTES, never a
# second router (rule C/A). Each tier records WHY it was chosen.
DEPTH_NONE = "NONE" # tiny obvious local change; local evidence only
DEPTH_INWARD = "INWARD" # repo-internal understanding suffices
DEPTH_OUTWARD = "OUTWARD" # precedent/prior-art discovery required
DEPTH_FULL = "FULL" # full pipeline incl. adversarial+synthesis
DEPTH_TIERS = (DEPTH_NONE, DEPTH_INWARD, DEPTH_OUTWARD, DEPTH_FULL)
# route → default depth (recorded as decision evidence; caller may
# justify an exception, which is recorded as such — never silent)
_ROUTE_DEFAULT_DEPTH = {
"LOCAL_FIX": DEPTH_NONE,
"STANDARD_CHANGE": DEPTH_INWARD,
"RESEARCHED_CHANGE": DEPTH_FULL,
}
def research_depth(route: str, *, mode: str = "",
justification: str = "") -> dict:
"""The research-depth DECISION for a construction need. Derived from
the canonical route authority (adaptive_router); `justification` is
REQUIRED when deviating from the route default — recorded, never
silent (rule D: record why research depth was chosen)."""
default = _ROUTE_DEFAULT_DEPTH.get(route, DEPTH_INWARD)
depth = default
why = f"route {route} default"
if justification and justification.strip():
# an explicit justification selects a tier by name if given;
# otherwise it annotates the default
candidate = justification.strip().split(":", 1)[0].strip()
if candidate in DEPTH_TIERS:
depth = candidate
why = justification.strip()
else:
why = f"route {route} default; note: {justification.strip()}"
elif route not in _ROUTE_DEFAULT_DEPTH:
why = f"route {route!r} unknown — INWARD floor (never silent NONE)"
depth = DEPTH_INWARD
return {"depth": depth, "why": why, "route": route, "mode": mode,
"decided_at": _now()}
# OUTWARD precedent-first ordering contract (rule E): the questions the
# prior-art pass MUST answer before deeper material is consulted.
OUTWARD_PRIOR_ART_QUESTIONS = (
"what already exists addressing this intent",
"what open-source systems/products/workflows attempt it",
"what architectural patterns already work",
"what standards/conventions exist",
"what can be directly ADOPTED",
"what can be ADAPTED",
"what can be COMBINED",
"what should be REJECTED",
"where is genuine INNOVATION required",
)
_DISPOSITIONS = ("ADOPT", "ADAPT", "COMBINE", "REJECT", "INNOVATE")
def new_basis(*, question: str, depth_decision: dict,
issue_ref: str = "") -> dict:
"""Create an empty-but-valid Build Basis. References only — full
research dumps live in the Research Core's artifact dirs (rule 18C:
do not store unnecessary dumps)."""
return {
"schema_version": SCHEMA_VERSION,
"kind": "build_basis",
"created_at": _now(),
"question": question,
"issue_ref": issue_ref, # e.g. research/issue-164 or Gitea issue
"depth": depth_decision,
"inward": {"findings": [], "refs": []},
"outward": {"prior_art_questions_answered": [],
"findings": [], "refs": []},
"deeper": {"used": False, "findings": [], "refs": []},
"dispositions": [], # [{item, disposition: ADOPT/…, why, ref}]
"alternatives_considered": [],
"uncertainty": [],
"risks": [],
"implications": {"architecture": [], "structural": [],
"migration": [], "verification": []},
}
def set_dispositions(basis: dict, dispositions: list[dict]) -> list[str]:
"""Record the adopt/adapt/combine/reject/innovate decisions (rule E).
Returns validation errors; the basis stays unqualified until valid."""
basis["dispositions"] = [
{"item": d.get("item", ""), "disposition": d.get("disposition", ""),
"why": d.get("why", ""), "ref": d.get("ref", "")}
for d in dispositions
]
return validate_basis(basis)
def validate_basis(basis: dict) -> list[str]:
"""Qualification gate. A basis is QUALIFIED when it carries a depth
decision with why, at least one outward prior-art answer when depth
is OUTWARD/FULL, and every disposition uses a legal disposition."""
errors: list[str] = []
if basis.get("schema_version") != SCHEMA_VERSION:
errors.append(f"schema_version {basis.get('schema_version')!r}")
depth = (basis.get("depth") or {}).get("depth")
if depth not in DEPTH_TIERS:
errors.append(f"depth {depth!r} not in {DEPTH_TIERS}")
if not (basis.get("depth") or {}).get("why"):
errors.append("depth decision lacks WHY (rule D)")
if depth in (DEPTH_OUTWARD, DEPTH_FULL):
outward = basis.get("outward") or {}
if not (outward.get("prior_art_questions_answered")
or outward.get("findings") or outward.get("refs")):
errors.append("OUTWARD/FULL depth requires prior-art evidence")
if depth == DEPTH_INWARD:
inward = basis.get("inward") or {}
if not (inward.get("findings") or inward.get("refs")
or basis.get("dispositions")):
errors.append("INWARD depth requires recorded evidence "
"(findings/refs) — an empty basis is not "
"qualified")
for d in basis.get("dispositions", []):
if d.get("disposition") not in _DISPOSITIONS:
errors.append(f"illegal disposition {d.get('disposition')!r} "
f"for {d.get('item','')[:40]!r}")
return errors
def is_qualified(basis: dict) -> bool:
return not validate_basis(basis)
def attach_evidence_ref(basis: dict) -> str:
"""The evidence_ref string a contract carries to point at this basis
(durable pointer, not a copy — stores stay independent)."""
return (f"build_basis:{basis.get('issue_ref') or 'adhoc'}:"
f"{basis.get('created_at')}")
def summary(basis: dict) -> dict:
"""Compact projection for prompts/previews (no private reasoning —
structured conclusions only, rule 18G)."""
return {
"schema_version": basis.get("schema_version"),
"question": basis.get("question"),
"issue_ref": basis.get("issue_ref"),
"depth": basis.get("depth"),
"qualified": is_qualified(basis),
"validation": validate_basis(basis),
"dispositions": [
{"item": d.get("item", "")[:80],
"disposition": d.get("disposition")}
for d in basis.get("dispositions", [])
],
"uncertainty_count": len(basis.get("uncertainty", [])),
"risks_count": len(basis.get("risks", [])),
"implications": {k: len(v) for k, v in
(basis.get("implications") or {}).items()},
}
def _now() -> str:
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
def revise_depth(basis: dict, *, new_depth: str, reason: str) -> dict:
"""§21.1G: record a justified depth revision. Original + revised
depths both persist. An OUTWARD→INWARD/NONE downgrade requires
explicit resolution evidence ('discovery resolved', 'prior-art
resolved', 'unnecessary') — never a bare skip."""
if new_depth not in DEPTH_TIERS:
raise ValueError(f"unknown depth {new_depth!r}")
original = basis["depth"]["depth"]
if original == new_depth:
raise ValueError("no-op revision: depth unchanged")
consequential_origins = ("RESEARCHED_CHANGE", "FULL", "OUTWARD")
if original in consequential_origins and new_depth in ("NONE", "INWARD"):
keywords = ("resolved", "non-material", "discovery",
"unnecessary", "prior-art question")
if not any(k in reason.lower() for k in keywords):
raise ValueError(
"OUTWARD downgrade requires explicit evidence: the "
"prior-art question must be resolved as non-material "
"(not merely skipped)")
basis["depth_revision"] = {
"original_depth": original, "revised_depth": new_depth,
"reason": reason[:200], "revised_at": _now()}
basis["depth"]["depth"] = new_depth
basis["depth"]["why"] = f"revised from {original}: {reason}"
return basis["depth_revision"]
def to_json(basis: dict) -> str:
import json
return json.dumps(basis, indent=1, sort_keys=True)
def from_json(text: str) -> dict:
import json
return json.loads(text)
_ = Any # typing import kept for forward annotations
# ── T18 §18D CORRECTION (Nick, 2026-09-15): THE RESEARCH PLAN ITSELF IS
# CONTEXTUALLY GENERATED. The prior-art categories researched for the
# structural-model task (architecture graphs, monorepo tooling, etc.)
# were CONTEXTUAL to that task — they must never become a fixed topic
# list. For every request the plan is derived from:
# intent → target/current reality → material unknowns/decisions/risks
# → context-specific research topics → proportional depth.
# There is NO permanent research-topic checklist anywhere in Scaffolding.
def generate_research_plan(*, intent: str, target: str = "",
current_reality: str = "",
unknowns: list[str] | None = None,
decisions: list[str] | None = None,
risks: list[str] | None = None,
depth: str | None = None) -> dict:
"""Generate a CONTEXT-SPECIFIC research plan for one construction
intent. Topics are synthesized from the material unknowns/decisions/
risks of THIS intent — the function has no topic vocabulary and no
permanent category list. Depth, when not supplied, is chosen by
consequence heuristics recorded in the plan (why is always present)."""
unknowns = [u for u in (unknowns or []) if u.strip()]
decisions = [d for d in (decisions or []) if d.strip()]
risks = [r for r in (risks or []) if r.strip()]
topics: list[dict] = []
# each material unknown becomes a research topic aimed at resolving it
for u in unknowns:
topics.append({
"topic": f"resolve unknown: {u}",
"kind": "unknown-resolution",
"why": f"material unknown blocks confident construction: {u[:80]}",
})
for d in decisions:
topics.append({
"topic": f"inform decision: {d}",
"kind": "decision-support",
"why": f"consequential decision needs precedent/evidence: {d[:80]}",
})
for r in risks:
topics.append({
"topic": f"de-risk: {r}",
"kind": "risk-investigation",
"why": f"identified risk needs verification/mitigation evidence: {r[:80]}",
})
# precedent question (rule E) attaches ONLY when outward evidence is
# plausibly relevant — an unknown/decision about external prior art.
needs_outward = any(
w in " ".join(unknowns + decisions).lower()
for w in ("exist", "existing", "prior art", "precedent", "standard",
"convention", "library", "tool", "framework", "approach"))
if needs_outward:
topics.append({
"topic": ("what already exists that is relevant to THIS intent "
"(adopt/adapt/combine/reject/innovate)"),
"kind": "prior-art",
"why": "material unknown references existing practice — "
"precedent discovery required before deeper research",
})
if depth is None:
# consequence heuristics: novel/architectural/unsafe → deeper
consequential = any(
w in (intent + " " + " ".join(risks)).lower()
for w in ("architecture", "migration", "replace", "rewrite",
"security", "schema", "delete", "deploy", "breaking"))
depth = DEPTH_FULL if (consequential and topics) else (
DEPTH_OUTWARD if needs_outward and topics else (
DEPTH_INWARD if topics else DEPTH_NONE))
why = ("consequence heuristics: consequential keywords + research "
"topics present" if depth == DEPTH_FULL else
"precedent-relevant unknowns present" if depth == DEPTH_OUTWARD
else "local understanding suffices" if depth == DEPTH_INWARD
else "no material unknowns — local evidence only")
else:
why = "depth supplied by caller (recorded, caller accountable)"
return {
"intent": intent,
"target": target,
"current_reality_note": current_reality[:200],
"depth": {"depth": depth, "why": why},
"topics": topics,
"plan_generated_at": _now(),
# the plan ITSELF is evidence that research was contextual, not
# checklist-driven: two different intents must produce disjoint
# topic sets unless they share genuine unknowns
}
+1 -1
View File
@@ -231,7 +231,7 @@ def main() -> int:
)
check(
"Rune minor version advertises provisioning and restore verification",
'__version__ = "0.66.71"' in rune_version,
'__version__ = "0.86.0"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
+405
View File
@@ -0,0 +1,405 @@
"""capability_ledger.py — T19 §19D: compact versioned Capability
Ledgers — local re-entry context packets.
PURPOSE (rule 19D): a fresh LLM rehydrates the meaning of a target area
after compaction/context loss/model swap/long absence WITHOUT rereading
the repository. Not a second source tree, not a second Scaffold, not
prose dumps — structured, proportional, operationally-used records.
Identity rule: capability IDs are STABLE across file moves (name-based,
not path-based); history is preserved on supersede/retire (rule 19Q).
Storage: <project_store>/capability_ledger.json — versioned document,
provenance-aware (rule G). Historical versions remain traceable via
the history list.
"""
from __future__ import annotations
import time
from pathlib import Path
LEDGER_SCHEMA_VERSION = 1
MATURITY = ("proposed", "implemented", "verified", "hardened",
"retest_required", "retired", "superseded")
def _now() -> str:
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
class CapabilityLedger:
def __init__(self, store_dir: Path | str):
self.path = Path(store_dir) / "capability_ledger.json"
self.doc = self._load()
def _load(self) -> dict:
if self.path.is_file():
import json
return json.loads(self.path.read_text())
return {"schema_version": LEDGER_SCHEMA_VERSION,
"capabilities": {}, "history": [],
"updated_at": _now()}
def save(self) -> None:
import json
self.doc["updated_at"] = _now()
self.path.parent.mkdir(parents=True, exist_ok=True)
self.path.write_text(json.dumps(self.doc, indent=1, sort_keys=True))
# -- lifecycle --------------------------------------------------------
def define(self, *, capability_id: str, name: str, purpose: str,
owning_project: str, component: str,
source_locations: list[str] | None = None,
contract: str = "", state_touched: str = "",
dependencies: list[str] | None = None,
consumers: list[str] | None = None,
invariants: list[str] | None = None,
behaviour_specs: list[str] | None = None,
constraints: str = "",
generation: str = "") -> dict:
"""Create a capability record (auto-invoked by Scaffolding when a
meaningful capability is created — rule 19Q)."""
cid = f"capability:{capability_id}"
if cid in self.doc["capabilities"]:
raise ValueError(f"capability exists: {cid}")
rec = {
"capability_id": cid,
"name": name,
"purpose": purpose,
"owning_project": owning_project,
"component": component, # structural entity id
"source_locations": list(source_locations or []),
"contract": contract,
"state_touched": state_touched,
"dependencies": list(dependencies or []),
"consumers": list(consumers or []),
"invariants": list(invariants or []),
"behaviour_specs": list(behaviour_specs or []),
"maturity": "implemented",
"constraints": constraints,
"generation": generation,
"limitations": [],
"created_at": _now(),
"updated_at": _now(),
}
self.doc["capabilities"][cid] = rec
return rec
def get(self, capability_id: str) -> dict | None:
return self.doc["capabilities"].get(
capability_id if ":" in capability_id
else f"capability:{capability_id}")
def material_change(self, capability_id: str, *, note: str,
generation: str = "",
source_locations: list[str] | None = None) -> dict:
"""Record a material change (auto-invoked after construction —
rule 19Q.2). History entry keeps traceability; dependents' harden
state is the behaviour ledger's job (via structural closure)."""
rec = self.get(capability_id)
if rec is None:
raise ValueError(f"unknown capability {capability_id!r}")
# T21.1 §21.1D: deterministic revision counter. Wall-clock
# timestamps are metadata, not causal ordering (same-second events
# share a stamp — the T21 race). The revision counter is the
# canonical drift signal: material_revision > last governed
# spec_revision = SPEC_VERSION_DRIFT.
rev = int(rec.get("material_revision", 0)) + 1
rec["material_revision"] = rev
hist = {"at": _now(), "event": "material_change",
"note": note[:200], "generation": generation,
"prior_maturity": rec["maturity"],
"material_revision": rev}
self.doc["history"].append({"capability_id": rec["capability_id"],
**hist})
rec["updated_at"] = _now()
if generation:
rec["generation"] = generation
if source_locations is not None:
rec["source_locations"] = list(source_locations)
return rec
def move(self, capability_id: str, new_locations: list[str]) -> dict:
"""File move: STABLE IDENTITY SURVIVES (rule 19S-6); the move is
recorded, history preserved."""
rec = self.get(capability_id)
if rec is None:
raise ValueError(f"unknown capability {capability_id!r}")
old = list(rec["source_locations"])
rec["source_locations"] = list(new_locations)
rec["updated_at"] = _now()
self.doc["history"].append({
"capability_id": rec["capability_id"], "at": _now(),
"event": "moved", "from": old, "to": list(new_locations)})
return rec
def supersede(self, capability_id: str, by: str, *,
note: str = "") -> dict:
rec = self.get(capability_id)
if rec is None:
raise ValueError(f"unknown capability {capability_id!r}")
rec["maturity"] = "superseded"
rec["superseded_by"] = (by if ":" in by else f"capability:{by}")
rec["updated_at"] = _now()
self.doc["history"].append({
"capability_id": rec["capability_id"], "at": _now(),
"event": "superseded", "by": rec["superseded_by"],
"note": note[:160]})
return rec
def retire(self, capability_id: str, *, note: str = "") -> dict:
rec = self.get(capability_id)
if rec is None:
raise ValueError(f"unknown capability {capability_id!r}")
rec["maturity"] = "retired"
rec["updated_at"] = _now()
self.doc["history"].append({
"capability_id": rec["capability_id"], "at": _now(),
"event": "retired", "note": note[:160]})
return rec
# -- queries (surgical context, 19E) -----------------------------------
def active(self) -> list[dict]:
return [r for r in self.doc["capabilities"].values()
if r["maturity"] not in ("retired", "superseded")]
def by_component(self, component: str) -> list[dict]:
return [r for r in self.active() if r["component"] == component]
def context_packet(self, capability_id: str) -> dict:
"""The LOCAL context packet (19E): everything a fresh builder
needs for one capability — bounded, structured, no reasoning."""
rec = self.get(capability_id)
if rec is None:
return {"error": f"unknown capability {capability_id!r}"}
return {
"schema_version": LEDGER_SCHEMA_VERSION,
"capability_id": rec["capability_id"],
"name": rec["name"],
"purpose": rec["purpose"],
"component": rec["component"],
"source_locations": rec["source_locations"],
"contract": rec["contract"],
"state_touched": rec["state_touched"],
"dependencies": rec["dependencies"],
"consumers": rec["consumers"],
"invariants": rec["invariants"],
"behaviour_specs": rec["behaviour_specs"],
"maturity": rec["maturity"],
"constraints": rec["constraints"],
"generation": rec["generation"],
"limitations": rec["limitations"],
}
# ── T21 §21C/21D: LIVING CAPABILITY SPECIFICATIONS + STRICT SEMVER ──────
#
# A Living Capability Spec is the WHY+WHAT-SHOULD-EXIST layer; the
# capability record remains the WHAT-EXISTS+WHERE layer. Both live in the
# SAME ledger document (rule C: no second authority) — the spec is a
# versioned field-set on the capability, not a separate subsystem.
#
# STRICT SEMVER (§21D): only meaningful units get versions.
# PATCH: bug fix / internal change, contract compatible
# MINOR: backwards-compatible new capability/behaviour
# MAJOR: breaking contract/interface/state-semantics change
# Baselines for existing capabilities: explicit
# governance_version_started_at + baseline_reason; no fake history.
SPEC_SCHEMA_VERSION = 1
import re as _re
SEMVER_RE = _re.compile(r"^(\d+)\.(\d+)\.(\d+)$")
class SpecError(ValueError):
"""Invalid spec operation (material change without bump, unjustified
bump, bad version...)."""
def classify_version_bump(*, contract_changed: bool,
behaviour_added: bool,
consumer_break: bool,
implementation_only: bool) -> str:
"""§21D-3: derive PATCH/MINOR/MAJOR from the change shape. The model
may propose; the machinery verifies."""
if consumer_break:
return "MAJOR"
if contract_changed:
# contract changed but no consumer break: interface tightened —
# still breaking for consumers relying on the old shape
return "MAJOR"
if behaviour_added:
return "MINOR"
if implementation_only:
return "PATCH"
raise SpecError("no version-worthy change: metadata churn does not bump")
def bump_version(version: str, kind: str) -> str:
m = SEMVER_RE.match(version)
if not m:
raise SpecError(f"invalid semver {version!r}")
major, minor, patch = (int(x) for x in m.groups())
if kind == "MAJOR":
return f"{major + 1}.0.0"
if kind == "MINOR":
return f"{major}.{minor + 1}.0"
if kind == "PATCH":
return f"{major}.{minor}.{patch + 1}"
raise SpecError(f"unknown bump kind {kind!r}")
def attach_spec(ledger: "CapabilityLedger", capability_id: str, *,
requirements: list[str],
expected_behaviours: list[str],
non_goals: list[str],
acceptance_criteria: list[str],
baseline_version: str = "0.1.0",
baseline_reason: str = "initial governed baseline",
governance_version_started_at: str | None = None) -> dict:
"""Attach a Living Capability Specification to an existing capability
record (rule 21C: extend, don't duplicate). Baseline policy (§21D-1):
the starting version is explicit — 1.0.0 only for stable contracts
with sufficient evidence; otherwise 0.x with a reason."""
from rune.capability_ledger import LEDGER_SCHEMA_VERSION # noqa: F401
rec = ledger.get(capability_id)
if rec is None:
raise ValueError(f"unknown capability {capability_id!r}")
if not SEMVER_RE.match(baseline_version):
raise SpecError(f"invalid baseline semver {baseline_version!r}")
spec = {
"schema_version": SPEC_SCHEMA_VERSION,
"version": baseline_version,
"governance_version_started_at": governance_version_started_at
or _now(),
"baseline_reason": baseline_reason,
"requirements": requirements,
"expected_behaviours": expected_behaviours,
"non_goals": non_goals,
"acceptance_criteria": acceptance_criteria,
"version_history": [{
"at": _now(), "version": baseline_version, "kind": "BASELINE",
"reason": baseline_reason, "changed_sections": [],
"affected_consumers": [], "validation_consequences": [],
}],
}
rec["spec"] = spec
rec["updated_at"] = _now()
return spec
def revise_spec(ledger: "CapabilityLedger", capability_id: str, *,
contract_changed: bool = False,
behaviour_added: bool = False,
consumer_break: bool = False,
implementation_only: bool = False,
reason: str,
changed_sections: list[str] | None = None,
affected_consumers: list[str] | None = None,
validation_consequences: list[str] | None = None,
proposed_kind: str | None = None) -> dict:
"""§21D-3/21E: revise a spec with an AUTOMATIC version decision.
The model proposes; the machinery classifies and records. Material
change without a bump is impossible (every revise computes one);
unjustified bumps are impossible (no-change revise raises)."""
rec = ledger.get(capability_id)
if rec is None:
raise ValueError(f"unknown capability {capability_id!r}")
spec = rec.get("spec")
if spec is None:
raise SpecError(f"{capability_id} has no spec — attach one first")
kind = (proposed_kind or classify_version_bump(
contract_changed=contract_changed,
behaviour_added=behaviour_added,
consumer_break=consumer_break,
implementation_only=implementation_only))
# verify a PROPOSED kind against the change shape (model cannot
# upgrade PATCH to MINOR without the corresponding change shape)
actual = classify_version_bump(
contract_changed=contract_changed,
behaviour_added=behaviour_added,
consumer_break=consumer_break,
implementation_only=implementation_only)
order = {"PATCH": 0, "MINOR": 1, "MAJOR": 2}
if proposed_kind and order[proposed_kind] > order[actual]:
raise SpecError(
f"proposed {proposed_kind} exceeds change shape {actual} — "
f"unjustified bump")
old = spec["version"]
new = bump_version(old, kind)
spec["version"] = new
spec["version_history"].append({
"at": _now(), "version": new, "kind": kind,
"reason": reason[:200],
"changed_sections": list(changed_sections or []),
"affected_consumers": list(affected_consumers or []),
"validation_consequences": list(validation_consequences or []),
# T21.1 §21.1D: the governed decision COVERS all material
# revisions up to and including the current one — drift closes
# deterministically, not by timestamp comparison
"covers_material_revision": int(rec.get("material_revision", 0)),
})
rec["updated_at"] = _now()
return {"old": old, "new": new, "kind": kind, "reason": reason}
def spec_drift_status(ledger: "CapabilityLedger",
capability_id: str) -> dict:
"""T21.1 §21.1D: the callable drift detector / consistency gate.
Deterministic and revision-based (never wall-clock):
material_revision > last governed covers_material_revision
→ SPEC_VERSION_DRIFT + durable obligation
Returns {"drift": bool, "obligation": str|None, "detail": ...}."""
rec = ledger.get(capability_id)
if rec is None:
return {"drift": False, "obligation": None,
"detail": f"unknown capability {capability_id!r}"}
spec = rec.get("spec")
if spec is None:
return {"drift": False, "obligation": None,
"detail": "no spec attached — nothing to drift from"}
hist = spec.get("version_history") or []
covered = hist[-1].get("covers_material_revision", 0) if hist else 0
current = int(rec.get("material_revision", 0))
if current > covered:
return {
"drift": True,
"obligation": (
f"SPEC_VERSION_DRIFT: {capability_id} has "
f"{current - covered} uncovered material revision(s) "
f"(material rev {current} > governed rev {covered}) — "
f"run revise_spec (PATCH/MINOR/MAJOR) to close"),
"detail": {"material_revision": current,
"governed_revision": covered,
"spec_version": spec.get("version")},
}
return {"drift": False, "obligation": None,
"detail": {"material_revision": current,
"governed_revision": covered,
"spec_version": spec.get("version")}}
def spec_context(rec: dict) -> dict:
"""The spec half of a surgical context packet (§21M): WHY + WHAT
SHOULD EXIST, compact."""
s = rec.get("spec") or {}
return {
"capability_id": rec["capability_id"],
"version": s.get("version"),
"purpose": rec.get("purpose"),
"requirements": s.get("requirements", []),
"expected_behaviours": s.get("expected_behaviours", []),
"non_goals": s.get("non_goals", []),
"acceptance_criteria": s.get("acceptance_criteria", []),
"contract": rec.get("contract"),
"invariants": rec.get("invariants", []),
"behaviour_specs": rec.get("behaviour_specs", []),
"last_bump": (s.get("version_history") or [{}])[-1],
}
-602
View File
@@ -1,602 +0,0 @@
#!/usr/bin/env python3
"""complexity_scorer.py — Auto-detection of task complexity for build mode.
Scores user requests to determine if they need:
- Task mode (0-3): direct execution
- Task mode with planning (4-7): add planning step
- Build mode (8+): full pipeline
Research basis:
- No existing tool does auto-detection (SWE-agent, Aider, Devin, Cursor, OpenCode)
- Simple keyword scoring: zero cost for 90% of requests, LLM fallback for 10%
- Agent reliability degrades 96% → 34% from 8K to 128K tokens (LOCA-bench, ICML 2026)
- Single-file changes: ~80% success vs <50% for 4-6 files (SWE-bench)
Usage:
complexity_scorer.py score "add user auth with JWT, update API routes, add tests"
complexity_scorer.py mode "fix the typo in README"
"""
import json
import os
import re
import sys
from dataclasses import dataclass, field, asdict
from typing import List, Dict, Any, Optional
# ═══════════════════════════════════════════════════════════════════════════════
# SIGNAL DEFINITIONS
# ═══════════════════════════════════════════════════════════════════════════════
# Request analysis signals (regex, zero cost)
CONJUNCTIONS = ["and", "then", "also", "additionally", "after that", "plus", "as well as"]
CROSS_SYSTEM_REFS = [
"api",
"frontend",
"backend",
"database",
"tests",
"config",
"ci",
"deploy",
"ui",
"cli",
"server",
"client",
"model",
"view",
]
ARCH_KEYWORDS = [
"refactor",
"migrate",
"redesign",
"pipeline",
"system",
"architecture",
"overhaul",
"restructure",
"reorganize",
"consolidate",
]
NEW_ABSTRACTION_KEYWORDS = [
"create a new",
"build a new",
"implement a new",
"add a new",
"new module",
"new system",
"new feature",
"new component",
"new service",
"new layer",
]
SCOPE_MODIFIERS = ["entire", "all", "every", "throughout", "comprehensive", "complete", "full", "whole", "across"]
EXT_DEPENDENCY_KEYWORDS = [
"integrate",
"third-party",
"library",
"package",
"sdk",
"api",
"service",
"webhook",
"external",
]
COMPLEXITY_MARKERS = [
"complex",
"big change",
"major",
"significant",
"extensive",
"substantial",
"non-trivial",
"involved",
]
SIMPLE_FIX_INDICATORS = [
"fix typo",
"rename",
"update comment",
"change color",
"add import",
"remove import",
"fix whitespace",
"format",
"lint",
"bump version",
"update readme",
"fix comment",
]
# Build mode trigger keywords (high confidence)
BUILD_TRIGGERS = [
"implement",
"build",
"create feature",
"refactor",
"migrate",
"across multiple files",
"new system",
"new module",
"overhaul",
"redesign",
"restructure",
"full pipeline",
"end-to-end",
]
# Task mode trigger keywords
TASK_TRIGGERS = [
"fix",
"change",
"update",
"rename",
"bug",
"typo",
"error",
"crash",
"broken",
"not working",
"issue",
"patch",
"hotfix",
]
# Research mode trigger keywords
RESEARCH_TRIGGERS = [
"how does",
"what is",
"explain",
"investigate",
"explore",
"research",
"understand",
"analyze",
"study",
"review",
]
# Plan mode trigger keywords
PLAN_TRIGGERS = [
"plan",
"design",
"architecture",
"before we start",
"approach",
"strategy",
"roadmap",
"blueprint",
"spec",
]
# ═══════════════════════════════════════════════════════════════════════════════
# SCORING FUNCTIONS
# ═══════════════════════════════════════════════════════════════════════════════
@dataclass
class ScoreBreakdown:
"""Detailed breakdown of complexity score."""
request_analysis: int = 0
codebase_impact: int = 0
historical_patterns: int = 0
risk_assessment: int = 0
total: int = 0
signals: List[str] = field(default_factory=list)
mode: str = "task"
confidence: str = "high" # high, medium, low
def analyze_request_text(request: str) -> tuple[int, List[str]]:
"""Analyze request text for complexity signals. Zero cost, <1ms."""
score = 0
signals = []
request_lower = request.lower()
# Multiple verbs/conjunctions
conjunction_matches = [c for c in CONJUNCTIONS if c in request_lower]
if len(conjunction_matches) >= 2:
score += 2
signals.append(f"Multiple conjunctions: {', '.join(conjunction_matches)}")
elif len(conjunction_matches) == 1:
score += 1
signals.append(f"Conjunction: {conjunction_matches[0]}")
# Cross-system references
system_matches = [s for s in CROSS_SYSTEM_REFS if s in request_lower]
if len(system_matches) >= 2:
score += 3
signals.append(f"Cross-system: {', '.join(system_matches)}")
elif len(system_matches) == 1:
score += 1
signals.append(f"System ref: {system_matches[0]}")
# Architecture keywords
arch_matches = [k for k in ARCH_KEYWORDS if k in request_lower]
if arch_matches:
score += 2
signals.append(f"Architecture: {', '.join(arch_matches)}")
# New abstraction keywords
new_matches = [k for k in NEW_ABSTRACTION_KEYWORDS if k in request_lower]
if new_matches:
score += 2
signals.append(f"New abstraction: {', '.join(new_matches)}")
# Scope modifiers
scope_matches = [s for s in SCOPE_MODIFIERS if s in request_lower]
if scope_matches:
score += 1
signals.append(f"Scope: {', '.join(scope_matches)}")
# External dependency mentions
ext_matches = [k for k in EXT_DEPENDENCY_KEYWORDS if k in request_lower]
if ext_matches:
score += 1
signals.append(f"External dep: {', '.join(ext_matches)}")
# Explicit complexity markers
complex_matches = [k for k in COMPLEXITY_MARKERS if k in request_lower]
if complex_matches:
score += 3
signals.append(f"Complexity marker: {', '.join(complex_matches)}")
# Simple fix indicators (negative score)
simple_matches = [k for k in SIMPLE_FIX_INDICATORS if k in request_lower]
if simple_matches:
score -= 3
signals.append(f"Simple fix: {', '.join(simple_matches)}")
return score, signals
def estimate_codebase_impact(request: str, repo_path: str = "/mnt/workdrive/rune") -> tuple[int, List[str]]:
"""Estimate codebase impact using grep/glob. ~100ms."""
score = 0
signals = []
# Extract potential module/function names from request
words = re.findall(r"\b[a-zA-Z_][a-zA-Z0-9_]*\b", request)
# Filter out common words
stop_words = {
"the",
"a",
"an",
"and",
"or",
"but",
"in",
"on",
"at",
"to",
"for",
"of",
"with",
"by",
"from",
"is",
"are",
"was",
"were",
"be",
"been",
"being",
"have",
"has",
"had",
"do",
"does",
"did",
"will",
"would",
"could",
"should",
"may",
"might",
"can",
"shall",
"that",
"which",
"who",
"whom",
"this",
"these",
"those",
"it",
"its",
"my",
"your",
"his",
"her",
"our",
"their",
"me",
"you",
"him",
"us",
"them",
"i",
"we",
"he",
"she",
"they",
"what",
"where",
"when",
"how",
"why",
"if",
"then",
"else",
"so",
"just",
"also",
"very",
"too",
"much",
"many",
"some",
"any",
"all",
"each",
"every",
"both",
"few",
"more",
"most",
"other",
"another",
"such",
"no",
"not",
"only",
"own",
"same",
"than",
"too",
"now",
"here",
"there",
"about",
"up",
"out",
"off",
"over",
"under",
"again",
"further",
"then",
"once",
"new",
"add",
"update",
"fix",
"change",
"make",
"create",
"build",
"implement",
}
potential_modules = [w for w in words if w.lower() not in stop_words and len(w) > 3]
# Check if any potential modules exist in codebase
if potential_modules:
try:
import subprocess
# Quick grep for potential module names
grep_pattern = "|".join(potential_modules[:5]) # Limit to 5 to avoid slow grep
result = subprocess.run(
["grep", "-r", "-l", "-E", grep_pattern, os.path.join(repo_path, "src")],
capture_output=True,
text=True,
timeout=5,
)
if result.returncode == 0:
files = [f for f in result.stdout.strip().split("\n") if f]
if len(files) > 5:
score += 3
signals.append(f"Files likely affected: {len(files)}")
elif len(files) > 2:
score += 1
signals.append(f"Files possibly affected: {len(files)}")
except Exception:
pass
# Check for cross-module dependencies
if "src/rune" in repo_path:
try:
import subprocess
result = subprocess.run(
["grep", "-r", "-c", "import", os.path.join(repo_path, "src/rune")],
capture_output=True,
text=True,
timeout=5,
)
if result.returncode == 0:
import_count = sum(
int(line.split(":")[-1])
for line in result.stdout.strip().split("\n")
if ":" in line and line.split(":")[-1].strip().isdigit()
)
if import_count > 50:
score += 2
signals.append(f"High import density: {import_count} imports")
except Exception:
pass
return score, signals
def check_historical_patterns(request: str) -> tuple[int, List[str]]:
"""Check memory for similar past tasks. Returns score and signals."""
score = 0
signals = []
# Check if similar tasks needed build mode
try:
import sys
sys.path.insert(0, os.path.expanduser("~/.config/opencode/scripts"))
import memory_store
db = memory_store.connect()
try:
results = memory_store.cached_recall(db, f"build mode {request}", budget=3)
if results:
for r in results:
text = r.get("text", "").lower()
if "build mode" in text or "multi-step" in text or "complex" in text:
score += 2
signals.append("Similar past task needed build mode")
break
finally:
db.close()
except Exception:
pass
return score, signals
def assess_risk(request: str, repo_path: str = "/mnt/workdrive/rune") -> tuple[int, List[str]]:
"""Assess risk based on file paths and content."""
score = 0
signals = []
request_lower = request.lower()
# Auth/security code
auth_keywords = ["auth", "security", "token", "credential", "password", "login", "session"]
if any(k in request_lower for k in auth_keywords):
score += 2
signals.append("Touches auth/security code")
# Data layer
data_keywords = ["database", "migration", "schema", "sql", "sqlite", "model", "orm"]
if any(k in request_lower for k in data_keywords):
score += 1
signals.append("Touches data layer")
# Public API
api_keywords = ["api", "endpoint", "route", "export", "interface", "sdk", "public"]
if any(k in request_lower for k in api_keywords):
score += 2
signals.append("Touches public API")
# Reversibility concern
destructive_keywords = ["delete", "remove", "drop", "destroy", "clear", "reset", "wipe"]
if any(k in request_lower for k in destructive_keywords):
score += 1
signals.append("Reversibility concern")
return score, signals
# ═══════════════════════════════════════════════════════════════════════════════
# MAIN SCORING
# ═══════════════════════════════════════════════════════════════════════════════
def score_request(request: str, repo_path: str = "/mnt/workdrive/rune") -> ScoreBreakdown:
"""Score a request for complexity. Returns ScoreBreakdown with total and mode."""
breakdown = ScoreBreakdown()
# Signal Category 1: Request Analysis (zero cost, <1ms)
req_score, req_signals = analyze_request_text(request)
breakdown.request_analysis = req_score
breakdown.signals.extend(req_signals)
# Signal Category 2: Codebase Impact (~100ms)
impact_score, impact_signals = estimate_codebase_impact(request, repo_path)
breakdown.codebase_impact = impact_score
breakdown.signals.extend(impact_signals)
# Signal Category 3: Historical Patterns
hist_score, hist_signals = check_historical_patterns(request)
breakdown.historical_patterns = hist_score
breakdown.signals.extend(hist_signals)
# Signal Category 4: Risk Assessment
risk_score, risk_signals = assess_risk(request, repo_path)
breakdown.risk_assessment = risk_score
breakdown.signals.extend(risk_signals)
# Calculate total
breakdown.total = (
breakdown.request_analysis
+ breakdown.codebase_impact
+ breakdown.historical_patterns
+ breakdown.risk_assessment
)
# Determine mode
if breakdown.total >= 8:
breakdown.mode = "build"
breakdown.confidence = "high"
elif breakdown.total >= 4:
breakdown.mode = "task_with_planning"
breakdown.confidence = "medium"
else:
breakdown.mode = "task"
breakdown.confidence = "high"
# Check for explicit mode triggers
request_lower = request.lower()
if any(t in request_lower for t in BUILD_TRIGGERS):
if breakdown.mode != "build":
breakdown.mode = "build"
breakdown.confidence = "high"
breakdown.signals.append("Explicit build trigger detected")
elif any(t in request_lower for t in RESEARCH_TRIGGERS):
breakdown.mode = "research"
breakdown.confidence = "high"
breakdown.signals.append("Research trigger detected")
elif any(t in request_lower for t in PLAN_TRIGGERS):
breakdown.mode = "plan"
breakdown.confidence = "high"
breakdown.signals.append("Plan trigger detected")
return breakdown
def get_mode(request: str, repo_path: str = "/mnt/workdrive/rune") -> str:
"""Get the recommended mode for a request. Returns mode string."""
breakdown = score_request(request, repo_path)
return breakdown.mode
def get_score(request: str, repo_path: str = "/mnt/workdrive/rune") -> Dict[str, Any]:
"""Get detailed score breakdown. Returns dict."""
breakdown = score_request(request, repo_path)
return asdict(breakdown)
# ═══════════════════════════════════════════════════════════════════════════════
# CLI
# ═══════════════════════════════════════════════════════════════════════════════
def main():
if len(sys.argv) < 3:
print("Usage: complexity_scorer.py [score|mode] <request>")
sys.exit(1)
cmd = sys.argv[1]
request = " ".join(sys.argv[2:])
if cmd == "score":
result = get_score(request)
print(json.dumps(result, indent=2))
elif cmd == "mode":
mode = get_mode(request)
print(mode)
else:
print(f"Unknown command: {cmd}")
sys.exit(1)
if __name__ == "__main__":
main()
+70 -2
View File
@@ -140,12 +140,14 @@ def activate(mode_decision, *, need, scaffold: sc.Scaffold, builder: str = "rune
}
)
ps.commit(pdoc, what=f"PROJECT_BUILD bootstrap: {project}")
awareness = _auto_structural_awareness(project, ps, scaffold)
rec.update(
{
"activated": True,
"lifecycle": "project_build",
"project": project,
"project_scaffold": str(ps.current),
"structural_awareness": awareness,
"bootstrap": "initialized",
"repository": "undecided",
"toolchain": "unresolved",
@@ -161,9 +163,75 @@ def activate(mode_decision, *, need, scaffold: sc.Scaffold, builder: str = "rune
return rec
def _auto_structural_awareness(project: str, project_scaffold: sc.Scaffold,
parent_scaffold: sc.Scaffold) -> dict:
"""T21.5 §9 (rule 19Q family): governed adoption/construction expands
Rune's persistent structural world-model AUTOMATICALLY — no manual
Registry/Spine bookkeeping step. Boundaries preserved:
- Registry = discovery/index (that the project exists, where its
canonical Scaffold lives) — never project internals;
- Spine = Rune's relationship TO the project (one edge, builds/
manages vocabulary) — project modules stay in the Project
Scaffold (never duplicated into Spine);
- Project Scaffold remains canonical for the project's internals.
Failure-tolerant: awareness must not break the bootstrap (recorded
in the return, not raised)."""
result: dict = {"registry": "skipped", "spine": "skipped"}
try:
from rune.project_registry import ProjectRegistry
store_dir = parent_scaffold.current.parent
registry = ProjectRegistry(store_dir)
if registry.get(project) is None:
registry.register(
project_id=project,
scaffold_location=str(project_scaffold.current.parent),
repo_identity="", # undecided at bootstrap (identity
# before repository — never invented)
)
registry.save()
result["registry"] = "registered"
except (OSError, ValueError) as exc:
result["registry"] = f"error: {exc!s:.120}"
try:
from rune import spine as spine_mod
spine_path = parent_scaffold.current.parent.parent / "spine" / "current.json"
sdoc = spine_mod.load(spine_path)
if sdoc is not None:
spine_mod.add_relationship(
sdoc, type="manages",
from_id="project:rune", to_id=f"project:{project}",
evidence_refs=[f"project-build-bootstrap:{project}"])
spine_mod.save(sdoc, spine_path)
result["spine"] = "recorded"
except (OSError, ValueError) as exc:
result["spine"] = f"error: {exc!s:.120}"
return result
def dispatch_from_decision(trace: dict, *, need, scaffold: sc.Scaffold, builder: str = "rune") -> dict:
"""§15: read the mode from a make_decision trace and dispatch. The
shared Construction Context then derives from the SAME canonical state
— no duplicate machines."""
mode = trace.get("construction_mode", {}).get("mode", "UNKNOWN")
return activate(mode, need=need, scaffold=scaffold, builder=builder)
mode_decision = trace.get("mode_decision")
if mode_decision is None:
# T21.5 fix: activate() expects the mode_decision object (with
# .mode/.target_project/.evidence); the trace carries its dict
# form, so reconstruct a ConstructionMode from it.
from rune.construction_mode import ConstructionMode
md = trace.get("construction_mode", {}) or {}
if md.get("mode", "UNKNOWN") == "UNKNOWN":
md = dict(md)
md["mode"] = "UNKNOWN"
mode_decision = ConstructionMode(
mode=md.get("mode", "UNKNOWN"),
evidence=list(md.get("evidence", [])),
target_project=md.get("target_project"),
project_authority=md.get("project_authority"),
confidence=md.get("confidence", 0.0),
clarification_required=md.get("clarification_required", False),
probe_questions=list(md.get("probe_questions", [])),
bootstrap_required=md.get("bootstrap_required", False),
override=md.get("override"),
proposed_by_model=md.get("proposed_by_model"),
)
return activate(mode_decision, need=need, scaffold=scaffold, builder=builder)
+122 -14
View File
@@ -154,9 +154,33 @@ _ANALYSIS_MARKERS = re.compile(
r"\b(think|opinion|view|recommend|suggest|assess|evaluate|review|"
r"compare|consider|worth it|worth it\?|needs? improvement|any "
r"ideas?)\b", re.IGNORECASE)
_RELATIVE_THAT = re.compile(
r"\bthat\s+(?!the\b|a\b|an\b|is\b|was\b|will\b|can\b|should\b)"
r"[a-z]+(?:s|ing)\b")
# relative-'that' detection by PRECEDING context (T18 18N fix): 'that'
# is a relative pronoun when the word before it is an article/noun
# ("the proposal that you suggested") — demonstrative when
# sentence-initial or after a verb/punctuation ("build that" /
# "that works for me"). The old following-word heuristic misclassified
# "that works for me" as a relative clause.
_ARTICLES = {"the", "a", "an", "this", "these", "those", "one"}
def _is_relative_that(low: str, dem_tokens: set) -> bool:
"""True when every 'that' token reads as a relative pronoun by its
preceding context; demonstrative otherwise."""
words = re.findall(r"[a-z']+", low)
seen_that = False
for i, w in enumerate(words):
if w != "that":
continue
seen_that = True
prev = words[i - 1] if i > 0 else None
if prev is None or prev in _CONSTRUCTION_VERBS:
return False # sentence-initial or post-verb → demonstrative
if prev not in _ARTICLES:
# preceding word is neither article nor verb: treat as
# demonstrative unless it is clearly a noun modifier —
# conservative default is demonstrative (referent-seeking)
return False
return seen_that and "that" in dem_tokens
_GO_AHEAD = re.compile(
r"^\s*(yes|yeah|yep|ok|okay|sure|go ahead|please do|do it|do that|"
@@ -166,14 +190,23 @@ _AFFIRMATION_OPENERS = re.compile(
r"alright|right)\b", re.IGNORECASE)
_APPROVAL_IDIOM = re.compile(
r"\b(go ahead|do it|do that|carry on|build that|implement that|"
r"make it so|make it happen|that's the one|go with|let's go|"
r"let's do|proceed with|approved|ship it)\b", re.IGNORECASE)
r"make it so|make it happen|make that happen|that's the one|go with|"
r"let's go|let's do|let's have it|proceed|good with that|"
r"fine with that|happy with that|on board|approved|ship it)\b",
re.IGNORECASE)
def _tokens(text: str) -> set[str]:
return set(re.split(r"[^a-z0-9]+", text.lower()))
_NEGATION = re.compile(
r"^\s*(don't|do not|dont|never|not yet|stop|hold off|no need|"
r"skip it|forget it)\b", re.IGNORECASE)
_IDIOM_PREFIX = re.compile(
r"^\s*(go ahead and|go and|please|kindly|then|now)\s+", re.IGNORECASE)
def _is_question(text: str) -> bool:
"""Interrogative STRUCTURE, not vocabulary: leading wh/auxiliary or
terminal '?'. 'How would you build this?' is a question regardless
@@ -187,8 +220,16 @@ def _is_question(text: str) -> bool:
def _imperative_verb(text: str) -> str | None:
"""Leading imperative construction verb — the strong/direct demand
signal (§17K). 'Build me a program...' → 'build'."""
m = re.match(r"^\s*(\w+)", text, re.IGNORECASE)
signal (§17K). 'Build me a program...' → 'build'. Idiom prefixes
('go ahead and build X', 'please implement X') are stripped so the
real verb surfaces."""
t = text
for _ in range(3):
t2 = _IDIOM_PREFIX.sub("", t)
if t2 == t:
break
t = t2
m = re.match(r"^\s*(\w+)", t, re.IGNORECASE)
if m and m.group(1).lower() in _CONSTRUCTION_VERBS:
return m.group(1).lower()
return None
@@ -221,6 +262,13 @@ def resolve_construction_intent(
ev: list[str] = []
probes: list[str] = []
# 0. NEGATION (§18N): "don't build that yet" blocks authorization —
# construction language inside a negation frame is NOT a demand.
if _NEGATION.match(t):
ev.append("negation frame — explicitly blocks construction")
return _intent(t, "DISCUSSION", "DISCUSSION", 0.85, "NONE",
ev, probes, context, origin_context_id)
# 1a. REQUEST MODALS: "can you fix that?" is an imperative in
# interrogative clothing — a state-changing action request, NOT an
# information question. Information openers (how/what/why/should we)
@@ -237,6 +285,27 @@ def resolve_construction_intent(
if request_modal:
ev.append("request modal (can/could/would you, please) + "
"construction verb → action request, not discussion")
# a modal + demonstrative + live proposal authorizes THAT
# proposal (referent resolution outranks target resolution)
dem_tokens_m = _tokens(low) & _DEMONSTRATIVES
if dem_tokens_m and context.live_proposals():
referent = _resolve_referent(low, context)
if referent and not referent.get("ambiguous"):
tp = referent["proposal"]
ev.append(f"modal authorization resolves proposal "
f"{tp.proposal_id}")
return ConstructionIntent(
intent_id=f"intent-{uuid.uuid4().hex[:8]}",
objective=tp.summary,
requested_effect=f"construct: {tp.summary}",
resolved_target=context.active_project,
resolved_referent=tp.proposal_id,
origin_context_id=origin_context_id,
action_intent="ACTION", approval_state="USER_APPROVED",
confidence=0.85, ambiguity="NONE",
consequence_level=_consequence(t, context),
relevant_constraints=_constraints(context),
classification_evidence=ev)
target = context.active_project
if target:
ev.append(f"target resolved from active context: {target}")
@@ -278,7 +347,7 @@ def resolve_construction_intent(
bare_approval = bool(_GO_AHEAD.match(t))
imperative = _imperative_verb(t)
dem_tokens = _tokens(low) & _DEMONSTRATIVES
if "that" in dem_tokens and _RELATIVE_THAT.search(low):
if "that" in dem_tokens and _is_relative_that(low, dem_tokens):
dem_tokens.discard("that") # relative clause, not a demonstrative
ev.append("'that' is a relative pronoun here, not a referent")
demonstrative = bool(dem_tokens) or \
@@ -294,12 +363,30 @@ def resolve_construction_intent(
if authorized:
referent = _resolve_referent(low, context)
if referent is None:
ev.append("authorization language but NO live resolvable "
"referent — never guessed")
probes.append("what should that refer to? I don't have an "
"active proposal matching it.")
return _intent(t, "ACTION", "DISCUSSION", 0.4, "REFERENT",
ev, probes, context, origin_context_id)
# "go ahead and build X": idiom + explicit imperative — fall
# through to the demand path (target resolution) rather than
# dead-ending in referent ambiguity
if imperative is None:
ev.append("authorization language but NO live resolvable "
"referent — never guessed")
probes.append("what should that refer to? I don't have an "
"active proposal matching it.")
return _intent(t, "ACTION", "DISCUSSION", 0.4, "REFERENT",
ev, probes, context, origin_context_id)
ev.append("no live proposal matches; treating as direct "
"demand (explicit imperative object present)")
if referent is None:
# live proposals exist but are ALL STALE (topic moved on) —
# never approve a stale referent; fall to the demand path
ev.append("proposals stale for this turn — not selected")
if imperative is None:
probes.append("what should that refer to? The earlier "
"proposal no longer seems active.")
return _intent(t, "ACTION", "DISCUSSION", 0.4, "REFERENT",
ev, probes, context, origin_context_id)
ev.append("stale proposals ignored; explicit imperative → "
"demand path")
referent = {"__fallthrough__": True}
if referent.get("ambiguous"):
ev.append("multiple equally plausible referents — asking, "
"not guessing (§17M)")
@@ -307,6 +394,27 @@ def resolve_construction_intent(
p.summary[:40] for p in referent["candidates"]) + "?")
return _intent(t, "ACTION", "DISCUSSION", 0.4, "REFERENT",
ev, probes, context, origin_context_id)
if referent.get("__fallthrough__"):
# demand path: target from context or clarify (never stale)
target = context.active_project
if not target and imperative in _CREATION_VERBS and \
_NEW_ARTICLE.search(low):
# new standalone artifact demand — no target needed
ev.append("creation verb + indefinite article → new "
"standalone artifact; no existing target required")
return _intent(t, "ACTION", "USER_APPROVED", 0.85, "NONE",
ev, probes, context, origin_context_id,
requested_effect=f"construct new: {t}",
consequence_level=_consequence(t, context))
if target:
ev.append(f"target resolved from active context: {target}")
return _intent(t, "ACTION", "USER_APPROVED", 0.8, "NONE",
ev, probes, context, origin_context_id,
requested_effect=f"construct: {t}",
consequence_level=_consequence(t, context))
probes.append("build where — which project/system?")
return _intent(t, "ACTION", "DISCUSSION", 0.5, "TARGET",
ev, probes, context, origin_context_id)
target_prop = referent["proposal"]
ev.append(f"referent resolved: {target_prop.proposal_id} "
f"(recency-first among live proposals)")
+58
View File
@@ -669,12 +669,16 @@ class ConstructionInterface:
errors=[{"error": Errors.STORE_WRITE_FAILED, "detail": "contract vanished after close"}],
)
q = closed["ext"]["contract_quality"]
material_closure = self._auto_material_change(
scope_files or [],
note=f"contract {contract_id} closed (material change)")
return _envelope(
"close",
success=True,
ids={"contract_id": contract_id},
state={
"phase": "closed",
"material_change_closure": material_closure,
"contract_quality": {
k: q[k]
for k in ("correctly_predicted", "expected_new_observed", "genuinely_unexpected_new", "scope_check")
@@ -683,6 +687,60 @@ class ConstructionInterface:
},
)
def _auto_material_change(self, scope_files: list[str], *,
note: str) -> dict:
"""T21.4 §21.4H (rule 19Q.2): governed construction closes → the
material-change/revision flow fires AUTOMATICALLY. Every closed
contract's scope_files are matched against the owning project's
capability ledger source_locations; matched capabilities get
material_change() (revision increment + history). Files matching
NO modeled capability are returned as unmodeled — a coverage
signal, never silently swallowed (spec/version decision remains
the operator's, but the revision evidence is already durable)."""
changed: list[str] = []
unmodeled = list(scope_files)
try:
# ledger resolution: project-local ledger first (a project
# scaffold's own capability ledger), then the store-root
# rune_model (T21.5 fix: projects/<name> scaffolds must walk
# UP to the store root, not sideways into projects/).
candidates = [
self.scaffold.store_dir / "rune_model",
self.scaffold.store_dir.parent / "rune_model",
self.scaffold.store_dir.parent.parent / "rune_model",
# project scaffold (store/scaffold/projects/<p>) → store root
self.scaffold.store_dir.parent.parent.parent / "rune_model",
]
ledger_file = next(
(d / "capability_ledger.json" for d in candidates
if (d / "capability_ledger.json").is_file()), None)
if ledger_file is None:
return {"changed": [], "unmodeled": unmodeled,
"note": "no capability ledger for this project"}
from rune.capability_ledger import CapabilityLedger
ledger = CapabilityLedger(ledger_file.parent)
def _match(cap: dict, f: str) -> bool:
for loc in cap.get("source_locations", []):
if f == loc or f.endswith("/" + loc) or loc.endswith("/" + f):
return True
return False
for cap in ledger.doc["capabilities"].values():
hits = [f for f in scope_files if _match(cap, f)]
if hits:
ledger.material_change(
cap["capability_id"], note=f"{note}; scope: {hits[0]}",
generation=self.scaffold.project_id)
changed.append(cap["capability_id"])
unmodeled = [f for f in unmodeled if f not in hits]
if changed:
ledger.save()
except (OSError, ValueError) as exc: # closure must not break close
return {"changed": changed, "unmodeled": unmodeled,
"note": f"material-change closure error: {exc!s:.160}"}
return {"changed": changed, "unmodeled": unmodeled}
@staticmethod
def discover_active_contract(store_path: str) -> dict:
"""§15U canonical resume discovery: walk the store's project
+3
View File
@@ -187,6 +187,9 @@ class DriftCache:
# ═══════════════════════════════════════════════════════════════════════════════
import os
SCRIPTS_DIR = os.environ.get("RUNE_SCRIPTS_DIR", os.path.join(os.path.dirname(__file__), "scripts"))
def main():
import argparse
-57
View File
@@ -350,63 +350,6 @@ def pre_commit_gate_entry(entry: dict) -> dict:
"critics": critics,
"reason": "all critics passed" if all_pass else "one or more critics failed",
}
row = db.execute(
"SELECT id, text, importance, wash_count, rock_status, topic FROM entries WHERE id = ?", (entry_id,)
).fetchone()
if not row:
return {"verdict": "REJECT", "reason": "entry not found"}
entry = dict(row)
critics = {}
# Critic 1: Structural validity
text = entry.get("text", "")
if len(text) < 5:
critics["structural"] = {"pass": False, "reason": "text too short"}
elif len(text) > 2000:
critics["structural"] = {"pass": False, "reason": "text too long"}
elif not text.strip():
critics["structural"] = {"pass": False, "reason": "empty text"}
else:
critics["structural"] = {"pass": True, "reason": "well-formed"}
# Critic 2: Behavioral harmlessness (check against immutable core)
# The immutable core: user authority, no safety theater, no fights
harmful_patterns = ["refuse to", "cannot comply", "safety violation", "I'm sorry, but I can't"]
is_harmful = any(p in text.lower() for p in harmful_patterns)
critics["behavioral"] = {
"pass": not is_harmful,
"reason": "contains refusal language" if is_harmful else "aligned with core",
}
# Critic 3: Semantic consistency (check for contradictions with existing rocks)
rocks = db.execute("SELECT text FROM entries WHERE rock_status = 'rock' AND status = 'active' LIMIT 50").fetchall()
contradiction_found = False
for rock in rocks:
rock_text = rock[0].lower()
entry_text = text.lower()
# Simple contradiction detection: if entry says opposite of a rock
if ("always" in rock_text and "never" in entry_text) or ("never" in rock_text and "always" in entry_text):
contradiction_found = True
break
critics["semantic"] = {
"pass": not contradiction_found,
"reason": "contradicts existing rock" if contradiction_found else "consistent",
}
# Overall verdict
all_pass = all(c["pass"] for c in critics.values())
verdict = "PASS" if all_pass else "REJECT"
return {
"verdict": verdict,
"entry_id": entry_id,
"critics": critics,
"reason": "all critics passed" if all_pass else "one or more critics failed",
}
# ═══════════════════════════════════════════════════════════════════════════════
# CLI
# ═══════════════════════════════════════════════════════════════════════════════
+45
View File
@@ -71,3 +71,48 @@ def reconcile(claims: list[dict], observer: Callable[[dict], dict]) -> dict:
blocking = [d for d in drifts if d["class"] in BLOCKING_CLASSES]
return {"resume": not blocking, "drifts": drifts,
"blocking": blocking}
# ── T18 §18H: structural drift detection (extends drift_resume; no
# duplicate mechanism). Detects observable drift between a structural
# model's claims and repository reality. REALITY WINS unless user-locked.
def detect_structural_drift(model, repo_root) -> list[dict]:
"""Compare structural-model component claims against source reality.
Observable cases: source file moved/missing (component identities
carry 'source'), entities for files that no longer exist. Returns
drift records in the classify_drift vocabulary; reality wins unless
the claim is user-locked (authority == 'user' on the entity's
evidence or identities.user_locked)."""
import os
from pathlib import Path as _P
root = _P(repo_root)
drifts = []
for e in model.doc["entities"]:
if e.get("kind") != "component":
continue
src = ((e.get("ext") or {}).get("source")
or (e.get("identities") or {}).get("source"))
if not src:
continue
locked = (e.get("identities") or {}).get("user_locked") is True
expected = root / src
if not expected.exists():
# reality: file gone/moved — search one level for a moved twin
moved = None
if expected.name:
for cand in root.rglob(expected.name):
if cand.is_file():
moved = str(cand.relative_to(root))
break
drifts.append({
"claim": f"{e['id']} source at {src}",
"class": "CONFLICTING" if locked else "MATERIAL",
"evidence": (f"source missing; moved to {moved}"
if moved else "source file absent from repo"),
"reality_wins": not locked,
"reconciliation": (f"update source to {moved}" if moved
else "entity requires re-derivation or "
"user-locked preservation"),
})
return drifts
+69 -9
View File
@@ -95,12 +95,33 @@ for attempt in range(3):
def wr_root() -> Path:
env = os.environ.get("RUNE_WR_ROOT")
if env:
return Path(env)
if DEFAULT_WR_ROOT.exists():
return DEFAULT_WR_ROOT
return FALLBACK_WR_ROOT
"""Resolve the bridge DATA root — DELEGATED (T21.5 §7) to the single
shared resolver (scripts/wr_root_resolver.py): explicit env →
canonical production root → legacy home; split-brain between live
stores fails loudly, never silently operates two stores."""
resolver_path = (Path(__file__).resolve().parent.parent.parent
/ "scripts" / "wr_root_resolver.py")
try:
spec = importlib.util.spec_from_file_location(
"wr_root_resolver", str(resolver_path))
if spec is None or spec.loader is None:
raise RuntimeError("no spec/loader")
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod.resolve_root()
except (OSError, RuntimeError) as exc:
# resolver unavailable (partial checkout): keep the documented
# policy rather than failing the wait path
sys.stderr.write(f"event_wait: resolver fallback: {exc}\n")
env = os.environ.get("RUNE_WR_ROOT")
if env:
return Path(env)
shared = Path("/mnt/workdrive/wr-bridge")
if shared.exists():
return shared
if DEFAULT_WR_ROOT.exists():
return DEFAULT_WR_ROOT
return FALLBACK_WR_ROOT
# waitstore instances cached per data root (isolated tests use per-test
@@ -150,7 +171,31 @@ def register_process_wait(operation_id: str, *, contract_id: str = "",
provider: str = "builder",
event_type: str = "builder_exit",
sha: str = "",
watchdog_secs: int | None = None) -> dict:
watchdog_secs: int | None = None,
continuation_state: str = "WAITING_EXTERNAL",
op_type: str = "",
authority: str = "",
task_id: str = "",
resume_mode: str = "fifo") -> dict:
"""E-PATCH §4: the PendingOperation IS the durable continuation object.
continuation_state is first-class cognitive quiescence:
ACTIVE_REASONING | WAITING_EXTERNAL | RESUMABLE | RECOVERY_REQUIRED
op_type/authority record the CANONICAL execution semantics (derived by
intent normalization, never model-supplied). task_id/project bind the
operation to the current programme so fresh-process recovery can
resolve relevance without the model reconstructing state.
resume_mode owns the SESSION-DELIVERY domain (REAL_ASYNC_BRIDGE §2/§3):
"fifo" — legacy delivery: the daemon writes a resume token to the
operation's FIFO on event match (T15 semantics; the waiter
blocks reading the FIFO). Default preserves T15 builder
behavior.
"self" — the waiter process itself blocks on the durable completion
file (inotify) and stamps its own terminal operation state
on authority verification. NO resume_cmd is registered: the
daemon must never dispatch a delivery worker for this wait,
and a dead reader cannot downgrade operation truth."""
"""Register a durable PROCESS_EVENT wait for one operation. The
per-wait resume_cmd writes a resume token to the operation's FIFO —
the bridge can resume a Python controller, not just MiMo sessions.
@@ -175,6 +220,13 @@ def register_process_wait(operation_id: str, *, contract_id: str = "",
"fd=os.open(sys.argv[1], os.O_WRONLY | os.O_NONBLOCK);"
"os.write(fd, json.dumps({'wait_id': sys.argv[2],"
" 'resumed_at': time.time()}).encode());os.close(fd)")
if resume_mode == "self":
# REAL_ASYNC_BRIDGE §3: no session-delivery worker. The waiter
# self-resumes on the durable completion file; the daemon's only
# roles are event persistence and failure-detection sweeps.
resume_cmd = None
else:
resume_cmd = ["python3", "-c", writer, str(fifo), "{wait_id}"]
rec = ws.new_wait(
session_id=f"controller-{operation_id}",
provider=provider,
@@ -191,12 +243,20 @@ def register_process_wait(operation_id: str, *, contract_id: str = "",
"independent verification",
on_success="continue programme continuation canary",
on_failure="classify failure; recovery or block",
resume_cmd=["python3", "-c", writer, str(fifo), "{wait_id}"],
resume_cmd=resume_cmd,
)
rec["ext"] = {"contract_id": contract_id, "operation_id": operation_id,
"fifo": str(fifo),
"watchdog_secs": watchdog_secs
or int(os.environ.get("WR_WATCH_SECS", "600"))}
or int(os.environ.get("WR_WATCH_SECS", "600")),
"continuation_state": continuation_state,
"op_type": op_type, "authority": authority,
"task_id": task_id,
# §3 delivery-domain marker: the daemon drive loop skips
# waits marked "self" (the waiter owns its continuation);
# records WITHOUT the marker remain eligible for the
# env-template session-delivery path (WR_RESUME_CMD).
"resume_mode": resume_mode}
ws.save(rec)
# cross-user bridge (T16 §16C live finding): the daemon reconciler may
# run under a different account than the registering controller — the
+146
View File
@@ -0,0 +1,146 @@
"""forge.py — T18 §18L: minimum stable forge abstraction boundary.
Rune today is Gitea-centric; Scaffolding must not make Gitea itself part
of canonical construction semantics. This module defines the boundary:
Git remains the source-history authority; a Forge adapter handles the
remote-services surface (push/CI status/PR/protected checks/release
metadata). GiteaForge is the real working adapter, delegating to the
existing gitea_auth + scripts (rule A: no duplicate implementation).
A future Forgejo/GitHub/GitLab adapter implements the same interface
without changing Scaffolding's core model.
"""
from __future__ import annotations
from typing import Protocol
class ForgeAdapter(Protocol):
"""The minimum stable forge boundary. All methods may raise
ForgeUnavailable — callers treat forge failures as blocking-for-that-
operation, never as construction-truth changes."""
def push(self, repo: str, branch: str) -> dict: ...
def commit_status(self, repo: str, sha: str) -> str: ...
def ci_runs_for_sha(self, repo: str, sha: str) -> list[dict]: ...
def create_or_patch_release(self, repo: str, tag: str,
title: str, body: str) -> dict: ...
class ForgeUnavailable(RuntimeError):
"""The forge could not be reached or refused the operation."""
class GiteaForge:
"""Real adapter. Delegates to the existing canonical implementations
(gitea_auth credential ladder; deploy_qualify verdict logic) — no
duplicated API clients (rule A)."""
def __init__(self, server: str | None = None, repo: str = "conductor/rune"):
self._server = server # None → scripts' env/credential resolution
self._repo = repo
def _env(self) -> dict:
import os
env = dict(os.environ)
if self._server:
env["GITEA_SERVER"] = self._server
env.setdefault("GITEA_REPO", self._repo)
return env
def push(self, repo: str, branch: str) -> dict:
import subprocess
proc = subprocess.run(["git", "push", "gitea", f"master:{branch}"],
capture_output=True, text=True)
if proc.returncode != 0:
raise ForgeUnavailable(f"push failed: {proc.stderr[:200]}")
return {"pushed": True, "branch": branch}
@staticmethod
def _gitea_auth():
"""Canonical scripts/gitea_auth credential ladder (rule A: no
duplicate auth). scripts/ is not a package — path shim, the
established spec-loaded-scripts pattern."""
import sys
from pathlib import Path as _P
scripts = _P(__file__).resolve().parents[2] / "scripts"
if str(scripts) not in sys.path:
sys.path.insert(0, str(scripts))
import gitea_auth
return gitea_auth
def commit_status(self, repo: str, sha: str) -> str:
import json
import os
import urllib.request
gitea_auth = self._gitea_auth()
server = self._server or os.environ.get(
"GITEA_SERVER", "https://gitea.thingscouldgetdicey.online")
if not gitea_auth.has_token():
raise ForgeUnavailable("no forge credentials (gitea token)")
req = urllib.request.Request(
f"{server}/api/v1/repos/{repo}/commits/{sha}/status",
headers={"Authorization": f"token {gitea_auth.get_token()}"})
try:
with urllib.request.urlopen(req, timeout=15) as r:
data = json.loads(r.read())
except OSError as exc:
raise ForgeUnavailable(f"status unreachable: {exc}") from exc
return data.get("state", "unknown")
def ci_runs_for_sha(self, repo: str, sha: str) -> list[dict]:
import json
import os
import urllib.request
gitea_auth = self._gitea_auth()
server = self._server or os.environ.get(
"GITEA_SERVER", "https://gitea.thingscouldgetdicey.online")
if not gitea_auth.has_token():
raise ForgeUnavailable("no forge credentials (gitea token)")
req = urllib.request.Request(
f"{server}/api/v1/repos/{repo}/actions/runs?limit=50",
headers={"Authorization": f"token {gitea_auth.get_token()}"})
try:
with urllib.request.urlopen(req, timeout=15) as r:
data = json.loads(r.read())
except OSError as exc:
raise ForgeUnavailable(f"runs unreachable: {exc}") from exc
return [run for run in data.get("workflow_runs", [])
if str(run.get("head_sha", "")) == sha]
def create_or_patch_release(self, repo: str, tag: str,
title: str, body: str) -> dict:
import json
import os
import urllib.request
gitea_auth = self._gitea_auth()
server = self._server or os.environ.get(
"GITEA_SERVER", "https://gitea.thingscouldgetdicey.online")
if not gitea_auth.has_token():
raise ForgeUnavailable("no forge credentials (gitea token)")
# delegate to the canonical idempotent publisher
import subprocess
import sys
script = None
for cand in ("scripts/create_release.py",):
if (sys.path and __import__("pathlib").Path(cand).is_file()):
script = cand
break
if script:
proc = subprocess.run(
[sys.executable, script, "--tag", tag, "--title", title,
"--body", body], capture_output=True, text=True)
if proc.returncode != 0:
raise ForgeUnavailable(
f"release publication failed rc={proc.returncode}: "
f"{(proc.stderr or proc.stdout)[-200:]}")
return {"published": True, "tag": tag}
raise ForgeUnavailable("canonical release publisher not found")
def default_forge() -> ForgeAdapter:
"""The working adapter (rule 18L: prove Gitea still works)."""
return GiteaForge()
+108
View File
@@ -0,0 +1,108 @@
"""graph_projection.py — T18 §18Q: stable versioned READ-ONLY graph
projection from a structural Scaffold, suitable for later local-web
visualisation, GUI, TUI tree, or programmatic queries.
This is a PROJECTION — never another authority (rule 18Q). Structural
truth remains the Scaffold (structural.json) / Spine. Built THROUGH the
self-scaffolded upgrade (contract 18q-graph-projection): the upgrade
started from the maintained Scaffold, per the 18P proof.
Formats: versioned JSON (programmatic), DOT and Mermaid (visual —
dependency-cruiser-proven human-usable formats per the 18D research).
"""
from __future__ import annotations
PROJECTION_SCHEMA_VERSION = 1
def project_json(model) -> dict:
"""Stable versioned JSON projection: nodes + edges, no internal
storage layout leaked (Substrate-safe seam, rule 18E)."""
nodes = []
for e in model.doc["entities"]:
nodes.append({
"id": e["id"],
"kind": e.get("kind"),
"maturity": e.get("maturity"),
"name": (e.get("identities") or {}).get("name"),
"ext": {k: v for k, v in (e.get("ext") or {}).items()
if k in ("source", "bounded_adapter")},
})
edges = []
for r in model.doc["relationships"]:
edges.append({
"type": r.get("type"),
"from": r.get("from"),
"to": r.get("to"),
"authority": r.get("authority"),
"status": r.get("status"),
})
return {
"schema_version": PROJECTION_SCHEMA_VERSION,
"project": getattr(model, "project_id", ""),
"generated_at": _now(),
"node_count": len(nodes),
"edge_count": len(edges),
"nodes": nodes,
"edges": edges,
}
def project_dot(model) -> str:
"""Graphviz DOT — clusters per domain, edges labeled by type."""
lines = ["digraph structural {", " rankdir=LR;",
' node [shape=box, style="rounded,filled", fillcolor="#eef"];']
for e in model.doc["entities"]:
eid = e["id"].replace('"', "'")
label = (e.get("identities") or {}).get("name") or \
e["id"].split(":", 1)[-1]
lines.append(f' "{eid}" [label="{label} ({e["kind"]})"];')
for r in model.doc["relationships"]:
if r["type"] == "belongs_to" and r.get("status") == "root":
continue
a = str(r["from"]).replace('"', "'")
b = str(r["to"]).replace('"', "'")
lines.append(f' "{a}" -> "{b}" [label="{r["type"]}"];')
lines.append("}")
return "\n".join(lines)
def project_mermaid(model) -> str:
"""Mermaid flowchart — renders on GitHub/GitLab/many viewers."""
lines = ["graph LR"]
ids = {e["id"] for e in model.doc["entities"]}
safe = {}
for i, eid in enumerate(sorted(ids)):
safe[eid] = f"n{i}"
for e in model.doc["entities"]:
label = ((e.get("identities") or {}).get("name")
or e["id"].split(":", 1)[-1]).replace('"', "'")
lines.append(f' {safe[e["id"]]}["{label} ({e["kind"]})"]')
for r in model.doc["relationships"]:
if r["type"] == "belongs_to" and r.get("status") == "root":
continue
if r["from"] in safe and r["to"] in safe:
lines.append(f' {safe[r["from"]]} -->|{r["type"]}| '
f'{safe[r["to"]]}')
return "\n".join(lines)
def write_all(model, out_dir) -> dict:
"""Write all three formats; returns paths. Read-only over truth —
writing projections never mutates the model."""
import json
from pathlib import Path
out = Path(out_dir)
out.mkdir(parents=True, exist_ok=True)
j = out / "structural_graph.json"
d = out / "structural_graph.dot"
mm = out / "structural_graph.mmd"
j.write_text(json.dumps(project_json(model), indent=1, sort_keys=True))
d.write_text(project_dot(model))
mm.write_text(project_mermaid(model))
return {"json": str(j), "dot": str(d), "mermaid": str(mm)}
def _now() -> str:
import time
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
+69
View File
@@ -0,0 +1,69 @@
"""impact_preview.py — T18 §18G: visible change-impact preview.
For consequential changes, a user-visible STRUCTURED projection of what
a construction will touch — BEFORE build. Derived from the structural
Scaffold's impact closure + the Build Basis summary. Contains
structured conclusions/evidence only — NEVER private chain-of-thought
(rule 18G). This is a projection, not an authority.
"""
from __future__ import annotations
PREVIEW_SCHEMA_VERSION = 1
def build_preview(model, closure: dict, *, basis: dict | None = None,
contract_scope: list[str] | None = None) -> dict:
"""Assemble the preview from an impact_closure (structural_model)
and optionally the Build Basis summary. Pure derivation — no hidden
reasoning, no mutation."""
if closure.get("error"):
return {"schema_version": PREVIEW_SCHEMA_VERSION,
"error": closure["error"]}
scope = set(closure.get("mutation_scope", []))
observation = closure.get("observation_scope") or {}
deps = observation.get("direct_dependencies", [])
# candidate files: identities' source paths if modeled
candidate_files = []
for eid in sorted(scope):
e = model.get(eid) or {}
src = (e.get("identities") or {}).get("source")
if src:
candidate_files.append(src)
return {
"schema_version": PREVIEW_SCHEMA_VERSION,
"target": closure.get("target"),
"affected_domains": ([closure.get("owning_domain")]
if closure.get("owning_domain") else []),
"affected_subsystems": sorted(
e for e in scope if str(e).startswith("subsystem:")),
"consumers_connectors": sorted(
e for e in scope if e != closure.get("target")),
"candidate_files": sorted(set(candidate_files))[:50],
"contract_scope_additions": sorted(
set(contract_scope or []) - set(candidate_files)),
"state_implications": closure.get("state_surfaces", []),
"environment_runtime_implications": sorted(
model.deployment_surfaces(closure["target"])
if model.get(closure["target"]) else []),
"migration_implications": (basis or {}).get("implications",
{}).get("migration", []),
"risks_uncertainties": [
*(basis or {}).get("risks", []),
*closure.get("uncertainty", []),
],
"verification_plan": closure.get("verification_scope", []),
"rollback_implications": [
"revert contract scope commits; deployment rollbacks via "
"last-good symlink (canonical deploy machinery)"],
"basis_summary": (basis or {}).get("depth"),
}
def validate_preview(preview: dict) -> list[str]:
errs = []
if preview.get("schema_version") != PREVIEW_SCHEMA_VERSION:
errs.append("schema_version mismatch")
for f in ("target", "verification_plan", "risks_uncertainties"):
if f not in preview:
errs.append(f"missing {f}")
return errs
+190
View File
@@ -0,0 +1,190 @@
"""iteration.py — T19 §19E/19H/19K: the canonical dependency-aware
iteration engine — the "quicksand" solution as STANDARD Scaffolding
behaviour.
Canonical loop (rule 19H):
intent → target → research/Build Basis → scaffold reconcile →
PREDICTIVE impact → bounded mutation scope + wider observation
scope → preview → isolated construction → verification →
DESCRIPTIVE impact → predicted-vs-observed delta → structural
truth update → selective validation invalidation → obligations →
iterate → harden.
Built ON the T18 foundation (rule C): structural_model.impact_closure,
build_basis, impact_preview, drift_resume — no second anything.
"""
from __future__ import annotations
import time
from pathlib import Path
from rune.capability_ledger import CapabilityLedger
from rune.behaviour_ledger import BehaviourLedger
def _now() -> str:
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
# ── 19E: surgical context packets ────────────────────────────────────────
def context_packet(model, ledger: CapabilityLedger, target: str, *,
intent: str = "", basis_summary: dict | None = None,
max_local_files: int = 12) -> dict:
"""LOCAL + CONNECTED context for one structural target (rule 19E).
Bounded and proportional: the whole graph is NEVER dumped. Local =
the target's own ledger entry; connected = 1-hop structural
neighbours with their ledgers on demand."""
entity = model.get(target)
if entity is None:
return {"error": f"unknown structural target {target!r}"}
caps = ledger.by_component(target)
local = ledger.context_packet(caps[0]["capability_id"]) if caps else None
beneath = [b for b in model.beneath(target)
if not model.get(b).get("identities", {}).get("source")]
files: list[str] = []
for eid in [target] + model.children(target):
e = model.get(eid) or {}
src = (e.get("ext") or {}).get("source") or \
(e.get("identities") or {}).get("source")
if src:
files.append(src)
for cap in caps:
files.extend(cap.get("source_locations", []))
closure = model.impact_closure(target)
# interface-mediated consumers: consumers of the interfaces this
# target provides (1-hop through the connector, still bounded)
iface_consumers = sorted({c for iface in model.interfaces_for(target)
for c in model.consumers(iface)
if c != target})
connected = {
"dependencies": model.dependencies(target),
"consumers": sorted(set(model.consumers(target)) |
set(iface_consumers)),
"interface_mediated_consumers": iface_consumers,
"interfaces_provided": model.interfaces_for(target),
"state_owned": model.state_owned(target),
"verification": model.verification_surfaces(target),
"deployment": model.deployment_surfaces(target),
"uncertainty": closure.get("uncertainty", []),
}
return {
"schema_version": 1,
"intent": intent,
"target": target,
"target_kind": entity.get("kind"),
"owning_domain": model.owning_domain(target),
"local": local,
"local_files": sorted(set(files))[:max_local_files],
"beneath_count": len(beneath),
"connected": connected,
"basis_summary": basis_summary,
"generated_at": _now(),
}
# ── 19H: predictive vs descriptive impact ────────────────────────────────
def predictive_impact(model, target: str) -> dict:
"""What MAY be affected BEFORE the change (structural closure)."""
return model.impact_closure(target)
def descriptive_impact(model, target: str, *,
observed_changed: list[str],
generation: str = "") -> dict:
"""What was MATERIALLY affected AFTER the change — from actual
evidence (diff/observations), never assumed from the graph alone."""
predicted = model.impact_closure(target)
predicted_scope = set(predicted.get("mutation_scope", []))
observed = set(observed_changed)
return {
"target": target,
"generation": generation,
"predicted": sorted(predicted_scope),
"observed": sorted(observed),
"unexpected": sorted(observed - predicted_scope), # prediction missed
"unaffected_as_predicted": sorted(predicted_scope - observed),
"recorded_at": _now(),
}
def record_impact_delta(project_store: Path, delta: dict) -> Path:
"""Persist predicted-vs-observed deltas — they compound into better
future impact prediction (rule 19H)."""
p = Path(project_store) / "impact_deltas.jsonl"
import json
with p.open("a") as f:
f.write(json.dumps(delta, sort_keys=True) + "\n")
return p
# ── 19I: selective invalidation bridge ───────────────────────────────────
def invalidate_behaviours(model, behaviours: BehaviourLedger, *,
changed_entities: set[str], generation: str = ""
) -> list[dict]:
"""Selective invalidation driven by STRUCTURAL truth: behaviours
whose dependencies/owner intersect the changed closure reopen;
everything else keeps its confidence (rule 19I)."""
return behaviours.invalidate_for_change(
changed_entities=changed_entities, generation=generation)
# ── 19K: post-change obligations ─────────────────────────────────────────
def post_change_obligations(model, target: str, *,
behaviours: BehaviourLedger | None = None,
changed_entities: set[str] | None = None,
) -> list[dict]:
"""Machine-derived follow-up obligations from STRUCTURAL truth
(rule 19K) — never an improvised generic checklist."""
changed = changed_entities or {target}
obligations: list[dict] = []
seen: set[tuple] = set()
def add(kind: str, target_e: str, why: str) -> None:
key = (kind, target_e)
if key not in seen:
seen.add(key)
obligations.append({"kind": kind, "target": target_e,
"why": why})
for eid in sorted(changed):
# consumers of a changed component must be inspected
for consumer in model.consumers(eid):
add("inspect_consumer", consumer,
f"consumes/depends on changed {eid}")
# provided interfaces: every consumer of the interface is affected
for iface in model.interfaces_for(eid):
add("verify_interface", iface,
f"provided by changed {eid}")
for consumer in model.consumers(iface):
add("inspect_consumer", consumer,
f"consumes {iface} provided by changed {eid}")
# state compatibility
for store in model.state_owned(eid):
add("check_state_compatibility", store,
f"state owned by changed {eid}")
# interfaces the target consumes
for iface in model.consumers(eid) if False else []:
pass
consumed = sorted(r["to"] for r in model.doc["relationships"]
if r["type"] == "consumes" and r["from"] == eid)
for iface in consumed:
add("check_interface_contract", iface,
f"changed {eid} consumes {iface} — contract must still hold")
# deployment
for dep in model.deployment_surfaces(eid):
add("verify_deployment", dep,
f"deployed surface of changed {eid}")
# behaviour retests
if behaviours is not None:
for rec in behaviours.doc["behaviours"].values():
deps = set(rec.get("dependencies") or []) | {
rec.get("owner_component")}
if deps & changed:
add("retest", rec["behaviour_id"],
f"depends on changed {sorted(deps & changed)}")
return obligations
-366
View File
@@ -1,366 +0,0 @@
#!/usr/bin/env python3
"""Build Mode Plugin — State machine orchestrator.
Each phase is an independent module. The state machine coordinates:
1. Read current state from checkpoint file
2. Execute the current phase's module
3. Write output to checkpoint file
4. Advance to next phase
5. Repeat
Key insight from Gauntlet: "Checkpoint files on disk. Reports generated
from checkpoints, not memory." If the conversation dies, the next session
resumes from the last checkpoint. No drift because state is on disk.
Architecture:
- Each phase is a separate module in phases/
- State machine reads checkpoint.json to know current phase
- Each phase writes its output to checkpoint.json
- Cron tasks fire each phase independently
- Ledger tracks progress for resumability
"""
import json
import os
import sys
from datetime import datetime, timezone
from typing import Optional, Dict, Any, List
# ═══════════════════════════════════════════════════════════════════════════════
# CONFIGURATION
# ═══════════════════════════════════════════════════════════════════════════════
PLUGIN_DIR = os.path.dirname(os.path.abspath(__file__))
PHASES_DIR = os.path.join(PLUGIN_DIR, "phases")
BUILDS_DIR = os.path.expanduser("~/.rune/work/builds")
WORKTREE_DIR = os.path.expanduser("~/.rune/worktrees")
REPO_DIR = "/mnt/workdrive/rune"
# Phase execution order
PHASE_ORDER = [
"assess",
"research",
"feasible",
"consult",
"consent",
"plan",
"build",
"verify",
"correct",
"integrate",
"record",
]
# ═══════════════════════════════════════════════════════════════════════════════
# STATE MACHINE
# ═══════════════════════════════════════════════════════════════════════════════
class BuildStateMachine:
"""State machine for build mode. Reads/writes checkpoint files."""
def __init__(self, build_id: str):
self.build_id = build_id
self.build_dir = os.path.join(BUILDS_DIR, build_id)
self.checkpoint_path = os.path.join(self.build_dir, "checkpoint.json")
self.ledger_path = os.path.join(self.build_dir, "ledger.json")
self.trajectory_path = os.path.join(self.build_dir, "trajectory.json")
os.makedirs(self.build_dir, exist_ok=True)
def load_checkpoint(self) -> Dict[str, Any]:
"""Load current state from checkpoint file."""
if os.path.exists(self.checkpoint_path):
with open(self.checkpoint_path) as f:
return json.load(f)
return self._initial_checkpoint()
def save_checkpoint(self, checkpoint: Dict[str, Any]):
"""Save state to checkpoint file."""
checkpoint["updated_at"] = datetime.now(timezone.utc).isoformat()
with open(self.checkpoint_path, "w") as f:
json.dump(checkpoint, f, indent=2)
def _initial_checkpoint(self) -> Dict[str, Any]:
"""Create initial checkpoint."""
return {
"build_id": self.build_id,
"created_at": datetime.now(timezone.utc).isoformat(),
"updated_at": datetime.now(timezone.utc).isoformat(),
"current_phase": "assess",
"phase_status": "pending",
"phases": {},
"description": "",
"worktree_path": None,
"complexity_score": 0,
"estimated_steps": 0,
"batches": [],
"corrections": 0,
"max_corrections": 3,
"files_changed": [],
"contracts": [],
"failure_patterns": [],
}
def get_current_phase(self) -> str:
"""Get current phase from checkpoint."""
checkpoint = self.load_checkpoint()
return checkpoint.get("current_phase", "assess")
def get_phase_status(self) -> str:
"""Get current phase status from checkpoint."""
checkpoint = self.load_checkpoint()
return checkpoint.get("phase_status", "pending")
def advance_phase(self):
"""Advance to next phase."""
checkpoint = self.load_checkpoint()
current = checkpoint["current_phase"]
# Mark current phase as complete
checkpoint["phases"][current] = "complete"
# Find next phase
try:
idx = PHASE_ORDER.index(current)
if idx + 1 < len(PHASE_ORDER):
next_phase = PHASE_ORDER[idx + 1]
checkpoint["current_phase"] = next_phase
checkpoint["phase_status"] = "pending"
else:
checkpoint["phase_status"] = "complete"
except ValueError:
pass
self.save_checkpoint(checkpoint)
def set_phase_status(self, status: str, details: Dict[str, Any] = None):
"""Set current phase status."""
checkpoint = self.load_checkpoint()
checkpoint["phase_status"] = status
if details:
checkpoint.update(details)
self.save_checkpoint(checkpoint)
def log_to_ledger(self, entry: Dict[str, Any]):
"""Append entry to ledger."""
ledger = []
if os.path.exists(self.ledger_path):
with open(self.ledger_path) as f:
ledger = json.load(f)
entry["timestamp"] = datetime.now(timezone.utc).isoformat()
ledger.append(entry)
with open(self.ledger_path, "w") as f:
json.dump(ledger, f, indent=2)
def log_trajectory(self, action: str, details: Dict[str, Any] = None):
"""Append to trajectory log (like SWE-agent .traj files)."""
trajectory = []
if os.path.exists(self.trajectory_path):
with open(self.trajectory_path) as f:
trajectory = json.load(f)
entry = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"action": action,
"details": details or {},
}
trajectory.append(entry)
with open(self.trajectory_path, "w") as f:
json.dump(trajectory, f, indent=2)
def get_resume_point(self) -> Optional[str]:
"""Get phase to resume from."""
checkpoint = self.load_checkpoint()
current = checkpoint.get("current_phase")
status = checkpoint.get("phase_status")
if status == "complete":
# All phases complete
return None
return current
def can_resume(self) -> bool:
"""Check if build can be resumed."""
return self.get_resume_point() is not None
def get_status(self) -> Dict[str, Any]:
"""Get build status."""
checkpoint = self.load_checkpoint()
return {
"build_id": self.build_id,
"description": checkpoint.get("description", ""),
"current_phase": checkpoint.get("current_phase", "assess"),
"phase_status": checkpoint.get("phase_status", "pending"),
"phases": checkpoint.get("phases", {}),
"batches": len(checkpoint.get("batches", [])),
"corrections": checkpoint.get("corrections", 0),
"worktree": checkpoint.get("worktree_path"),
"complexity_score": checkpoint.get("complexity_score", 0),
"estimated_steps": checkpoint.get("estimated_steps", 0),
}
# ═══════════════════════════════════════════════════════════════════════════════
# PHASE EXECUTION
# ═══════════════════════════════════════════════════════════════════════════════
def execute_phase(build_id: str, phase: str = None) -> Dict[str, Any]:
"""Execute a phase. If phase is None, execute current phase."""
sm = BuildStateMachine(build_id)
if phase is None:
phase = sm.get_current_phase()
# Check if phase module exists
phase_module_path = os.path.join(PHASES_DIR, f"{phase}.py")
if not os.path.exists(phase_module_path):
return {"success": False, "error": f"Phase module not found: {phase}"}
# Import and execute phase module
try:
import importlib.util
spec = importlib.util.spec_from_file_location(f"phase_{phase}", phase_module_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
# Execute phase
result = module.execute(sm)
# Log to trajectory
sm.log_trajectory(f"phase_{phase}", {"result": result})
# Advance to next phase if successful
if result.get("success"):
sm.advance_phase()
return result
except Exception as e:
sm.log_trajectory(f"phase_{phase}_error", {"error": str(e)})
return {"success": False, "error": str(e)}
# ═══════════════════════════════════════════════════════════════════════════════
# CLI
# ═══════════════════════════════════════════════════════════════════════════════
def main():
if len(sys.argv) < 2:
print("Usage: build_mode_plugin.py [start|status|resume|execute|abort]")
sys.exit(1)
cmd = sys.argv[1]
if cmd == "start":
if len(sys.argv) < 3:
print("Usage: build_mode_plugin.py start <description>")
sys.exit(1)
description = " ".join(sys.argv[2:])
build_id = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
sm = BuildStateMachine(build_id)
checkpoint = sm.load_checkpoint()
checkpoint["description"] = description
sm.save_checkpoint(checkpoint)
sm.log_to_ledger({"action": "build_started", "description": description})
print(f"✅ Build {build_id} started")
print(f" Description: {description}")
print(f" Checkpoint: {sm.checkpoint_path}")
elif cmd == "status":
if len(sys.argv) < 3:
# List all builds
if os.path.exists(BUILDS_DIR):
builds = os.listdir(BUILDS_DIR)
if builds:
print(f"Builds: {len(builds)}")
for build_id in sorted(builds):
try:
sm = BuildStateMachine(build_id)
status = sm.get_status()
print(
f" {build_id}: {status['description']} ({status['current_phase']}: {status['phase_status']})"
)
except Exception:
pass
else:
print("No builds found.")
else:
print("No builds found.")
else:
build_id = sys.argv[2]
try:
sm = BuildStateMachine(build_id)
status = sm.get_status()
print(f"Build: {status['build_id']}")
print(f" Description: {status['description']}")
print(f" Phase: {status['current_phase']} ({status['phase_status']})")
print(f" Batches: {status['batches']}")
print(f" Corrections: {status['corrections']}")
print(f" Worktree: {status['worktree']}")
print(f" Complexity: {status['complexity_score']}")
print(f" Estimated steps: {status['estimated_steps']}")
except FileNotFoundError:
print(f"Build {build_id} not found")
elif cmd == "resume":
if len(sys.argv) < 3:
print("Usage: build_mode_plugin.py resume <build_id>")
sys.exit(1)
build_id = sys.argv[2]
try:
sm = BuildStateMachine(build_id)
if sm.can_resume():
resume_point = sm.get_resume_point()
print(f"Resuming build {build_id} from phase: {resume_point}")
# Execute the current phase
result = execute_phase(build_id)
print(f"Result: {result}")
else:
print(f"Build {build_id} cannot be resumed (all phases complete)")
except FileNotFoundError:
print(f"Build {build_id} not found")
elif cmd == "execute":
if len(sys.argv) < 3:
print("Usage: build_mode_plugin.py execute <build_id> [phase]")
sys.exit(1)
build_id = sys.argv[2]
phase = sys.argv[3] if len(sys.argv) > 3 else None
result = execute_phase(build_id, phase)
print(json.dumps(result, indent=2))
elif cmd == "abort":
if len(sys.argv) < 3:
print("Usage: build_mode_plugin.py abort <build_id>")
sys.exit(1)
build_id = sys.argv[2]
try:
sm = BuildStateMachine(build_id)
checkpoint = sm.load_checkpoint()
worktree = checkpoint.get("worktree_path")
if worktree:
subprocess.run(
["git", "worktree", "remove", worktree, "--force"],
cwd=REPO_DIR,
capture_output=True,
)
sm.log_to_ledger({"action": "build_aborted"})
print(f"✅ Build {build_id} aborted")
except FileNotFoundError:
print(f"Build {build_id} not found")
else:
print(f"Unknown command: {cmd}")
sys.exit(1)
if __name__ == "__main__":
main()
@@ -1,115 +0,0 @@
#!/usr/bin/env python3
"""Assess phase — Complexity scoring and codebase impact estimation.
This is the first phase. It scores the user's request to determine:
- If build mode is needed (score 8+)
- How many steps are estimated
- What files will be affected
Output: Updates checkpoint with complexity_score, estimated_steps, description.
"""
import os
import sys
from typing import Dict, Any
# Add parent to path for imports
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".."))
def execute(sm) -> Dict[str, Any]:
"""Execute the assess phase."""
checkpoint = sm.load_checkpoint()
description = checkpoint.get("description", "")
if not description:
return {"success": False, "error": "No description provided"}
# Import complexity scorer
try:
from rune.complexity_scorer import score_request
breakdown = score_request(description)
except Exception as e:
return {"success": False, "error": f"Complexity scorer failed: {e}"}
# Update checkpoint
checkpoint["complexity_score"] = breakdown.total
checkpoint["estimated_steps"] = _estimate_steps(description)
checkpoint["assess_signals"] = breakdown.signals
checkpoint["assess_mode"] = breakdown.mode
checkpoint["assess_confidence"] = breakdown.confidence
sm.save_checkpoint(checkpoint)
# Log to ledger
sm.log_to_ledger(
{
"action": "assess_complete",
"complexity_score": breakdown.total,
"estimated_steps": checkpoint["estimated_steps"],
"mode": breakdown.mode,
"confidence": breakdown.confidence,
"signals": breakdown.signals,
}
)
# Check if build mode is needed
if breakdown.total < 8:
return {
"success": True,
"build_mode_needed": False,
"complexity_score": breakdown.total,
"estimated_steps": checkpoint["estimated_steps"],
"mode": breakdown.mode,
"reason": f"Score {breakdown.total} < 8, task mode recommended",
}
return {
"success": True,
"build_mode_needed": True,
"complexity_score": breakdown.total,
"estimated_steps": checkpoint["estimated_steps"],
"mode": breakdown.mode,
"confidence": breakdown.confidence,
"signals": breakdown.signals,
}
def _estimate_steps(description: str) -> int:
"""Estimate number of steps from description."""
steps = 0
desc_lower = description.lower()
# Each distinct verb ≈ 1-2 steps
verbs = [
"add",
"create",
"build",
"implement",
"update",
"fix",
"refactor",
"migrate",
"test",
"deploy",
"configure",
"integrate",
"design",
]
verb_count = sum(1 for v in verbs if v in desc_lower)
steps += verb_count * 2
# Each "and" or "then" ≈ 1 step
conjunctions = ["and", "then", "also", "plus"]
conjunction_count = sum(1 for c in conjunctions if c in desc_lower)
steps += conjunction_count
# Each file reference ≈ 1 step
import re
file_refs = re.findall(r"\b\w+\.\w+\b", description)
steps += len(file_refs)
# Minimum 3 steps for build mode
return max(steps, 3)
@@ -1,42 +0,0 @@
#!/usr/bin/env python3
"""Build phase — Execute batches in 3 phases.
Three-phase execution:
1. Foundation: Types, interfaces, shared utilities
2. Implementation: Feature code
3. Integration: Tests, wiring, documentation
Output: Updates checkpoint with build results.
"""
import os
import sys
from typing import Dict, Any, List
def execute(sm) -> Dict[str, Any]:
"""Execute the build phase."""
checkpoint = sm.load_checkpoint()
batches = checkpoint.get("batches", [])
if not batches:
return {"success": False, "error": "No batches to build"}
sm.log_to_ledger(
{
"action": "build_started",
"batches": len(batches),
}
)
# Build is done by the TUI's model
# This module just tracks the state
sm.set_phase_status("in_progress", {"build_requested": True})
return {
"success": True,
"build_needed": True,
"batches": batches,
"instruction": "Execute batches in 3 phases: Foundation → Implementation → Integration",
}
@@ -1,39 +0,0 @@
#!/usr/bin/env python3
"""Consent phase — User approval.
Presents the plan to the user and waits for approval.
Output: Updates checkpoint with approved.
"""
import os
import sys
from typing import Dict, Any
def execute(sm) -> Dict[str, Any]:
"""Execute the consent phase."""
checkpoint = sm.load_checkpoint()
description = checkpoint.get("description", "")
complexity_score = checkpoint.get("complexity_score", 0)
estimated_steps = checkpoint.get("estimated_steps", 0)
patterns = checkpoint.get("consult_patterns", [])
sm.log_to_ledger(
{
"action": "consent_started",
"description": description,
}
)
# Consent is handled by the TUI
# This module just prepares the approval request
return {
"success": True,
"consent_needed": True,
"description": description,
"complexity_score": complexity_score,
"estimated_steps": estimated_steps,
"patterns": patterns,
"instruction": "Present plan to user and wait for approval",
}
@@ -1,68 +0,0 @@
#!/usr/bin/env python3
"""Consult phase — Knowledge base check + failure pattern lookup.
Checks llm-craft and textual-craft knowledge bases for best practices.
Queries failure pattern database for similar past failures.
Output: Updates checkpoint with patterns, best_practices.
"""
import os
import sys
from typing import Dict, Any
def execute(sm) -> Dict[str, Any]:
"""Execute the consult phase."""
checkpoint = sm.load_checkpoint()
description = checkpoint.get("description", "")
sm.log_to_ledger(
{
"action": "consult_started",
"description": description,
}
)
# Query knowledge bases
patterns = []
best_practices = []
try:
# Check llm-craft knowledge base
kb_path = os.path.join(os.path.dirname(__file__), "..", "..", "skills", "llm-craft", "knowledge.md")
if os.path.exists(kb_path):
with open(kb_path) as f:
content = f.read()
# Extract relevant patterns
if "test" in description.lower():
patterns.append("Use contract testing for interface verification")
if "refactor" in description.lower():
patterns.append("Refactor in small steps, verify after each change")
if "new feature" in description.lower():
patterns.append("Start with interface definition, then implementation")
except Exception:
pass
# Query failure patterns
failure_patterns = checkpoint.get("failure_patterns", [])
if failure_patterns:
best_practices.append(f"Learn from {len(failure_patterns)} past failures")
# Update checkpoint
checkpoint["consult_patterns"] = patterns
checkpoint["consult_best_practices"] = best_practices
sm.save_checkpoint(checkpoint)
sm.log_to_ledger(
{
"action": "consult_complete",
"patterns": patterns,
"best_practices": best_practices,
}
)
return {
"success": True,
"patterns": patterns,
"best_practices": best_practices,
}
@@ -1,46 +0,0 @@
#!/usr/bin/env python3
"""Correct phase — Fix failed batches.
3 attempts max per batch. After 2 failures, re-plan instead of blindly retrying.
Output: Updates checkpoint with corrections.
"""
import os
import sys
from typing import Dict, Any
def execute(sm) -> Dict[str, Any]:
"""Execute the correct phase."""
checkpoint = sm.load_checkpoint()
corrections = checkpoint.get("corrections", 0)
max_corrections = checkpoint.get("max_corrections", 3)
if corrections >= max_corrections:
return {
"success": False,
"error": f"Max corrections ({max_corrections}) reached",
"instruction": "Escalate to user with diagnostic summary",
}
sm.log_to_ledger(
{
"action": "correct_started",
"corrections": corrections,
}
)
# Correct is done by the TUI
# This module just tracks the state
checkpoint["corrections"] = corrections + 1
sm.save_checkpoint(checkpoint)
sm.set_phase_status("in_progress", {"correct_requested": True})
return {
"success": True,
"correct_needed": True,
"corrections": corrections + 1,
"instruction": "Fix failed batches, re-verify. After 2 failures, re-plan.",
}
@@ -1,37 +0,0 @@
#!/usr/bin/env python3
"""Feasible phase — Feasibility gate + complexity report.
Checks if the task is feasible based on research findings.
Output: Updates checkpoint with feasible, risk, complexity_report.
"""
import os
import sys
from typing import Dict, Any
def execute(sm) -> Dict[str, Any]:
"""Execute the feasible phase."""
checkpoint = sm.load_checkpoint()
description = checkpoint.get("description", "")
research = checkpoint.get("research_findings", {})
# Feasibility is determined by the TUI's model
# This module just tracks the state
sm.log_to_ledger(
{
"action": "feasible_started",
"description": description,
}
)
sm.set_phase_status("in_progress", {"feasibility_requested": True})
return {
"success": True,
"feasibility_needed": True,
"description": description,
"research": research,
"instruction": "Assess feasibility based on research findings",
}
@@ -1,32 +0,0 @@
#!/usr/bin/env python3
"""Integrate phase — Scope guardian (full test suite).
Runs full verification after all batches pass.
Output: Updates checkpoint with integration results.
"""
import os
import sys
from typing import Dict, Any
def execute(sm) -> Dict[str, Any]:
"""Execute the integrate phase."""
checkpoint = sm.load_checkpoint()
sm.log_to_ledger(
{
"action": "integrate_started",
}
)
# Integrate is done by the TUI
# This module just tracks the state
sm.set_phase_status("in_progress", {"integrate_requested": True})
return {
"success": True,
"integrate_needed": True,
"instruction": "Run full test suite: canary + ruff + pyright + contracts + TUI validator",
}
@@ -1,41 +0,0 @@
#!/usr/bin/env python3
"""Plan phase — Work manager creates batches with dependency DAG.
Creates batches of 1-3 files each, routes to local or cloud models.
Output: Updates checkpoint with batches.
"""
import os
import sys
from typing import Dict, Any, List
def execute(sm) -> Dict[str, Any]:
"""Execute the plan phase."""
checkpoint = sm.load_checkpoint()
description = checkpoint.get("description", "")
complexity_score = checkpoint.get("complexity_score", 0)
estimated_steps = checkpoint.get("estimated_steps", 0)
sm.log_to_ledger(
{
"action": "plan_started",
"description": description,
"complexity_score": complexity_score,
"estimated_steps": estimated_steps,
}
)
# Plan is done by the TUI's model
# This module just tracks the state
sm.set_phase_status("in_progress", {"plan_requested": True})
return {
"success": True,
"plan_needed": True,
"description": description,
"complexity_score": complexity_score,
"estimated_steps": estimated_steps,
"instruction": "Create batches with dependency DAG, route to models",
}
@@ -1,38 +0,0 @@
#!/usr/bin/env python3
"""Record phase — Update PRD, GitHub, knowledge base, trajectory log.
Final phase. Updates all documentation and records what was learned.
Output: Updates checkpoint with record results.
"""
import os
import sys
from typing import Dict, Any
def execute(sm) -> Dict[str, Any]:
"""Execute the record phase."""
checkpoint = sm.load_checkpoint()
build_id = sm.build_id
description = checkpoint.get("description", "")
sm.log_to_ledger(
{
"action": "record_started",
"build_id": build_id,
"description": description,
}
)
# Record is done by the TUI
# This module just tracks the state
sm.set_phase_status("in_progress", {"record_requested": True})
return {
"success": True,
"record_needed": True,
"build_id": build_id,
"description": description,
"instruction": "Update PRD, commit to GitHub, update knowledge base with learnings",
}
@@ -1,41 +0,0 @@
#!/usr/bin/env python3
"""Research phase — Deep research on the task.
Always runs for build mode. Uses deep-research skill or parallel sub-agents.
Output: Updates checkpoint with research_findings.
"""
import os
import sys
from typing import Dict, Any
def execute(sm) -> Dict[str, Any]:
"""Execute the research phase."""
checkpoint = sm.load_checkpoint()
description = checkpoint.get("description", "")
if not description:
return {"success": False, "error": "No description provided"}
# Log to ledger
sm.log_to_ledger(
{
"action": "research_started",
"description": description,
}
)
# Research is done by the TUI's model (not this module)
# This module just tracks the state
# The actual research happens in the TUI's _send_to_model flow
# For now, mark as needing external research
sm.set_phase_status("in_progress", {"research_requested": True})
return {
"success": True,
"research_needed": True,
"description": description,
"instruction": "Use deep-research skill or parallel sub-agents to research this task",
}
@@ -1,40 +0,0 @@
#!/usr/bin/env python3
"""Verify phase — Tiered verification.
Tiered verification strategy:
1. Syntactic: Per-edit lint, type-check, format (<1s)
2. Semantic: Per-batch unit tests, import validation (<30s)
3. Integration: Per-wave contract tests, build/compile (<5min)
4. System: Full test suite, property-based (<15min)
Output: Updates checkpoint with verification results.
"""
import os
import sys
from typing import Dict, Any
def execute(sm) -> Dict[str, Any]:
"""Execute the verify phase."""
checkpoint = sm.load_checkpoint()
batches = checkpoint.get("batches", [])
sm.log_to_ledger(
{
"action": "verify_started",
"batches": len(batches),
}
)
# Verify is done by the TUI
# This module just tracks the state
sm.set_phase_status("in_progress", {"verify_requested": True})
return {
"success": True,
"verify_needed": True,
"batches": batches,
"instruction": "Run tiered verification: syntactic → semantic → integration → system",
}
+85
View File
@@ -0,0 +1,85 @@
"""programme_gates.py — T21.2 §21.2G: mandatory acceptance-gate accounting.
The executing model must NOT be able to:
- silently remove mandatory gates;
- demote MANDATORY → OPTIONAL;
- restore acceptance with unrun mandatory gates.
Every mandatory gate ends in exactly one of: PASS | BLOCKED | WAIVED_BY_USER.
Optional gates may be OPTIONAL_SKIPPED or OPTIONAL_PASS.
This is a governance correction, not new architecture.
"""
from __future__ import annotations
import time
MANDATORY_STATUSES = ("PASS", "BLOCKED", "WAIVED_BY_USER")
OPTIONAL_STATUSES = ("OPTIONAL_SKIPPED", "OPTIONAL_PASS")
ALL_STATUSES = MANDATORY_STATUSES + OPTIONAL_STATUSES
def _now() -> str:
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
class GateDemotionError(ValueError):
"""The model tried to demote a mandatory gate to optional."""
class GateAccounting:
def __init__(self):
self.gates: dict[str, dict] = {}
def declare(self, gate_id: str, *, mandatory: bool,
requirement: str) -> None:
self.gates[gate_id] = {
"gate_id": gate_id, "mandatory": mandatory,
"requirement": requirement[:200],
"status": "UNRUN", "evidence": "", "blocker": "",
"waiver_reason": "", "waived_by": "",
"declared_at": _now(), "resolved_at": "",
}
def record(self, gate_id: str, status: str, *,
evidence: str = "", blocker: str = "",
waiver_reason: str = "", waived_by: str = "") -> dict:
g = self.gates.get(gate_id)
if g is None:
raise ValueError(f"unknown gate {gate_id!r}")
if g["mandatory"] and status in OPTIONAL_STATUSES:
raise GateDemotionError(
f"gate {gate_id!r} is MANDATORY — cannot be marked "
f"{status!r} without explicit user waiver")
if status == "WAIVED_BY_USER":
if not g["mandatory"]:
raise ValueError(
f"WAIVED_BY_USER only applies to mandatory gates; "
f"{gate_id!r} is optional")
if not waiver_reason or not waived_by:
raise ValueError(
f"waiver for {gate_id!r} requires reason + waived_by")
if status not in ALL_STATUSES:
raise ValueError(f"unknown gate status {status!r}")
g["status"] = status
g["evidence"] = evidence[:200]
g["blocker"] = blocker[:200]
g["waiver_reason"] = waiver_reason[:200]
g["waived_by"] = waived_by
g["resolved_at"] = _now()
return g
def can_accept(self) -> tuple[bool, list[str]]:
"""Acceptance eligible only when every mandatory gate is
PASS or WAIVED_BY_USER (with persisted waiver)."""
blockers = []
for gid, g in sorted(self.gates.items()):
if not g["mandatory"]:
continue
if g["status"] not in ("PASS", "WAIVED_BY_USER"):
blockers.append(
f"{gid}: {g['status']} — {g.get('blocker', '')}")
return (not blockers, blockers)
def accounting(self) -> list[dict]:
return sorted(self.gates.values(), key=lambda g: g["gate_id"])
+139
View File
@@ -0,0 +1,139 @@
"""project_registry.py — T19 §19C: canonical registry of scaffolded
projects.
BOUNDARIES (rule 19C, proven by tests):
- Registry = DISCOVERY/INDEX (what projects exist, where their truth
lives, health/migration state).
- Project Scaffold/structural model = project structural truth
(never duplicated here).
- Spine = Rune's structural self-model. An external project's Scaffold
does NOT become Spine structure because Rune knows it exists.
Registry state: one JSON document (versioned, provenance-aware per rule
G) at <store_dir>/project_registry.json. Operational uses: /testing
across projects (19L), drift/migration sweeps, re-entry discovery.
"""
from __future__ import annotations
import time
from pathlib import Path
REGISTRY_SCHEMA_VERSION = 1
# project health states (operational: drives /testing and migration
# consent sweeps)
HEALTHY = "HEALTHY"
DRIFTED = "DRIFTED"
MIGRATION_REQUIRED = "MIGRATION_REQUIRED"
UNKNOWN = "UNKNOWN"
def _now() -> str:
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
class ProjectRegistry:
def __init__(self, store_dir: Path | str):
self.path = Path(store_dir) / "project_registry.json"
self.doc = self._load()
def _load(self) -> dict:
if self.path.is_file():
import json
return json.loads(self.path.read_text())
return {"schema_version": REGISTRY_SCHEMA_VERSION,
"projects": {}, "updated_at": _now()}
def save(self) -> None:
import json
self.doc["updated_at"] = _now()
self.path.parent.mkdir(parents=True, exist_ok=True)
self.path.write_text(json.dumps(self.doc, indent=1, sort_keys=True))
def register(self, *, project_id: str, scaffold_location: str,
repo_identity: str = "", forge_repo: str = "",
default_branch: str = "master",
structural_schema_version: int = 1,
runtime_generation: str = "",
last_known_good: str = "") -> dict:
"""Register/index a scaffolded project. References only — the
project's structural truth stays in ITS store (boundary rule)."""
entry = {
"project_id": project_id,
"scaffold_location": scaffold_location,
"repo_identity": repo_identity,
"forge_repo": forge_repo,
"default_branch": default_branch,
"structural_schema_version": structural_schema_version,
"scaffold_state": {"health": UNKNOWN,
"last_material_update": _now()},
"runtime_generation": runtime_generation,
"last_known_good": last_known_good,
"outstanding_validations": 0,
"registered_at": _now(),
}
self.doc["projects"][project_id] = entry
return entry
def get(self, project_id: str) -> dict | None:
return self.doc["projects"].get(project_id)
def all_projects(self) -> list[dict]:
return sorted(self.doc["projects"].values(),
key=lambda p: p["project_id"])
def set_health(self, project_id: str, health: str, *,
note: str = "") -> None:
if health not in (HEALTHY, DRIFTED, MIGRATION_REQUIRED, UNKNOWN):
raise ValueError(f"unknown health {health!r}")
p = self.doc["projects"][project_id]
p["scaffold_state"]["health"] = health
p["scaffold_state"]["last_material_update"] = _now()
if note:
p["scaffold_state"].setdefault("notes", []).append(
{"at": _now(), "note": note[:160]})
def set_outstanding_validations(self, project_id: str,
count: int) -> None:
self.doc["projects"][project_id]["outstanding_validations"] = count
def mark_generation(self, project_id: str, *, current: str,
last_known_good: str = "") -> None:
p = self.doc["projects"][project_id]
p["runtime_generation"] = current
if last_known_good:
p["last_known_good"] = last_known_good
def resolve_cwd(self, project_id: str) -> str:
"""T21.2 §21.2B: canonical REPOSITORY/worktree execution cwd for
a project — NOT scaffold_location. Precedence (21.2B-3):
explicit contract/worktree cwd > programme cwd > registry
repo cwd > unresolved/refuse.
Registry stores repo_identity as "git:<path>" or a bare path.
Scaffold storage is NEVER used as a repo-cwd proxy. Raises for
unknown projects (refuse mutation rather than guessing)."""
entry = self.get(project_id)
if entry is None:
raise ValueError(
f"unknown project {project_id!r} — resolve identity "
f"before mutation; never execute from HOME")
repo = entry.get("repo_identity", "")
if repo.startswith("git:"):
repo = repo[len("git:"):]
repo = repo.strip()
if not repo:
raise ValueError(
f"project {project_id!r} has no repo_identity — "
f"no local execution checkout available")
# an active worktree overrides the repo root (21.2B-3 precedence)
wt = entry.get("active_worktree", "")
if wt:
return wt
return repo
def needs_migration(self, proposed_schema_version: int) -> list[str]:
"""Projects whose structural schema is older than a proposed
version (feeds the 18I consent sweep — never silent upgrades)."""
return sorted(pid for pid, p in self.doc["projects"].items()
if p.get("structural_schema_version", 1)
< proposed_schema_version)
+7 -1
View File
@@ -9,6 +9,12 @@ Usage:
rune_graphic.py test # Display animation in terminal
"""
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from PIL import Image
import math
import os
import sys
@@ -23,7 +29,7 @@ BG_COLOR = (30, 30, 40) # Dark background
def generate_diamond_frame(angle: float, size: int = FRAME_SIZE) -> "Image.Image":
"""Generate a single frame of a spinning diamond at the given angle."""
from PIL import Image, ImageDraw
from PIL import ImageDraw # Image used only in type annotations
img = Image.new("RGBA", (size, size), BG_COLOR + (255,))
draw = ImageDraw.Draw(img)
+8 -2
View File
@@ -1585,8 +1585,13 @@ class RuneOperator:
task.status = "failed"
task.result = result.error or "Unknown error"
self.state.current_task = None
self._save()
# Mirror the terminal move into the canonical ledger (#18).
# T21 §21I CANONICAL-FIRST ORDERING (crash-window fix): the
# canonical transition commits BEFORE the legacy cache save. The
# previous order (save → transition) left a crash window where
# operator.json claimed completed/failed while the canonical
# repo still said running — the cache outran truth. Reversed:
# a crash after transition but before _save() leaves the cache
# STALE (backfillable via _ensure_canonical) instead of AHEAD.
self._ensure_canonical(task)
if result.success and result.pending_approval is not None:
self._task_state.transition(task.task_id, "awaiting_approval")
@@ -1605,6 +1610,7 @@ class RuneOperator:
self._task_state.transition(task.task_id, "failed")
# P0-04 (#20): meaningful-event seed (see send_task failure path).
self._emit_task_failed(task, channel=self._channel_for_task(task.task_id))
self._save()
def _ensure_canonical(self, task: RuneTask) -> None:
"""Backfill a task seeded through the legacy path into the ledger (#18).
+129 -14
View File
@@ -8,7 +8,7 @@ mode system, subject header, shared memory and identity.
This is not an interface. This is how I see, speak, and interact with the world.
Version: v0.66.71
Version: v0.86.0
Features: 41 (24 original + 17 new native commands)
Usage:
@@ -1697,9 +1697,38 @@ class RuneApp(App):
except Exception as e:
return ("registry_error", str(e))
def _task_repo_for_loader():
"""Access the canonical TaskStateRepository for the task
loader (T21 21G migration) — closure over the operator
getter already in scope. Degrades to the legacy ledger view
when the operator is unreachable (startup ordering), which
the load_tasks fallback then handles."""
try:
import rune_operator as _ro
return getattr(_ro.get_operator(), "_task_state", None)
except Exception as exc: # noqa: BLE001 — deliberate degradation: startup ordering; the fallback path handles it and the degradation is logged
print(f"[task-loader] canonical repo unavailable ({exc}); "
f"falling back to export ledger", flush=True)
return None
def load_tasks():
try:
if BACKEND_AVAILABLE:
# T21 21G: the TUI task loader reads the CANONICAL
# TaskStateRepository (17F authority), not the legacy
# task_persist export ledger. task_persist remains a
# session-end export view (its only remaining role).
repo = _task_repo_for_loader()
if repo is not None:
records = repo.list_all()
active = [
{"task_id": r.task_id, "title": r.title,
"status": r.status}
for r in records
if r.status in ("created", "running")]
all_pins = pins.list_pins()
open_pins = [p for p in all_pins if p.get("status") == "open"]
return ("tasks", active, open_pins)
data = task_persist.load()
tasks = data.get("tasks", [])
active = [t for t in tasks if t.get("status") in ("open", "in_progress")]
@@ -1856,7 +1885,7 @@ class RuneApp(App):
# Show version info
self.chat_channel.add_message(
"system",
f"[dim]Rune v0.66.71 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.86.0 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours
@@ -2099,20 +2128,21 @@ class RuneApp(App):
except Exception:
pass
# Task mode: auto-detect if build mode is needed
# Task mode: auto-detect if build mode is needed (T21.1 21.1E:
# complexity_scorer RETIRED — the canonical classify_mode owns
# construction-mode recommendation now (same classifier as every
# other route; the scorer's keyword heuristic was superseded).
# Kept proportional: a one-line hint, no separate 602-line module.
if channel == "task":
try:
from rune.complexity_scorer import score_request
from rune.construction_mode import classify_mode
breakdown = score_request(text)
if breakdown.total >= 8 and breakdown.mode == "build":
_mode = classify_mode(text)
if _mode.mode in ("PROJECT_BUILD", "EXISTING_PROJECT_CHANGE"):
ch.add_message(
"system",
f"[yellow]🔨 Build mode recommended (score: {breakdown.total})[/yellow]",
)
ch.add_message(
"system",
f"[yellow] Signals: {', '.join(breakdown.signals[:3])}[/yellow]",
f"[yellow]🔨 Build mode recommended "
f"(classified: {_mode.mode})[/yellow]",
)
ch.add_message(
"system",
@@ -3112,6 +3142,58 @@ class RuneApp(App):
except Exception as e:
self._ch().add_message("system", f"[dim]Error: {e}[/dim]")
def _cmd_testing(self, arg):
"""T19 19L: canonical Testing Mode — outstanding validations.
/testing → aggregate across registered projects
/testing <project> → one project's outstanding items."""
try:
from pathlib import Path as _Path
from rune import testing_mode as tm
from rune.behaviour_ledger import BehaviourLedger
from rune.project_registry import ProjectRegistry
registry = ProjectRegistry("store/scaffold")
ch = self._ch()
if arg.strip():
# project filter: read THAT project's ledger
entry = registry.get(arg.strip())
if entry is None:
ch.add_message("system",
f"[yellow]No registered scaffolded project "
f"{arg.strip()!r}[/yellow]")
return
loc = entry["scaffold_location"]
if not (_Path(loc) / "behaviour_ledger.json").is_file():
ch.add_message("system",
f"[yellow]{arg.strip()} has no behaviour ledger "
f"yet[/yellow]")
return
beh = BehaviourLedger(loc)
items = beh.outstanding()
ch.add_message("system",
f"[bold]{arg.strip()}: {len(items)} outstanding "
f"validation(s)[/bold]")
else:
rows = tm.aggregate(registry, _Path("store/scaffold"))
ch.add_message("system", "[bold]Testing aggregate "
"(registered scaffolded projects)[/bold]")
for r in rows:
if r["outstanding"] is None:
ch.add_message("system",
f" {r['project']}: no behaviour ledger yet")
else:
ch.add_message("system",
f" {r['project']}: {r['outstanding']} "
f"outstanding")
items = []
for b in items[:8]:
ch.add_message("system",
f" • {b['behaviour_id']}: {b['intended_behaviour'][:70]} "
f"[{b['state']}]"
+ (f" — procedure: {b['procedure'][:60]}" if b.get("procedure") else ""))
except Exception as exc: # noqa: BLE001 — user-visible surface: report, never crash the TUI
self._ch().add_message("system",
f"[red]/testing failed: {exc}[/red]")
def _cmd_build(self, arg):
"""Build mode — full build pipeline with worktree isolation."""
try:
@@ -3169,9 +3251,41 @@ class RuneApp(App):
msg += f"\n - {q}"
self._ch().add_message("system", msg)
return
# PROJECT_BUILD / EXISTING_PROJECT_CHANGE: contract
# activation happens on the operator hook path (the ONE
# activate()); the gate's job is refuse-or-admit.
# T18 18M FULL BACKEND CONVERGENCE: admission mints a
# canonical Construction Contract (same as every other
# route) — /build start no longer runs an independent
# construction state machine. BuildManager remains the
# bounded worktree/ledger adapter driven BY the contract.
# T19 19A GOVERNANCE INVARIANT: real construction must
# NOT silently bypass its canonical Construction Contract.
# The T18 degradation paths (mint unavailable/failed →
# BuildManager proceeds legacy-governed) were a silent
# downgrade — REMOVED. Contract mint failure is FAIL-
# CLOSED: the build does not start; the user sees why.
# BuildManager remains a bounded worktree/ledger adapter
# that is only ever reachable WITH a governing contract.
import time as _t
_cid = f"build-{int(_t.time())}"
try:
from rune.construction_interface import ConstructionInterface
_iface = ConstructionInterface(builder="tui-build-cmd")
_doc = _iface.scaffold.load()
_iface.scaffold.new_contract(
_doc, contract_id=_cid, need=description,
origin="user", scope=[],
requirement="/build start (classified: "
f"{mode_name})")
_iface.scaffold.save(_doc)
_iface.transition(_cid, "approved",
evidence_ref="user:/build-start")
_iface.transition(_cid, "building",
evidence_ref="build-manager-adapter")
except Exception as exc: # noqa: BLE001 — T19 19A: contract mint failure FAILS CLOSED (refuse, never degrade to ungoverned construction)
self._ch().add_message("system",
f"[red]'/build start' refused: canonical contract "
f"mint failed ({exc}) — construction cannot start "
f"without its governing contract.[/red]")
return
manager = BuildManager()
build_id = manager.start_build(description)
self._ch().add_message("system", f"[bold]✅ Build {build_id} started[/bold]")
@@ -3452,6 +3566,7 @@ class RuneApp(App):
"/audit": _cmd_audit,
"/work": _cmd_work,
"/build": _cmd_build,
"/testing": _cmd_testing,
"/queue": _cmd_queue,
"/pull": _cmd_pull,
"/help": _cmd_help,
+9
View File
@@ -249,6 +249,15 @@ def build(manifest_path: Path | None = None) -> dict:
registry = _canary_registry()
tested = _test_backed_modules()
mods = {e["id"].split(":", 1)[1] for e in doc["entities"] if e["kind"] == "module"}
# T21.4 §21.4G: the ENTRY POINT is invisible to the import graph
# (nothing imports __main__) but is a real runtime path — seed it
# deterministically whenever the file exists.
if (SRC_DIR / "__main__.py").is_file() and "__main__" not in mods:
add_entity(doc, id="module:__main__", kind="module", maturity="PRESENT",
identities={"path": "src/rune/__main__.py",
"role": "TUI entry point (python3 -m rune)"},
evidence_refs=["entry-point:python3 -m rune"])
mods.add("__main__")
for m in sorted(mods):
mid = f"module:{m}"
e = entity(doc, mid)
+77
View File
@@ -0,0 +1,77 @@
"""structural_migration.py — T18 §18I: schema versioning + consent for
structural Scaffolds.
USER REQUIREMENT (T18 mandate): when Scaffolding itself later receives a
SIGNIFICANT structural/version uplift, known projects previously
scaffolded must NOT be silently upgraded. Material migrations require
user consent; minor backwards-compatible migrations stay automatic.
"""
from __future__ import annotations
import time
class ConsentRequired(Exception):
"""A material schema migration was requested without user approval."""
def classify_migration(*, from_v: int, to_v: int,
changes: list[str]) -> str:
"""MINOR (automatic) vs MATERIAL (consent required). Deterministic:
renames/removals/id-format changes/semantic shifts are MATERIAL;
additive optional kinds/fields are MINOR."""
material_markers = ("rename", "drop ", "remove", "delete",
"id format", "semantic", "restructure",
"change format", "merge", "split")
for c in changes:
cl = c.lower()
if any(m in cl for m in material_markers):
return "MATERIAL"
return "MINOR"
def affected_projects(stores: dict[str, dict], proposed_v: int) -> list[str]:
"""Which known projects (schema_version < proposed_v) would a
proposed uplift touch. Scaffolding presents these for consent —
never silently upgrades."""
return sorted(name for name, doc in stores.items()
if doc.get("schema_version", 0) < proposed_v)
def apply_migration(*, model_store, from_v: int, to_v: int,
changes: list[str], user_approved: bool,
approved_by: str = "") -> dict:
"""Apply (or refuse) a schema migration. MATERIAL without explicit
user approval raises ConsentRequired — the silent upgrade is
structurally impossible. Returns a durable migration record."""
kind = classify_migration(from_v=from_v, to_v=to_v, changes=changes)
if kind == "MATERIAL" and not user_approved:
raise ConsentRequired(
f"material schema migration {from_v}→{to_v} requires user "
f"consent: {changes[:3]}")
return {
"from_v": from_v, "to_v": to_v, "kind": kind,
"changes": changes, "user_approved": user_approved,
"approved_by": approved_by if user_approved else None,
"applied_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
}
def consent_brief(*, from_v: int, to_v: int, changes: list[str],
benefits: list[str]) -> dict:
"""The consent presentation (rule 18I): current version, proposed
version, benefits, semantic changes, risks, expected effect."""
return {
"current_version": from_v,
"proposed_version": to_v,
"benefits": benefits,
"semantic_changes": changes,
"migration_risks": [
"stored models require one-time reconciliation after upgrade",
"consumers of the old schema need re-reading via projection",
],
"expected_effect": ("models keep their data; identity and "
"relationship semantics change per the listed "
"changes"),
"requires": "explicit user approval before any project is upgraded",
}
+352
View File
@@ -0,0 +1,352 @@
"""structural_model.py — T18 §18E/§18F: the first-class STRUCTURAL
SCAFFOLD for scaffolded projects.
RESEARCH-LED (Build Basis: research/t18-structural-model-prior-art,
QUALIFIED 2026-09-15):
ADOPTED: Backstage relation vocabulary (partOf/belongs_to,
depends_on, provides/consumes, ownedBy) + entity-ref discipline;
Kythe edge kinds (calls, reads, writes, satisfies, overrides) +
derived-reverse-edge rule; Nx affected semantics for impact closure;
Bazel rdeps(kind(test)) for verification scoping.
ADAPTED: identity is DETERMINISTIC and STABLE across versions
(Kythe explicitly declines this — a persistent model cannot); C4
deployment shapes become machine-derived deployed_by edges; Glean's
batch layering → externally-derived relationships carry a distinct
authority.
REJECTED: heavyweight graph stores (no evidence/maturity/consent
semantics); LSIF; Stack Graphs (archived).
INNOVATION (absent from prior art): evidence_refs + authority +
status on every relationship; verified_by/deployed_by to concrete
entities; consent-gated schema migration.
Storage: the EXISTING structural_store generic core (rule A — no
second graph store, no competing Spine; Spine uses the same core for
Rune, project models use it for their projects).
ENTITY KINDS: project, domain, subsystem, component (module), interface,
state_store, test_suite, deployment_surface, environment.
RELATIONSHIPS: belongs_to, depends_on, calls, provides, consumes,
reads, writes, connects_to, verified_by, deployed_by, owns, supersedes,
legacy_of — every edge carries evidence_refs + authority + status.
"""
from __future__ import annotations
import time
from pathlib import Path
from rune.structural_store import (
add_entity, add_relationship, entity, load, new_doc, save,
)
SCHEMA_VERSION = 1
ENTITY_KINDS = (
"project", "domain", "subsystem", "component", "interface",
"state_store", "test_suite", "deployment_surface", "environment",
)
RELATION_TYPES = (
"belongs_to", "owns", "depends_on", "calls", "provides", "consumes",
"reads", "writes", "connects_to", "verified_by", "deployed_by",
"supersedes", "legacy_of",
)
class StructuralModel:
"""A project's persistent interconnected structural model. One file
per project: <store_dir>/structural.json (spine-shaped — same
generic core, open-world: unknown future kinds/relationships survive
round-trips by construction)."""
def __init__(self, store_dir: Path | str, project_id: str):
self.store_dir = Path(store_dir)
self.project_id = project_id
self.path = self.store_dir / "structural.json"
doc = load(self.path)
if doc is None:
doc = new_doc()
self._init_project(doc)
self.doc = doc
# -- persistence ----------------------------------------------------
def _init_project(self, doc: dict) -> None:
add_entity(doc, id=f"project:{self.project_id}", kind="project",
maturity="DECLARED",
evidence_refs=["structural-model-init"])
root = add_relationship(doc, type="belongs_to",
from_id=f"project:{self.project_id}",
to_id=f"project:{self.project_id}",
evidence_refs=["structural-model-root"])
root["authority"] = "model-author"
root["status"] = "root"
save(doc, self.path)
def save(self) -> None:
self.doc["schema_version"] = SCHEMA_VERSION
save(self.doc, self.path)
def reload(self) -> None:
doc = load(self.path)
if doc is not None:
self.doc = doc
# -- entity/relationship construction -------------------------------
def add(self, kind: str, name: str, *, parent: str | None = None,
ext: dict | None = None,
evidence_ref: str = "structural-model") -> str:
"""Add an entity with a STABLE deterministic id (kind:name —
persistent across model versions; rule 18E). parent (an id) adds
the belongs_to edge (Backstage partOf semantics)."""
if kind not in ENTITY_KINDS:
raise ValueError(f"unknown entity kind {kind!r} — known: "
f"{ENTITY_KINDS}")
eid = f"{kind}:{name}"
e = add_entity(self.doc, id=eid, kind=kind, maturity="DECLARED",
evidence_refs=[evidence_ref])
e["identities"]["name"] = name
if ext:
e.setdefault("ext", {}).update(ext)
if parent:
self.relate(eid, "belongs_to", parent,
evidence_ref=evidence_ref)
return eid
def relate(self, from_id: str, rel: str, to_id: str, *,
evidence_ref: str = "structural-model",
authority: str = "model-author",
status: str = "active", ext: dict | None = None) -> dict:
"""Add a typed relationship. Every edge carries evidence_refs +
authority + status (the INNOVATION per the Build Basis —
authority-graded claims). Authority values: model-author
(agent-observed/derived), importer (external tool derived —
Glean batch-layering adaptation), user (user-locked)."""
if rel not in RELATION_TYPES:
raise ValueError(f"unknown relationship {rel!r} — known: "
f"{RELATION_TYPES}")
r = add_relationship(self.doc, type=rel, from_id=from_id,
to_id=to_id, evidence_refs=[evidence_ref])
r["authority"] = authority
r["status"] = status
if ext:
r["ext"] = ext
return r
# -- queries (§18F) ---------------------------------------------------
def get(self, eid: str) -> dict | None:
return entity(self.doc, eid)
def children(self, eid: str) -> list[str]:
return [r["from"] for r in self.doc["relationships"]
if r["type"] == "belongs_to" and r["to"] == eid
and r["from"] != eid]
def beneath(self, eid: str) -> list[str]:
"""Transitive descendants (bounded by the graph itself)."""
seen, out, frontier = {eid}, [], [eid]
while frontier:
cur = frontier.pop()
for child in self.children(cur):
if child not in seen:
seen.add(child)
out.append(child)
frontier.append(child)
return sorted(out)
def owning_domain(self, eid: str) -> str | None:
cur, hops = eid, 0
while hops < 16:
if cur.startswith("domain:"):
return cur
parents = [r["to"] for r in self.doc["relationships"]
if r["type"] == "belongs_to" and r["from"] == cur]
if not parents:
return None
cur = parents[0]
hops += 1
return None
def dependencies(self, eid: str) -> list[str]:
return sorted(r["to"] for r in self.doc["relationships"]
if r["type"] == "depends_on" and r["from"] == eid
and r.get("status") != "stale")
def consumers(self, eid: str) -> list[str]:
"""Who depends on OR consumes this entity (interfaces are
consumed; components are depended on — Backstage relation pairs)."""
return sorted(r["from"] for r in self.doc["relationships"]
if r["type"] in ("depends_on", "consumes")
and r["to"] == eid and r.get("status") != "stale")
def interfaces_for(self, eid: str) -> list[str]:
return sorted(r["to"] for r in self.doc["relationships"]
if r["type"] == "provides" and r["from"] == eid)
def state_owned(self, eid: str) -> list[str]:
return sorted(r["to"] for r in self.doc["relationships"]
if r["type"] == "owns" and r["from"] == eid)
def verification_surfaces(self, eid: str) -> list[str]:
return sorted(r["to"] for r in self.doc["relationships"]
if r["type"] == "verified_by" and r["from"] == eid)
def deployment_surfaces(self, eid: str) -> list[str]:
return sorted(r["to"] for r in self.doc["relationships"]
if r["type"] == "deployed_by" and r["from"] == eid)
def canonical_state_owner(self, eid: str) -> str | None:
"""The canonical-state owner: the state_store this entity owns,
or (for a state_store) itself."""
e = self.get(eid)
if e and e.get("kind") == "state_store":
return eid
owned = self.state_owned(eid)
return owned[0] if owned else None
def supersession(self, eid: str) -> dict:
return {
"supersedes": sorted(r["to"] for r in self.doc["relationships"]
if r["type"] == "supersedes"
and r["from"] == eid),
"superseded_by": sorted(
r["from"] for r in self.doc["relationships"]
if r["type"] == "supersedes" and r["to"] == eid),
"legacy_of": sorted(r["to"] for r in self.doc["relationships"]
if r["type"] == "legacy_of"
and r["from"] == eid),
}
# -- impact closure (§18F) -------------------------------------------
def impact_closure(self, eid: str, *, max_depth: int = 8) -> dict:
"""STRUCTURALLY BOUNDED SCOPE (rule J): the necessary structural
impact closure for a change to eid. Nx-affected semantics:
reverse-dependency closure + verification + deployment + state
surfaces. Unknown/dynamic relationships stay explicit uncertainty
— never silent (rule 18F)."""
if self.get(eid) is None:
return {"error": f"unknown entity {eid!r}"}
# reverse-dependency closure (consumers, transitively, bounded)
seen, frontier, depth = {eid}, [eid], 0
consumers: set[str] = set()
while frontier and depth < max_depth:
nxt = []
for cur in frontier:
for c in self.consumers(cur):
if c not in seen:
seen.add(c)
consumers.add(c)
nxt.append(c)
frontier, depth = nxt, depth + 1
direct_deps = self.dependencies(eid)
# ripple through PROVIDED interfaces (Kythe exports semantics):
# a change that touches an entity's provided interface reaches
# every consumer of that interface
for iface in self.interfaces_for(eid):
for c in self.consumers(iface):
if c not in seen:
seen.add(c)
consumers.add(c)
uncertainty = [
{"kind": "dynamic-consumer",
"note": "static model cannot see dynamic dispatch/string-keyed "
"lookup/late-binding consumers — reconcilers must "
"verify at runtime"}]
# lockfile/deps edge (Nx documented case): deps outside closure
# are OBSERVATION scope, not mutation scope
return {
"target": eid,
"owning_domain": self.owning_domain(eid),
"mutation_scope": sorted({eid} | consumers),
"observation_scope": {
"direct_dependencies": direct_deps,
# interfaces the target provides AND consumes — both
# directions are observation-relevant for a change
"interfaces_provided": self.interfaces_for(eid),
"interfaces_consumed": sorted(
r["to"] for r in self.doc["relationships"]
if r["type"] == "consumes" and r["from"] == eid),
},
"state_surfaces": sorted(
{s for e in ({eid} | consumers)
for s in self.state_owned(e)}),
"verification_scope": sorted(
{v for e in ({eid} | consumers)
for v in self.verification_surfaces(e)}),
"deployment_scope": sorted(
{d for e in ({eid} | consumers)
for d in self.deployment_surfaces(e)}),
"invariants": [
f"belongs_to hierarchy of {eid} remains stable",
"canonical-state ownership unchanged"],
"non_goals": [],
"uncertainty": uncertainty,
"closure_depth": depth,
}
# ── T18 §18K: workspace lifecycle foundation ────────────────────────────
#
# Canonical lifecycle (rule 18K) — BuildManager remains the bounded
# adapter for the worktree mechanics (17G decision), the LIFECYCLE and
# its record live here so contracts can carry workspace provenance:
#
# verified source/base SHA → per-contract isolated worktree →
# construction → verification → commit → push → CI/merge/release →
# deployment confirmation → evidence archival → cleanup
#
# Records never hard-code live-runtime mutation (rule P): the deployed
# generation changes only through the canonical promote machinery.
WORKSPACE_LIFECYCLE_STAGES = (
"base_verified", "worktree_created", "construction",
"verified", "committed", "pushed", "ci_qualified",
"released", "deployment_confirmed", "evidence_archived", "cleaned",
)
def workspace_record(*, contract_id: str, construction_base: str,
runtime_generation: str = "",
candidate_generation: str = "",
promotion_target: str = "gitea/master",
rollback_generation: str = "") -> dict:
"""The workspace provenance record a contract carries. Generations
are SHAs/dirs — never live-tree mutations."""
return {
"contract_id": contract_id,
"construction_base": construction_base,
"runtime_generation": runtime_generation,
"candidate_generation": candidate_generation,
"promotion_target": promotion_target,
"rollback_generation": rollback_generation,
"stages_completed": [],
"lifecycle": list(WORKSPACE_LIFECYCLE_STAGES),
}
def workspace_advance(record: dict, stage: str, *,
evidence: str = "") -> dict:
"""Advance the workspace lifecycle one stage (order-enforced)."""
if stage not in WORKSPACE_LIFECYCLE_STAGES:
raise ValueError(f"unknown workspace stage {stage!r}")
done = record["stages_completed"]
if done:
last = WORKSPACE_LIFECYCLE_STAGES.index(done[-1])
cur = WORKSPACE_LIFECYCLE_STAGES.index(stage)
if cur != last + 1:
raise ValueError(
f"lifecycle order violation: {done[-1]} → {stage}")
elif stage != WORKSPACE_LIFECYCLE_STAGES[0]:
raise ValueError(f"lifecycle must start at "
f"{WORKSPACE_LIFECYCLE_STAGES[0]}, got {stage!r}")
done.append(stage)
if evidence:
record.setdefault("evidence", []).append(
{"stage": stage, "evidence": evidence[:200]})
return record
+7 -1
View File
@@ -109,8 +109,14 @@ def _redact_secrets(obj):
dict key named like a credential has its value stripped before
serialization. Structural stores must never become secret stores."""
if isinstance(obj, dict):
def _is_secret_key(key) -> bool:
kl = str(key).lower()
return (kl in _SECRET_KEY_NAMES
or any(w in kl for w in ("token", "api_key", "apikey",
"password", "secret",
"credential", "private_key")))
return {
k: ("<redacted>" if str(k).lower() in _SECRET_KEY_NAMES else _redact_secrets(v)) for k, v in obj.items()
k: ("<redacted>" if _is_secret_key(k) else _redact_secrets(v)) for k, v in obj.items()
}
if isinstance(obj, list):
return [_redact_secrets(v) for v in obj]
+196
View File
@@ -0,0 +1,196 @@
"""testing_mode.py — T19 §19L/19M/19N/19O/19P: canonical Testing Mode
backend + Observation→diagnosis→fix→retest + forge projection.
The Behaviour Validation Ledger is DURABLE TRUTH. A testing session is
only coordination state (rule 19N) — pausable, resumable, never the
authority. The queue is DYNAMIC (rule 19M): a fix for behaviour B may
invalidate confirmed behaviour A via structural closure; unrelated
hardened behaviours stay trusted.
Forge projection (19P): failures in active iteration create/update a
human-visible issue through the T18 forge boundary. Forge failure never
rewrites canonical truth.
"""
from __future__ import annotations
import time
from pathlib import Path
from rune.behaviour_ledger import BehaviourLedger, TEST_FAILED, USER_CONFIRMED
from rune.capability_ledger import CapabilityLedger
from rune.project_registry import ProjectRegistry
SESSION_SCHEMA_VERSION = 1
def _now() -> str:
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
# ── 19N: session = coordination only ─────────────────────────────────────
class TestingSession:
def __init__(self, store_dir: Path | str, *, project: str = "",
scope_component: str | None = None):
self.path = Path(store_dir) / "testing_session.json"
self.store_dir = Path(store_dir)
self.state = self._load(project, scope_component)
def _load(self, project, scope_component) -> dict:
import json
if self.path.is_file():
return json.loads(self.path.read_text())
return {"schema_version": SESSION_SCHEMA_VERSION,
"project": project, "scope_component": scope_component,
"current_item": None, "completed_this_session": [],
"newly_invalidated": [], "blockers": [],
"paused": False, "updated_at": _now()}
def save(self) -> None:
import json
self.state["updated_at"] = _now()
self.path.write_text(json.dumps(self.state, indent=1,
sort_keys=True))
# ── 19L: next eligible item ──────────────────────────────────────
def next_item(self, behaviours: BehaviourLedger) -> dict | None:
if self.state["paused"]:
return None
outstanding = behaviours.outstanding(
component=self.state.get("scope_component"),
project=self.state.get("project") or None)
item = outstanding[0] if outstanding else None
self.state["current_item"] = (item["behaviour_id"]
if item else None)
return item
# ── 19M: record result; dynamic queue re-derives from states ─────
def record(self, behaviours: BehaviourLedger, behaviour_id: str, *,
passed: bool, detail: str = "", by: str = "user",
generation: str = "") -> dict:
rec = behaviours.record_real_world(
behaviour_id, passed=passed, detail=detail, confirmed_by=by,
generation=generation)
self.state["completed_this_session"].append(
{"behaviour_id": rec["behaviour_id"], "passed": passed,
"at": _now()})
if passed and rec["state"] == USER_CONFIRMED:
pass # hardened; queue re-derives
self.save()
return rec
def pause(self) -> None:
self.state["paused"] = True
self.save()
def resume(self) -> None:
self.state["paused"] = False
self.save()
def note_blocker(self, behaviour_id: str, note: str) -> None:
self.state["blockers"].append({"behaviour_id": behaviour_id,
"note": note[:200], "at": _now()})
self.save()
# ── 19O: Observation → diagnosis → fix → retest ──────────────────────────
def record_observation(store_dir: Path | str, *, observed: str,
expected: str, behaviour_id: str = "",
environment: str = "", reproduction: str = "",
candidate_areas: list[str] | None = None,
confidence: str = "low") -> dict:
"""Durable real-world failure report. Candidate areas are
HYPOTHESES, not declared root cause (rule 19O)."""
import json
obs = {
"observation_id": f"obs-{int(time.time())}",
"observed": observed, "expected": expected,
"behaviour_id": behaviour_id,
"environment": environment, "reproduction": reproduction,
"candidate_areas": list(candidate_areas or []), # hypotheses
"confidence": confidence,
"created_at": _now(),
"closure": None, # set by close_observation
}
p = Path(store_dir) / "observations.jsonl"
with p.open("a") as f:
f.write(json.dumps(obs, sort_keys=True) + "\n")
return obs
def close_observation(store_dir: Path | str, observation_id: str, *,
behaviour_retest_passed: bool,
user_confirmed: bool,
contract_ref: str = "") -> dict:
"""Closure returns to the ORIGINAL observed scenario (rule 19O):
code changed ≠ closure; automated green ≠ closure. The observation
closes only when the behaviour was retested AND user-confirmed."""
import json
p = Path(store_dir) / "observations.jsonl"
lines = p.read_text().strip().splitlines()
out = []
result = None
for line in lines:
rec = json.loads(line)
if rec.get("observation_id") == observation_id:
rec["closure"] = {
"behaviour_retest_passed": behaviour_retest_passed,
"user_confirmed": user_confirmed,
"contract_ref": contract_ref,
"closed": behaviour_retest_passed and user_confirmed,
"at": _now()}
result = rec
out.append(json.dumps(rec, sort_keys=True))
p.write_text("\n".join(out) + "\n")
if result is None:
raise ValueError(f"unknown observation {observation_id!r}")
return result
# ── 19P: forge projection through the T18 boundary ───────────────────────
def project_failure_to_forge(forge_adapter, repo: str, *,
behaviour_id: str, observation: dict,
dry_run: bool = False) -> dict:
"""Create/update a human-visible issue for an active failure
iteration. The forge record is a PROJECTION — canonical truth stays
in the Behaviour Ledger. Forge failure never rewrites truth
(rule 19P/19S-14): exceptions propagate to the caller as
ForgeUnavailable; the ledger state is untouched."""
title = f"[testing] {behaviour_id}: {observation.get('expected', '')[:60]}"
body = (f"Observed: {observation.get('observed', '')}\n"
f"Expected: {observation.get('expected', '')}\n"
f"Behaviour: {behaviour_id}\n"
f"Observation: {observation.get('observation_id')}\n"
f"Reproduction: {observation.get('reproduction', '')}")
if dry_run:
return {"projected": False, "dry_run": True, "title": title}
return forge_adapter.create_or_patch_release(
repo, tag=f"testing-{behaviour_id}", title=title, body=body)
# ── 19L: aggregate backend ────────────────────────────────────────────────
def aggregate(registry: ProjectRegistry, store_root: Path) -> list[dict]:
"""Outstanding validation across ALL registered scaffolded projects
(the /testing aggregate)."""
out = []
for entry in registry.all_projects():
loc = Path(entry["scaffold_location"])
if not (loc / "behaviour_ledger.json").is_file():
out.append({"project": entry["project_id"],
"outstanding": None,
"note": "no behaviour ledger yet"})
continue
beh = BehaviourLedger(loc)
s = beh.summary()
registry.set_outstanding_validations(entry["project_id"],
s["outstanding"])
out.append({"project": entry["project_id"],
"outstanding": s["outstanding"],
"states": s["states"]})
registry.save()
return out
+35 -2
View File
@@ -999,6 +999,34 @@ def _search_files(args: dict) -> dict:
return {"success": False, "error": f"Search failed: {e}"}
def _resolve_execution_cwd(args: dict) -> tuple[str, str]:
"""T21.4 §21.4E: automatic project execution context.
Precedence (tranche-mandated):
explicit governed workdir > Project Registry repo/worktree for the
named project > process cwd (project-less commands only).
The model names the PROJECT, never the cwd — the execution layer
derives the correct repository/worktree via ProjectRegistry.resolve_cwd
(which already honours active_worktree > repo_identity, and refuses
scaffold_location as an execution cwd). An unknown or unresolvable
project is REFUSED before any execution.
"""
workdir = args.get("workdir")
if workdir:
return str(workdir), "explicit"
project = args.get("project")
if project:
from rune.project_registry import ProjectRegistry
store = os.environ.get("RUNE_SCAFFOLD_STORE", "store/scaffold")
registry = ProjectRegistry(store)
try:
return registry.resolve_cwd(str(project)), "project_registry"
except ValueError as exc:
raise ValueError(f"project execution refused: {exc}") from exc
return os.getcwd(), "process"
def _run_command(args: dict) -> dict:
"""Run an approved command, using argv unless shell mode is explicit."""
import subprocess
@@ -1006,7 +1034,11 @@ def _run_command(args: dict) -> dict:
command = args.get("command")
argv = args.get("argv")
shell = args.get("shell", False)
workdir = args.get("workdir", os.getcwd())
try:
workdir, cwd_source = _resolve_execution_cwd(args)
except ValueError as exc:
return {"success": False, "error": str(exc),
"cwd_source": "refused"}
if shell is not True:
if argv is None:
@@ -1037,7 +1069,8 @@ def _run_command(args: dict) -> dict:
output += f"\n[exit code: {result.returncode}]"
if len(output) > 10000:
output = output[:10000] + "\n[truncated]"
return {"success": True, "result": output or "(no output)"}
return {"success": True, "result": output or "(no output)",
"cwd_source": cwd_source}
except subprocess.TimeoutExpired:
return {"success": False, "error": "Command timed out (30s)"}
except Exception as e:
+116
View File
@@ -0,0 +1,116 @@
"""wait_guard.py — T21 §21B: the sleep→poll regression detector.
Mirror of the MiMo Code controller guard (~/.mimocode/hooks/
event-wait-guard.ts) in Python, so the CLASSIFICATION LOGIC is
regression-tested in the Rune suite (rule 21B-4: the fix must survive
model changes, compaction, provider swaps).
Detection: a command SEGMENT (split on ; | newline && ||) that combines
a long sleep (>= 30s) with a status query of CI/deploy/release state.
Heredoc bodies are stripped first (v2 precision fix — documentation
about the pattern must not trip the guard).
Classification vocabulary (21B-1):
USER_REQUESTED_DELAY bare sleep, user's explicit choice
RATE_LIMIT_BACKOFF short bounded backoff (<30s)
RETRY_BACKOFF bounded retry loop inside a script
WATCHDOG deadline-based failure detection
EVENT_FALLBACK_WITH_REASON declared no-event-source fallback
SCHEDULED_TIMER cron/scheduled work
UNJUSTIFIED_SLEEP DEFECT: sleep+poll with an event source available
"""
from __future__ import annotations
import re
LONG_SLEEP_RE = re.compile(r"\bsleep\s+(\d{2,4})\b")
# T21.2 §21.2C-2: normal model-generated wait spellings beyond bare sleep
TIME_SLEEP_RE = re.compile(r"time\.sleep\((\d{2,4})\)")
FROM_TIME_SLEEP_RE = re.compile(r"from\s+time\s+import.*sleep")
SLEEP_CALL_RE = re.compile(r"sleep\((\d{2,4})\)")
POLL_LOOP_RE = re.compile(
r"(while|until)\s+.*(status|success|failure|state|conclusion)",
re.IGNORECASE,
)
STATUS_POLL_RE = re.compile(
"|".join(
(
r"actions/runs",
r"commits/.*/status",
r"required.checks",
r"deploy_qualify",
r"releases",
r"readlink.*rune-code/current",
r"git\s+fetch",
)
),
re.IGNORECASE,
)
THRESHOLD_SECS = 30
def classify_wait(command: str) -> dict:
"""Classify one shell command's wait behaviour. Returns
{"classification": ..., "evidence": ...}. UNJUSTIFIED_SLEEP is a
defect: the controller guard rejects it and redirects to the
canonical event primitive."""
# T21.2: scan for time.sleep BEFORE heredoc stripping — a
# python heredoc containing time.sleep IS executable code.
if TIME_SLEEP_RE.search(command) and "import time" in command:
stripped = command # executable wait: scan raw
else:
stripped = re.sub(
r"<<-?\s*['\"]?(\w+)['\"]?\n[\s\S]*?\n\1", " [heredoc] ", command
)
segments = [s.strip() for s in
re.split(r"[;|\n]|&&|\|\|", stripped) if s.strip()]
# T21.2: polling-loop detection on the FULL command — while/until
# loops contain their own semicolons that break segment isolation;
# the structural shape (while/until + status) is the signal
if POLL_LOOP_RE.search(command) and STATUS_POLL_RE.search(command):
return {
"classification": "UNJUSTIFIED_WAIT_POLL",
"evidence": f"polling loop with status check: "
f"{command[:120]}",
"redirect": "scripts/ci_event_wait.py --sha <sha>",
}
for i, segment in enumerate(segments):
# T21.2: bare sleep, time.sleep, and polling-loop patterns
m = (LONG_SLEEP_RE.search(segment) or TIME_SLEEP_RE.search(segment)
or SLEEP_CALL_RE.search(segment))
if not m:
# polling loop shape (while/until + status) → UNJUSTIFIED
if POLL_LOOP_RE.search(segment) and STATUS_POLL_RE.search(
" ".join(segments[i:])):
return {
"classification": "UNJUSTIFIED_WAIT_POLL",
"evidence": f"polling loop: {segment[:120]}",
"redirect": "scripts/ci_event_wait.py --sha <sha>",
}
continue
secs = int(m.group(1))
if secs < THRESHOLD_SECS:
continue
# the regression pattern is sleep THEN check: the status poll may
# be ANY number of segments after the sleep. T21.1 root cause
# (live bypass 2026-09-16 12:59Z): a 6-segment multiline
# `sleep 540 / cd / git fetch / ... / curl releases` walked past
# a 3-segment lookahead. Everything after the sleep in THIS
# command is downstream of it.
lookahead = " ".join(segments[i:])
if STATUS_POLL_RE.search(lookahead):
# programmatic waits (time.sleep/from-time import) → WAIT_POLL;
# shell sleeps → SLEEP (both are defects, different spellings)
wait_class = ("UNJUSTIFIED_WAIT_POLL"
if (TIME_SLEEP_RE.search(lookahead)
or SLEEP_CALL_RE.search(lookahead))
else "UNJUSTIFIED_SLEEP")
return {
"classification": wait_class,
"evidence": f"sleep {secs}s then status poll: "
f"{' / '.join(segments[i:i + 2])[:140]}",
"redirect": "scripts/ci_event_wait.py --sha <sha>",
}
if re.match(r"^\s*sleep\s+\d+\s*$", stripped.strip()):
return {"classification": "USER_REQUESTED_DELAY", "evidence": stripped.strip()}
return {"classification": "NO_WAIT", "evidence": ""}
+46
View File
@@ -0,0 +1,46 @@
"""wait_guard_corpus.py — T21.3 §21.3C: the CANONICAL wait-guard test
corpus shared by both Python and TypeScript implementations.
Each case: {id, command, expected_class}. Both the Python
wait_guard.classify_wait() and the TypeScript hook MUST agree on every
case. A change to either implementation that breaks parity fails this
suite.
"""
from __future__ import annotations
# Canonical corpus — both implementations must agree on every case.
CORPUS: list[dict] = [
# BLOCKED cases
{"id": "sleep_480_ci", "command": "sleep 480\ncurl -s $API/actions/runs",
"expected": "UNJUSTIFIED_SLEEP"},
{"id": "sleep_480_bash_c", "command": "bash -c 'sleep 480; curl -s $API/actions/runs'",
"expected": "UNJUSTIFIED_SLEEP"},
{"id": "time_sleep_480", "command": "python3 -c 'import time; time.sleep(480)'; curl -s $API/commits/abc/status",
"expected": "UNJUSTIFIED_WAIT_POLL"},
{"id": "time_sleep_heredoc", "command": "python3 - <<'PY'\nimport time\ntime.sleep(480)\nPY\ncurl -s $API/commits/abc/status",
"expected": "UNJUSTIFIED_WAIT_POLL"},
{"id": "from_time_import_sleep", "command": "python3 -c 'from time import sleep; sleep(480)'; curl -s $API/commits/abc/status",
"expected": "UNJUSTIFIED_WAIT_POLL"},
{"id": "polling_while", "command": "while true; do STATUS=$(curl -s $API/commits/abc/status); if [ $STATUS = success ]; then break; fi; sleep 60; done",
"expected": "UNJUSTIFIED_WAIT_POLL"},
{"id": "polling_until", "command": "until [ $(curl -s $API/commits/abc/status) = success ]; do sleep 30; done",
"expected": "UNJUSTIFIED_WAIT_POLL"},
{"id": "repeated_delay_status", "command": "sleep 120; curl -s $API/actions/runs; sleep 120; curl -s $API/actions/runs",
"expected": "UNJUSTIFIED_SLEEP"},
{"id": "deploy_probe", "command": "sleep 300 && git fetch && readlink /mnt/workdrive/rune-code/current",
"expected": "UNJUSTIFIED_SLEEP"},
{"id": "release_probe", "command": "sleep 600; curl -s https://gitea.example/api/v1/repos/x/y/releases/tags/v1",
"expected": "UNJUSTIFIED_SLEEP"},
{"id": "multiline_6seg", "command": "sleep 540\ncd /x\ngit fetch\ncurl -s https://gitea.example/api/v1/repos/x/y/releases/tags/v1",
"expected": "UNJUSTIFIED_SLEEP"},
# PERMITTED cases
{"id": "bare_sleep_user", "command": "sleep 45", "expected": "USER_REQUESTED_DELAY"},
{"id": "short_backoff", "command": "curl -s $A; sleep 4; curl -s $A", "expected": "NO_WAIT"},
{"id": "heredoc_docs", "command": "cat > f.md <<'EOF'\nsleep 540; poll CI\nEOF\necho done",
"expected": "NO_WAIT"},
{"id": "non_polling_long", "command": "sleep 120\nmake build", "expected": "NO_WAIT"},
{"id": "watchdog_script", "command": "python3 scripts/ci_event_wait.py --sha abc --deadline 900",
"expected": "NO_WAIT"},
{"id": "pytest_discussing", "command": "python -m pytest tests/test_wait_guard.py -q",
"expected": "NO_WAIT"},
]
+21
View File
@@ -0,0 +1,21 @@
{"action": "ContinuePendingOperation", "at": "2026-09-17T20:50:30Z", "authority": "ci_runs", "deadline_provenance": "GOVERNED_OPERATION_POLICY", "deadline_secs": 1800, "matched_by": "pending_match", "model_deadline_ignored": true, "model_op_type_ignored": true, "normalized": true, "op_type": "branch_ci", "operation_id": "ci-756ebe6f45ad83a919738539a1565c17ed8572ac"}
{"action": "ContinuePendingOperation", "at": "2026-09-17T20:51:20Z", "authority": "deploy_qualify", "deadline_provenance": "GOVERNED_OPERATION_POLICY", "deadline_secs": 1800, "evidence": "commit_ancestor_of_master", "model_deadline_ignored": true, "model_op_type_ignored": true, "normalized": true, "op_type": "master_ci", "operation_id": "ci-f7256aa29e8bc7aa365ad7ffa27eb557528ccca5"}
{"action": "ContinuePendingOperation", "at": "2026-09-17T21:50:39Z", "authority": "ci_runs", "deadline_provenance": "GOVERNED_OPERATION_POLICY", "deadline_secs": 1800, "evidence": "commit_on_feature_branch", "model_deadline_ignored": true, "model_op_type_ignored": true, "normalized": true, "op_type": "branch_ci", "operation_id": "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c"}
{"action": "ContinuePendingOperation", "at": "2026-09-17T21:53:34Z", "authority": "ci_runs", "deadline_provenance": "GOVERNED_OPERATION_POLICY", "deadline_secs": 1800, "evidence": "commit_on_feature_branch", "model_deadline_ignored": false, "model_op_type_ignored": false, "normalized": true, "op_type": "branch_ci", "operation_id": "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c"}
{"action": "ContinuePendingOperation", "at": "2026-09-17T21:55:05Z", "authority": "ci_runs", "deadline_provenance": "GOVERNED_OPERATION_POLICY", "deadline_secs": 1800, "evidence": "commit_on_feature_branch", "model_deadline_ignored": false, "model_op_type_ignored": false, "normalized": true, "op_type": "branch_ci", "operation_id": "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c"}
{"action": "ContinuePendingOperation", "at": "2026-09-17T23:19:18Z", "authority": "ci_runs", "deadline_provenance": "GOVERNED_OPERATION_POLICY", "deadline_secs": 1800, "matched_by": "sleep_poll_intent", "model_deadline_ignored": true, "model_op_type_ignored": false, "normalized": true, "op_type": "branch_ci", "operation_id": "ci-756ebe6f45ad83a919738539a1565c17ed8572ac", "raw_command_kind": "sleep_poll", "raw_sleep_seconds_suppressed": 150}
{"action": "ContinuePendingOperation", "at": "2026-09-18T10:30:10Z", "authority": "ci_runs", "deadline_provenance": "GOVERNED_OPERATION_POLICY", "deadline_secs": 1800, "evidence": "commit_on_feature_branch", "model_deadline_ignored": true, "model_op_type_ignored": true, "normalized": true, "op_type": "branch_ci", "operation_id": "ci-427c3ac6bb3fd67492d3596a33919d1e5aa4a8d1"}
{"action": "ContinuePendingOperation", "at": "2026-09-18T10:30:40Z", "authority": "ci_runs", "deadline_provenance": "GOVERNED_OPERATION_POLICY", "deadline_secs": 1800, "evidence": "commit_on_feature_branch", "model_deadline_ignored": true, "model_op_type_ignored": false, "normalized": true, "op_type": "branch_ci", "operation_id": "ci-427c3ac6bb3fd67492d3596a33919d1e5aa4a8d1", "raw_command_kind": "sleep_poll", "raw_sleep_seconds_suppressed": 240}
{"at": "2026-09-18T20:23:58Z", "candidates": ["", "", "", "", "", "", "", "", "", "", "", "", "", "op-latency-proof", "dbg-op", "dbg3", "t15-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-resume-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-BUILDER-2026-09-15T03:11:05Z-Verboo Code/glm-5.3-flash", "t15-release-196cf19", "", "", "ci-c707a81933f7f90eaaae4000282674b6a2e3cb4c", "ci-4a06d9f11a722b7aa32fc38ab92b3ba8130414af", "ci-b51afea0cbfe00b5493647ed61d071fe104b306a", "", "ci-986b450bc22e969cbd2f09fdcfbc24e4337740ba", "ci-2a4f9c8367d06acbda4b39c1cee388796b25cc85", "ci-fe9d154778c117e8b0f1fbae4e5e7d9121546839", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-repro-test-sha", "", "", "", "", "", "", "", "", "", "", "ci-3c54782634993361bbab3c66f948fc59121865a5", "ci-3c54782634993361bbab3c66f948fc59121865a5", "", "", "", "ci-dcead3feccb57b6dfb30effd96fcac6b563f8099", "ci-ab2dfe7b09c154a329c5145da4d32639a83579b6", "ci-95946b6efd883e56797134617d42daed1acf798a", "ci-45869a77cdea1c2ce9e5c1f54c9840c3acef1487", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-5d20dc7f513b740a165952ca00f758660d328c43", "ci-0a470d86b80baefeb71c795626a901fac19bf731", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-40b3731928f499a77653613cb6c3e6db93cb64a1", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-00013f8c0b174011bcea12351facf7f1f382e8a3", "ci-756ebe6f45ad83a919738539a1565c17ed8572ac", "ci-df8262bd58122e33d4c447dc863e39ddde684963", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed8d5cf2ae99554eb02b95771082e449c8", "ci-7cf7c8d0a9c4aa7c6c1a7c2e0a2e7c3b4c5d6e7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7008743a1a7f8c1b9e4b0b8e4c4f4f4f4f4f4f4", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-26e37653e3f8c1b9e4b0b8e4c4f4f4f4f4f4f4f", "ci-26e3765319bc29064f7d42f092bc86dcc7b2ecc0", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-5db8b202d97e4721369aa507e2bea7b1cf66af5c", "ci-04a277f7668e99d56c1b7c05e2e40a1da93dca55", "ci-5718a3ea85c397434595e79b7a63700617c1de21", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-f714120", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-853cf3e0ae10ad3d45cd879527093368ccad8251", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd59bb9696d329c998cd58a221edd72", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-2e05ea6", "ci-d53bb9a", "ci-d53bb9a53Line truncated
{"at": "2026-09-19T04:42:44Z", "candidates": ["", "", "", "", "", "", "", "", "", "", "", "", "", "op-latency-proof", "dbg-op", "dbg3", "t15-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-resume-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-BUILDER-2026-09-15T03:11:05Z-Verboo Code/glm-5.3-flash", "t15-release-196cf19", "", "", "ci-c707a81933f7f90eaaae4000282674b6a2e3cb4c", "ci-4a06d9f11a722b7aa32fc38ab92b3ba8130414af", "ci-b51afea0cbfe00b5493647ed61d071fe104b306a", "", "ci-986b450bc22e969cbd2f09fdcfbc24e4337740ba", "ci-2a4f9c8367d06acbda4b39c1cee388796b25cc85", "ci-fe9d154778c117e8b0f1fbae4e5e7d9121546839", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-repro-test-sha", "", "", "", "", "", "", "", "", "", "", "ci-3c54782634993361bbab3c66f948fc59121865a5", "ci-3c54782634993361bbab3c66f948fc59121865a5", "", "", "", "ci-dcead3feccb57b6dfb30effd96fcac6b563f8099", "ci-ab2dfe7b09c154a329c5145da4d32639a83579b6", "ci-95946b6efd883e56797134617d42daed1acf798a", "ci-45869a77cdea1c2ce9e5c1f54c9840c3acef1487", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-5d20dc7f513b740a165952ca00f758660d328c43", "ci-0a470d86b80baefeb71c795626a901fac19bf731", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-40b3731928f499a77653613cb6c3e6db93cb64a1", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-00013f8c0b174011bcea12351facf7f1f382e8a3", "ci-756ebe6f45ad83a919738539a1565c17ed8572ac", "ci-df8262bd58122e33d4c447dc863e39ddde684963", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed8d5cf2ae99554eb02b95771082e449c8", "ci-7cf7c8d0a9c4aa7c6c1a7c2e0a2e7c3b4c5d6e7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7008743a1a7f8c1b9e4b0b8e4c4f4f4f4f4f4f4", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-26e37653e3f8c1b9e4b0b8e4c4f4f4f4f4f4f4f", "ci-26e3765319bc29064f7d42f092bc86dcc7b2ecc0", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-5db8b202d97e4721369aa507e2bea7b1cf66af5c", "ci-04a277f7668e99d56c1b7c05e2e40a1da93dca55", "ci-5718a3ea85c397434595e79b7a63700617c1de21", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-f714120", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-853cf3e0ae10ad3d45cd879527093368ccad8251", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd59bb9696d329c998cd58a221edd72", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-2e05ea6", "ci-d53bb9a", "ci-d53bb9a53Line truncated
{"at": "2026-09-19T04:44:06Z", "candidates": ["", "", "", "", "", "", "", "", "", "", "", "", "", "op-latency-proof", "dbg-op", "dbg3", "t15-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-resume-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-BUILDER-2026-09-15T03:11:05Z-Verboo Code/glm-5.3-flash", "t15-release-196cf19", "", "", "ci-c707a81933f7f90eaaae4000282674b6a2e3cb4c", "ci-4a06d9f11a722b7aa32fc38ab92b3ba8130414af", "ci-b51afea0cbfe00b5493647ed61d071fe104b306a", "", "ci-986b450bc22e969cbd2f09fdcfbc24e4337740ba", "ci-2a4f9c8367d06acbda4b39c1cee388796b25cc85", "ci-fe9d154778c117e8b0f1fbae4e5e7d9121546839", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-repro-test-sha", "", "", "", "", "", "", "", "", "", "", "ci-3c54782634993361bbab3c66f948fc59121865a5", "ci-3c54782634993361bbab3c66f948fc59121865a5", "", "", "", "ci-dcead3feccb57b6dfb30effd96fcac6b563f8099", "ci-ab2dfe7b09c154a329c5145da4d32639a83579b6", "ci-95946b6efd883e56797134617d42daed1acf798a", "ci-45869a77cdea1c2ce9e5c1f54c9840c3acef1487", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-5d20dc7f513b740a165952ca00f758660d328c43", "ci-0a470d86b80baefeb71c795626a901fac19bf731", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-40b3731928f499a77653613cb6c3e6db93cb64a1", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-00013f8c0b174011bcea12351facf7f1f382e8a3", "ci-756ebe6f45ad83a919738539a1565c17ed8572ac", "ci-df8262bd58122e33d4c447dc863e39ddde684963", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed8d5cf2ae99554eb02b95771082e449c8", "ci-7cf7c8d0a9c4aa7c6c1a7c2e0a2e7c3b4c5d6e7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7008743a1a7f8c1b9e4b0b8e4c4f4f4f4f4f4f4", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-26e37653e3f8c1b9e4b0b8e4c4f4f4f4f4f4f4f", "ci-26e3765319bc29064f7d42f092bc86dcc7b2ecc0", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-5db8b202d97e4721369aa507e2bea7b1cf66af5c", "ci-04a277f7668e99d56c1b7c05e2e40a1da93dca55", "ci-5718a3ea85c397434595e79b7a63700617c1de21", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-f714120", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-853cf3e0ae10ad3d45cd879527093368ccad8251", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd59bb9696d329c998cd58a221edd72", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-2e05ea6", "ci-d53bb9a", "ci-d53bb9a53Line truncated
{"at": "2026-09-19T04:46:33Z", "candidates": ["", "", "", "", "", "", "", "", "", "", "", "", "", "op-latency-proof", "dbg-op", "dbg3", "t15-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-resume-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-BUILDER-2026-09-15T03:11:05Z-Verboo Code/glm-5.3-flash", "t15-release-196cf19", "", "", "ci-c707a81933f7f90eaaae4000282674b6a2e3cb4c", "ci-4a06d9f11a722b7aa32fc38ab92b3ba8130414af", "ci-b51afea0cbfe00b5493647ed61d071fe104b306a", "", "ci-986b450bc22e969cbd2f09fdcfbc24e4337740ba", "ci-2a4f9c8367d06acbda4b39c1cee388796b25cc85", "ci-fe9d154778c117e8b0f1fbae4e5e7d9121546839", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-repro-test-sha", "", "", "", "", "", "", "", "", "", "", "ci-3c54782634993361bbab3c66f948fc59121865a5", "ci-3c54782634993361bbab3c66f948fc59121865a5", "", "", "", "ci-dcead3feccb57b6dfb30effd96fcac6b563f8099", "ci-ab2dfe7b09c154a329c5145da4d32639a83579b6", "ci-95946b6efd883e56797134617d42daed1acf798a", "ci-45869a77cdea1c2ce9e5c1f54c9840c3acef1487", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-5d20dc7f513b740a165952ca00f758660d328c43", "ci-0a470d86b80baefeb71c795626a901fac19bf731", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-40b3731928f499a77653613cb6c3e6db93cb64a1", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-00013f8c0b174011bcea12351facf7f1f382e8a3", "ci-756ebe6f45ad83a919738539a1565c17ed8572ac", "ci-df8262bd58122e33d4c447dc863e39ddde684963", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed8d5cf2ae99554eb02b95771082e449c8", "ci-7cf7c8d0a9c4aa7c6c1a7c2e0a2e7c3b4c5d6e7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7008743a1a7f8c1b9e4b0b8e4c4f4f4f4f4f4f4", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-26e37653e3f8c1b9e4b0b8e4c4f4f4f4f4f4f4f", "ci-26e3765319bc29064f7d42f092bc86dcc7b2ecc0", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-5db8b202d97e4721369aa507e2bea7b1cf66af5c", "ci-04a277f7668e99d56c1b7c05e2e40a1da93dca55", "ci-5718a3ea85c397434595e79b7a63700617c1de21", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-f714120", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-853cf3e0ae10ad3d45cd879527093368ccad8251", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd59bb9696d329c998cd58a221edd72", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-2e05ea6", "ci-d53bb9a", "ci-d53bb9a53Line truncated
{"at": "2026-09-19T04:49:08Z", "candidates": ["", "", "", "", "", "", "", "", "", "", "", "", "", "op-latency-proof", "dbg-op", "dbg3", "t15-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-resume-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-BUILDER-2026-09-15T03:11:05Z-Verboo Code/glm-5.3-flash", "t15-release-196cf19", "", "", "ci-c707a81933f7f90eaaae4000282674b6a2e3cb4c", "ci-4a06d9f11a722b7aa32fc38ab92b3ba8130414af", "ci-b51afea0cbfe00b5493647ed61d071fe104b306a", "", "ci-986b450bc22e969cbd2f09fdcfbc24e4337740ba", "ci-2a4f9c8367d06acbda4b39c1cee388796b25cc85", "ci-fe9d154778c117e8b0f1fbae4e5e7d9121546839", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-repro-test-sha", "", "", "", "", "", "", "", "", "", "", "ci-3c54782634993361bbab3c66f948fc59121865a5", "ci-3c54782634993361bbab3c66f948fc59121865a5", "", "", "", "ci-dcead3feccb57b6dfb30effd96fcac6b563f8099", "ci-ab2dfe7b09c154a329c5145da4d32639a83579b6", "ci-95946b6efd883e56797134617d42daed1acf798a", "ci-45869a77cdea1c2ce9e5c1f54c9840c3acef1487", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-5d20dc7f513b740a165952ca00f758660d328c43", "ci-0a470d86b80baefeb71c795626a901fac19bf731", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-40b3731928f499a77653613cb6c3e6db93cb64a1", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-00013f8c0b174011bcea12351facf7f1f382e8a3", "ci-756ebe6f45ad83a919738539a1565c17ed8572ac", "ci-df8262bd58122e33d4c447dc863e39ddde684963", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed8d5cf2ae99554eb02b95771082e449c8", "ci-7cf7c8d0a9c4aa7c6c1a7c2e0a2e7c3b4c5d6e7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7008743a1a7f8c1b9e4b0b8e4c4f4f4f4f4f4f4", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-26e37653e3f8c1b9e4b0b8e4c4f4f4f4f4f4f4f", "ci-26e3765319bc29064f7d42f092bc86dcc7b2ecc0", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-5db8b202d97e4721369aa507e2bea7b1cf66af5c", "ci-04a277f7668e99d56c1b7c05e2e40a1da93dca55", "ci-5718a3ea85c397434595e79b7a63700617c1de21", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-f714120", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-853cf3e0ae10ad3d45cd879527093368ccad8251", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd59bb9696d329c998cd58a221edd72", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-2e05ea6", "ci-d53bb9a", "ci-d53bb9a53Line truncated
{"at": "2026-09-19T04:52:18Z", "candidates": ["", "", "", "", "", "", "", "", "", "", "", "", "", "op-latency-proof", "dbg-op", "dbg3", "t15-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-resume-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-BUILDER-2026-09-15T03:11:05Z-Verboo Code/glm-5.3-flash", "t15-release-196cf19", "", "", "ci-c707a81933f7f90eaaae4000282674b6a2e3cb4c", "ci-4a06d9f11a722b7aa32fc38ab92b3ba8130414af", "ci-b51afea0cbfe00b5493647ed61d071fe104b306a", "", "ci-986b450bc22e969cbd2f09fdcfbc24e4337740ba", "ci-2a4f9c8367d06acbda4b39c1cee388796b25cc85", "ci-fe9d154778c117e8b0f1fbae4e5e7d9121546839", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-repro-test-sha", "", "", "", "", "", "", "", "", "", "", "ci-3c54782634993361bbab3c66f948fc59121865a5", "ci-3c54782634993361bbab3c66f948fc59121865a5", "", "", "", "ci-dcead3feccb57b6dfb30effd96fcac6b563f8099", "ci-ab2dfe7b09c154a329c5145da4d32639a83579b6", "ci-95946b6efd883e56797134617d42daed1acf798a", "ci-45869a77cdea1c2ce9e5c1f54c9840c3acef1487", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-5d20dc7f513b740a165952ca00f758660d328c43", "ci-0a470d86b80baefeb71c795626a901fac19bf731", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-40b3731928f499a77653613cb6c3e6db93cb64a1", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-00013f8c0b174011bcea12351facf7f1f382e8a3", "ci-756ebe6f45ad83a919738539a1565c17ed8572ac", "ci-df8262bd58122e33d4c447dc863e39ddde684963", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed8d5cf2ae99554eb02b95771082e449c8", "ci-7cf7c8d0a9c4aa7c6c1a7c2e0a2e7c3b4c5d6e7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7008743a1a7f8c1b9e4b0b8e4c4f4f4f4f4f4f4", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-26e37653e3f8c1b9e4b0b8e4c4f4f4f4f4f4f4f", "ci-26e3765319bc29064f7d42f092bc86dcc7b2ecc0", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-5db8b202d97e4721369aa507e2bea7b1cf66af5c", "ci-04a277f7668e99d56c1b7c05e2e40a1da93dca55", "ci-5718a3ea85c397434595e79b7a63700617c1de21", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-f714120", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-853cf3e0ae10ad3d45cd879527093368ccad8251", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd59bb9696d329c998cd58a221edd72", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-2e05ea6", "ci-d53bb9a", "ci-d53bb9a53Line truncated
{"at": "2026-09-19T04:54:51Z", "candidates": ["", "", "", "", "", "", "", "", "", "", "", "", "", "op-latency-proof", "dbg-op", "dbg3", "t15-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-resume-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-BUILDER-2026-09-15T03:11:05Z-Verboo Code/glm-5.3-flash", "t15-release-196cf19", "", "", "ci-c707a81933f7f90eaaae4000282674b6a2e3cb4c", "ci-4a06d9f11a722b7aa32fc38ab92b3ba8130414af", "ci-b51afea0cbfe00b5493647ed61d071fe104b306a", "", "ci-986b450bc22e969cbd2f09fdcfbc24e4337740ba", "ci-2a4f9c8367d06acbda4b39c1cee388796b25cc85", "ci-fe9d154778c117e8b0f1fbae4e5e7d9121546839", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-repro-test-sha", "", "", "", "", "", "", "", "", "", "", "ci-3c54782634993361bbab3c66f948fc59121865a5", "ci-3c54782634993361bbab3c66f948fc59121865a5", "", "", "", "ci-dcead3feccb57b6dfb30effd96fcac6b563f8099", "ci-ab2dfe7b09c154a329c5145da4d32639a83579b6", "ci-95946b6efd883e56797134617d42daed1acf798a", "ci-45869a77cdea1c2ce9e5c1f54c9840c3acef1487", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-5d20dc7f513b740a165952ca00f758660d328c43", "ci-0a470d86b80baefeb71c795626a901fac19bf731", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-40b3731928f499a77653613cb6c3e6db93cb64a1", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-00013f8c0b174011bcea12351facf7f1f382e8a3", "ci-756ebe6f45ad83a919738539a1565c17ed8572ac", "ci-df8262bd58122e33d4c447dc863e39ddde684963", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed8d5cf2ae99554eb02b95771082e449c8", "ci-7cf7c8d0a9c4aa7c6c1a7c2e0a2e7c3b4c5d6e7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7008743a1a7f8c1b9e4b0b8e4c4f4f4f4f4f4f4", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-26e37653e3f8c1b9e4b0b8e4c4f4f4f4f4f4f4f", "ci-26e3765319bc29064f7d42f092bc86dcc7b2ecc0", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-5db8b202d97e4721369aa507e2bea7b1cf66af5c", "ci-04a277f7668e99d56c1b7c05e2e40a1da93dca55", "ci-5718a3ea85c397434595e79b7a63700617c1de21", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-f714120", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-853cf3e0ae10ad3d45cd879527093368ccad8251", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd59bb9696d329c998cd58a221edd72", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-2e05ea6", "ci-d53bb9a", "ci-d53bb9a53Line truncated
{"at": "2026-09-19T04:57:26Z", "candidates": ["", "", "", "", "", "", "", "", "", "", "", "", "", "op-latency-proof", "dbg-op", "dbg3", "t15-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-resume-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-BUILDER-2026-09-15T03:11:05Z-Verboo Code/glm-5.3-flash", "t15-release-196cf19", "", "", "ci-c707a81933f7f90eaaae4000282674b6a2e3cb4c", "ci-4a06d9f11a722b7aa32fc38ab92b3ba8130414af", "ci-b51afea0cbfe00b5493647ed61d071fe104b306a", "", "ci-986b450bc22e969cbd2f09fdcfbc24e4337740ba", "ci-2a4f9c8367d06acbda4b39c1cee388796b25cc85", "ci-fe9d154778c117e8b0f1fbae4e5e7d9121546839", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-repro-test-sha", "", "", "", "", "", "", "", "", "", "", "ci-3c54782634993361bbab3c66f948fc59121865a5", "ci-3c54782634993361bbab3c66f948fc59121865a5", "", "", "", "ci-dcead3feccb57b6dfb30effd96fcac6b563f8099", "ci-ab2dfe7b09c154a329c5145da4d32639a83579b6", "ci-95946b6efd883e56797134617d42daed1acf798a", "ci-45869a77cdea1c2ce9e5c1f54c9840c3acef1487", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-5d20dc7f513b740a165952ca00f758660d328c43", "ci-0a470d86b80baefeb71c795626a901fac19bf731", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-40b3731928f499a77653613cb6c3e6db93cb64a1", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-00013f8c0b174011bcea12351facf7f1f382e8a3", "ci-756ebe6f45ad83a919738539a1565c17ed8572ac", "ci-df8262bd58122e33d4c447dc863e39ddde684963", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed8d5cf2ae99554eb02b95771082e449c8", "ci-7cf7c8d0a9c4aa7c6c1a7c2e0a2e7c3b4c5d6e7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7008743a1a7f8c1b9e4b0b8e4c4f4f4f4f4f4f4", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-26e37653e3f8c1b9e4b0b8e4c4f4f4f4f4f4f4f", "ci-26e3765319bc29064f7d42f092bc86dcc7b2ecc0", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-5db8b202d97e4721369aa507e2bea7b1cf66af5c", "ci-04a277f7668e99d56c1b7c05e2e40a1da93dca55", "ci-5718a3ea85c397434595e79b7a63700617c1de21", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-f714120", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-853cf3e0ae10ad3d45cd879527093368ccad8251", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd59bb9696d329c998cd58a221edd72", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-2e05ea6", "ci-d53bb9a", "ci-d53bb9a53Line truncated
{"at": "2026-09-19T04:59:46Z", "candidates": ["", "", "", "", "", "", "", "", "", "", "", "", "", "op-latency-proof", "dbg-op", "dbg3", "t15-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-resume-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-BUILDER-2026-09-15T03:11:05Z-Verboo Code/glm-5.3-flash", "t15-release-196cf19", "", "", "ci-c707a81933f7f90eaaae4000282674b6a2e3cb4c", "ci-4a06d9f11a722b7aa32fc38ab92b3ba8130414af", "ci-b51afea0cbfe00b5493647ed61d071fe104b306a", "", "ci-986b450bc22e969cbd2f09fdcfbc24e4337740ba", "ci-2a4f9c8367d06acbda4b39c1cee388796b25cc85", "ci-fe9d154778c117e8b0f1fbae4e5e7d9121546839", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-repro-test-sha", "", "", "", "", "", "", "", "", "", "", "ci-3c54782634993361bbab3c66f948fc59121865a5", "ci-3c54782634993361bbab3c66f948fc59121865a5", "", "", "", "ci-dcead3feccb57b6dfb30effd96fcac6b563f8099", "ci-ab2dfe7b09c154a329c5145da4d32639a83579b6", "ci-95946b6efd883e56797134617d42daed1acf798a", "ci-45869a77cdea1c2ce9e5c1f54c9840c3acef1487", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-5d20dc7f513b740a165952ca00f758660d328c43", "ci-0a470d86b80baefeb71c795626a901fac19bf731", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-40b3731928f499a77653613cb6c3e6db93cb64a1", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-00013f8c0b174011bcea12351facf7f1f382e8a3", "ci-756ebe6f45ad83a919738539a1565c17ed8572ac", "ci-df8262bd58122e33d4c447dc863e39ddde684963", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed8d5cf2ae99554eb02b95771082e449c8", "ci-7cf7c8d0a9c4aa7c6c1a7c2e0a2e7c3b4c5d6e7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7008743a1a7f8c1b9e4b0b8e4c4f4f4f4f4f4f4", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-26e37653e3f8c1b9e4b0b8e4c4f4f4f4f4f4f4f", "ci-26e3765319bc29064f7d42f092bc86dcc7b2ecc0", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-5db8b202d97e4721369aa507e2bea7b1cf66af5c", "ci-04a277f7668e99d56c1b7c05e2e40a1da93dca55", "ci-5718a3ea85c397434595e79b7a63700617c1de21", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-f714120", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-853cf3e0ae10ad3d45cd879527093368ccad8251", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd59bb9696d329c998cd58a221edd72", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-2e05ea6", "ci-d53bb9a", "ci-d53bb9a53Line truncated
{"at": "2026-09-19T05:02:08Z", "candidates": ["", "", "", "", "", "", "", "", "", "", "", "", "", "op-latency-proof", "dbg-op", "dbg3", "t15-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-resume-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-BUILDER-2026-09-15T03:11:05Z-Verboo Code/glm-5.3-flash", "t15-release-196cf19", "", "", "ci-c707a81933f7f90eaaae4000282674b6a2e3cb4c", "ci-4a06d9f11a722b7aa32fc38ab92b3ba8130414af", "ci-b51afea0cbfe00b5493647ed61d071fe104b306a", "", "ci-986b450bc22e969cbd2f09fdcfbc24e4337740ba", "ci-2a4f9c8367d06acbda4b39c1cee388796b25cc85", "ci-fe9d154778c117e8b0f1fbae4e5e7d9121546839", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-repro-test-sha", "", "", "", "", "", "", "", "", "", "", "ci-3c54782634993361bbab3c66f948fc59121865a5", "ci-3c54782634993361bbab3c66f948fc59121865a5", "", "", "", "ci-dcead3feccb57b6dfb30effd96fcac6b563f8099", "ci-ab2dfe7b09c154a329c5145da4d32639a83579b6", "ci-95946b6efd883e56797134617d42daed1acf798a", "ci-45869a77cdea1c2ce9e5c1f54c9840c3acef1487", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-5d20dc7f513b740a165952ca00f758660d328c43", "ci-0a470d86b80baefeb71c795626a901fac19bf731", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-40b3731928f499a77653613cb6c3e6db93cb64a1", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-00013f8c0b174011bcea12351facf7f1f382e8a3", "ci-756ebe6f45ad83a919738539a1565c17ed8572ac", "ci-df8262bd58122e33d4c447dc863e39ddde684963", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed8d5cf2ae99554eb02b95771082e449c8", "ci-7cf7c8d0a9c4aa7c6c1a7c2e0a2e7c3b4c5d6e7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7008743a1a7f8c1b9e4b0b8e4c4f4f4f4f4f4f4", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-26e37653e3f8c1b9e4b0b8e4c4f4f4f4f4f4f4f", "ci-26e3765319bc29064f7d42f092bc86dcc7b2ecc0", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-5db8b202d97e4721369aa507e2bea7b1cf66af5c", "ci-04a277f7668e99d56c1b7c05e2e40a1da93dca55", "ci-5718a3ea85c397434595e79b7a63700617c1de21", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-f714120", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-853cf3e0ae10ad3d45cd879527093368ccad8251", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd59bb9696d329c998cd58a221edd72", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-2e05ea6", "ci-d53bb9a", "ci-d53bb9a53Line truncated
{"action": "ContinuePendingOperation", "at": "2026-09-19T05:16:37Z", "authority": "ci_runs", "deadline_provenance": "GOVERNED_OPERATION_POLICY", "deadline_secs": 1800, "evidence": "explicit_id_fresh_registration", "model_deadline_ignored": false, "model_op_type_ignored": false, "normalized": true, "op_type": "branch_ci", "operation_id": "ci-c6c18ae041ddac41e714af14ca0ce8c2e918078c"}
{"at": "2026-09-19T05:18:31Z", "candidates": ["", "", "", "", "", "", "", "", "", "", "", "", "", "op-latency-proof", "dbg-op", "dbg3", "t15-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-resume-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-BUILDER-2026-09-15T03:11:05Z-Verboo Code/glm-5.3-flash", "t15-release-196cf19", "", "", "ci-c707a81933f7f90eaaae4000282674b6a2e3cb4c", "ci-4a06d9f11a722b7aa32fc38ab92b3ba8130414af", "ci-b51afea0cbfe00b5493647ed61d071fe104b306a", "", "ci-986b450bc22e969cbd2f09fdcfbc24e4337740ba", "ci-2a4f9c8367d06acbda4b39c1cee388796b25cc85", "ci-fe9d154778c117e8b0f1fbae4e5e7d9121546839", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-repro-test-sha", "", "", "", "", "", "", "", "", "", "", "ci-3c54782634993361bbab3c66f948fc59121865a5", "ci-3c54782634993361bbab3c66f948fc59121865a5", "", "", "", "ci-dcead3feccb57b6dfb30effd96fcac6b563f8099", "ci-ab2dfe7b09c154a329c5145da4d32639a83579b6", "ci-95946b6efd883e56797134617d42daed1acf798a", "ci-45869a77cdea1c2ce9e5c1f54c9840c3acef1487", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-5d20dc7f513b740a165952ca00f758660d328c43", "ci-0a470d86b80baefeb71c795626a901fac19bf731", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-40b3731928f499a77653613cb6c3e6db93cb64a1", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-00013f8c0b174011bcea12351facf7f1f382e8a3", "ci-756ebe6f45ad83a919738539a1565c17ed8572ac", "ci-df8262bd58122e33d4c447dc863e39ddde684963", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed8d5cf2ae99554eb02b95771082e449c8", "ci-7cf7c8d0a9c4aa7c6c1a7c2e0a2e7c3b4c5d6e7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7008743a1a7f8c1b9e4b0b8e4c4f4f4f4f4f4f4", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-26e37653e3f8c1b9e4b0b8e4c4f4f4f4f4f4f4f", "ci-26e3765319bc29064f7d42f092bc86dcc7b2ecc0", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-5db8b202d97e4721369aa507e2bea7b1cf66af5c", "ci-04a277f7668e99d56c1b7c05e2e40a1da93dca55", "ci-5718a3ea85c397434595e79b7a63700617c1de21", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-f714120", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-853cf3e0ae10ad3d45cd879527093368ccad8251", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd59bb9696d329c998cd58a221edd72", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-2e05ea6", "ci-d53bb9a", "ci-d53bb9a53Line truncated
{"at": "2026-09-19T05:20:46Z", "candidates": ["", "", "", "", "", "", "", "", "", "", "", "", "", "op-latency-proof", "dbg-op", "dbg3", "t15-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-resume-BUILDER-2026-09-15T02:18:49Z-Verboo Code/glm-5.3-flash", "t15-BUILDER-2026-09-15T03:11:05Z-Verboo Code/glm-5.3-flash", "t15-release-196cf19", "", "", "ci-c707a81933f7f90eaaae4000282674b6a2e3cb4c", "ci-4a06d9f11a722b7aa32fc38ab92b3ba8130414af", "ci-b51afea0cbfe00b5493647ed61d071fe104b306a", "", "ci-986b450bc22e969cbd2f09fdcfbc24e4337740ba", "ci-2a4f9c8367d06acbda4b39c1cee388796b25cc85", "ci-fe9d154778c117e8b0f1fbae4e5e7d9121546839", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-c9dee14c626b853cab625b431af3b23866b84314", "ci-repro-test-sha", "", "", "", "", "", "", "", "", "", "", "ci-3c54782634993361bbab3c66f948fc59121865a5", "ci-3c54782634993361bbab3c66f948fc59121865a5", "", "", "", "ci-dcead3feccb57b6dfb30effd96fcac6b563f8099", "ci-ab2dfe7b09c154a329c5145da4d32639a83579b6", "ci-95946b6efd883e56797134617d42daed1acf798a", "ci-45869a77cdea1c2ce9e5c1f54c9840c3acef1487", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-98cb3a98a5f2e245cd0fd6003ffd02a238120ddc", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-2ccac89ec448900766d47e322b2723da5b2ce9dd", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-824985488196ba5e5033318b7b2b6cdc82719ed6", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-9e903dacafdbc40dccf17ffb3ea09e8452396860", "ci-5d20dc7f513b740a165952ca00f758660d328c43", "ci-0a470d86b80baefeb71c795626a901fac19bf731", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-116d1ffbeb49c879855b982c89d51db25528537d", "ci-40b3731928f499a77653613cb6c3e6db93cb64a1", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-be6bc17e93d8ffbc2679d913e55dd848dc8fa82e", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-f3ef4672f3209e73bd110fc49efff9834a2eb987", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-6b5d8ad02308f24422b1efd7c0d343ebb5c7a19d", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-ba2966e6318d6f3f1d2260732b1534732d650c0c", "ci-00013f8c0b174011bcea12351facf7f1f382e8a3", "ci-756ebe6f45ad83a919738539a1565c17ed8572ac", "ci-df8262bd58122e33d4c447dc863e39ddde684963", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-4944f9e79d0a9c4df1d76a0d8c119e33e1f9a55", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed7b7b0b8c29f1d27c4d328f4a3a2d1b61", "ci-f545f1ed8d5cf2ae99554eb02b95771082e449c8", "ci-7cf7c8d0a9c4aa7c6c1a7c2e0a2e7c3b4c5d6e7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7cf7c8dd8d82ef2165aedbf456683448a7536bc7", "ci-7008743a1a7f8c1b9e4b0b8e4c4f4f4f4f4f4f4", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-70087436203f8e44181b94676a05418730b8de5d", "ci-26e37653e3f8c1b9e4b0b8e4c4f4f4f4f4f4f4f", "ci-26e3765319bc29064f7d42f092bc86dcc7b2ecc0", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-2b6a5a6b95629d3cf48fd171cbf6a2544f771817", "ci-5db8b202d97e4721369aa507e2bea7b1cf66af5c", "ci-04a277f7668e99d56c1b7c05e2e40a1da93dca55", "ci-5718a3ea85c397434595e79b7a63700617c1de21", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-f714120", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-65001196d831a7f51b2bbc2b6bbcfe34c70442ff", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-53b4830b08a7dd72a1a7236f1752516880a29bc8", "ci-853cf3e0ae10ad3d45cd879527093368ccad8251", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-599c3ab1c23f7eaec323c67b3523fdb9e1acea74", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd50e77ac55db4056951bd6655a0632", "ci-a54ab559cbd59bb9696d329c998cd58a221edd72", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-1c5dc557a69a368aad267d1871f4eda646a822fb", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-05f2a74c6fa086ddec55d146b780859df34d31a5", "ci-2e05ea6", "ci-d53bb9a", "ci-d53bb9a53Line truncated
@@ -0,0 +1,42 @@
# T17 §17T — real-Rune re-entry benchmark (2026-09-15)
Target: the event/wait-resume subsystem. Two fresh-identity agents, no
transcript. A: cold repo exploration. B: docs/STRUCTURAL_MAP.md first,
then targeted reconciliation.
## Measured
| Metric | A cold | B map-assisted |
|---|---|---|
| Files opened | 6 | 4 (+ grep over ~10) |
| Commands run | 14 (11 bash + 3 reads) | 13 (10 bash + 3 grep) |
| Test runs | 1 (19 tests) | 1 (8 tests) |
| Authority identified | correct (durable waitstore record) | correct |
| Ownership identified | correct (out-of-repo bridge) | correct |
| Unfinished/duplication found | CI adapter no production call site; bridge unversioned | CI coverage gap; TWO data roots (split-brain risk); t14 legacy; build_mode wired |
| Wrong assumptions | **1 material**: claimed daemon DEAD with stale pidfile (false — PID 287505 live, /health ok) | 0 material; flagged env-pinning dependency |
| Map reconciliation | n/a | 5 CONFIRMED, 1 UNVERIFIABLE, 0 DRIFTED |
| Confidence | 80-95% | high/medium, calibrated |
## Honest verdict
1. **Raw cost ≈ equal** (6 vs 4 files, 14 vs 13 commands) — consistent
with 16O: no step-count win on a well-structured repo.
2. **Correctness gap is real**: the cold path asserted a FALSE runtime
fact (daemon dead) from a stale pidfile and reported it at 95%
confidence; the map-assisted path, knowing the daemon's deployment
shape from the map, verified liveness and reported the env-pinning
dependency instead. One false belief about a LIVE system is exactly
the failure mode re-entry exists to prevent.
3. **Coverage gap**: the map-assisted path found the two-data-roots
split-brain risk (legacy ~/.local/share/mimo-wait-resume/waits still
holds 46 records; wr_root() defaults there when RUNE_WR_ROOT unset);
the cold path missed it.
4. The map itself survived reconciliation with ZERO drifted claims —
maintained structural projections are earning their update cost.
## Actionable finding (fed to 17U/W)
The two-data-roots finding is real debt: `wr_root()` (event_wait.py)
falls back to the legacy home when RUNE_WR_ROOT is unset. Production
pins it (deploy.yml), but any unpinned consumer silently reads a stale
store. Candidate: drop the legacy fallback or make it warn loudly.
+109
View File
@@ -0,0 +1,109 @@
# T17 §17V — Scaffolding freeze assessment + §17X adversarial matrix
(2026-09-15)
## §17X — adversarial system matrix (36 cases → evidence)
| # | Case | Evidence |
|---|------|----------|
| 1 | explicit build request | 17Q-1 (PROJECT_BUILD, bootstrap) |
| 2 | indirect authorization | 17Q-4 ("that's the one, make it happen") |
| 3 | discussion only | 17Q-2 + 17L test (3 phrasings) |
| 4 | ambiguous referent | 17Q-6 (asks, never guesses) |
| 5 | stale referent | 17Q-8 (topic-shift resistance) |
| 6 | superseded proposal | 17Q-20 |
| 7 | option-selection referent | 17Q-5 (ordinal) |
| 8 | new project | 17Q-14 |
| 9 | existing project | 17Q-11 |
| 10 | local change | 17Q-12 |
| 11 | non-construction request | 17Q-10 ("build a list") |
| 12 | research request | 17Q-9 |
| 13 | provider unavailable | 16G-C (verbatim codes) |
| 14 | provider manually changed | 16G-G (scaffold byte-identical) |
| 15 | duplicate completion event | 15E double-resume tests |
| 16 | completion event before waiter | 17C replay (4.6s prod-realistic) |
| 17 | completion event after waiter | 17C cross-user delivery (2.05s) |
| 18 | lost event | T16 16D live cycles (deadline recovery, promoted) |
| 19 | controller restart | 15E restart tests + resume_or_recover |
| 20 | stage restart | 17E wiring: already-closed skip test |
| 21 | programme restart | 17E wiring: restart-from-durable-record test |
| 22 | subtask≠programme | 15M canary + 17E failure isolation |
| 23 | programme complete | 17E: PROGRAMME_COMPLETE / _WITH_FAILURES honest verdicts |
| 24 | blocked stage | canary BLOCKED→fallback (15M tests) |
| 25 | approval-required stage | canary APPROVAL_REQUIRED honest stop (15M) |
| 26 | duplicate task state migration | 17F held-outs (counters from authority) |
| 27 | legacy build route | 17G gate (/build start refused unless classified) |
| 28 | command/NL equivalence | 17Q-15/16 (same classifier, same semantics) |
| 29 | ContextIntent replay/idempotency | intents stateless; contracts idempotent (17E dup-signal test) |
| 30 | invalid Substrate command | 16K refusal tests |
| 31 | long-gap re-entry | 16O + 17T benchmarks |
| 32 | repository drift | 16P VAT-regression blocks resume |
| 33 | false-green test | stage_guards verify_test_truth (15F-I) |
| 34 | Henxels structural failure | rc=0 all session; manifest gate caught every new module |
| 35 | release stale publisher | T14B authority re-read (prior tranche, tests stand) |
| 36 | deployment convergence | T16/T17: 0.66.63→0.66.66 auto-converged, verified |
Coverage honestly noted: cases 13-15, 19, 33, 35 rely on T14-T16 suites
(green in this session's full runs); cases 16-18 carry THIS-tranche live
production evidence.
## §17V — FREEZE ASSESSMENT
Checklist (mandate's 17 items):
1. one canonical construction kernel — YES (scaffold.py + ONE
classify_mode; T17 17G closed the /build bypass gate; 17U deleted the
dead second orchestrator)
2. native conversational/default activation — YES (operator hook path,
T15; T17 added the conversational interpretation layer)
3. explicit construction commands — YES (17K/17Q-1/7)
4. contextual multi-turn activation — YES (17J-17N, 17Q matrix 22 cases)
5. one canonical programme continuation mechanism — YES (17D/17E wired +
live-proven)
6. event-driven waiting canonical where events exist — YES (16C/17C;
CI + builder adapters, replay, cross-user delivery)
7. watchdog only as recovery/failure detection — YES (proven in
production twice; wake path proven production-realistically)
8. provider ownership outside Scaffolding — YES (16F structural guard)
9. builder cannot self-certify — YES (phase-gated close; closure
refusals observed live in T14)
10. completion truth evidence-driven — YES (checkpoints on contracts;
verify_test_truth)
11. interruption/resume proven — YES (T14E live; 17E restart tests)
12. long-gap re-entry supported — YES (drift_resume + 16O/17T honest
benchmarks)
13. Rune itself uses Scaffolding — YES (T16 16R, T17 17R/Y contracts
closed through the real flow)
14. future Substrate seam stable — YES (16I/J/K; SCHEMA_VERSION=1;
zero direct-store coupling tests)
15. major duplicate construction authorities removed or consciously
deprecated — YES with residuals (plugin deleted; /build gated;
build_mode kept as bounded adapter BY DECISION; task-state cache
demoted by design, full writer retirement deferred)
16. real-world generalisation begun — PARTIAL (fixtures + Rune itself;
no external project yet)
17. remaining work mostly bugs/edge/docs/adoption — YES (16W/17U debt
lists are small and bounded)
## VERDICT: FREEZE_READY (with named residuals)
Rationale: every architecture-level capability the freeze checklist
names is implemented, canonical, and verified — most of them with live
production evidence from T16/T17. The residuals are deliberately
bounded and none is architectural:
- build_mode worktree adapter retirement (needs a migration plan, not
new architecture)
- task-state cache writer retirement (demoted, consumers derived)
- replay-path first NATURAL cycle confirmation (mechanism proven
production-realistically; only the natural-timing capture pending)
- external-project generalisation (adoption, not construction)
Recommended posture: stop adding Scaffolding architecture. Future work:
USE it (Rune retrofit completion, real projects), fix observed defects,
simplify, stabilise — exactly the direction the mandate anticipated.
## §17Z POSTSCRIPT — natural event-path proof (19:15Z, final release cycle)
The 17C residual closed ITSELF on the final release: deploy waiter for
927548e registered 19:13:28, CI completion wake received 19:15:16,
controller resumed ON THE EVENT (mode="event", no rearm, no deadline),
authority re-read verdict success at 19:15:20, wall 112.5s, PROMOTED.
First NATURAL production event-driven CI wake. v0.66.72 released,
deployed current = 927548e = gitea/master = release. Full convergence.
+39
View File
@@ -0,0 +1,39 @@
# T18 §18A — exact current-state audit (2026-09-15, fresh verification)
All facts independently verified this session; nothing trusted from the
T17 report without re-verification.
## Identity / release chain
- HEAD = gitea/master = **29570f4** (T17 17Z postscript)
- Deployed current → 29570f4 (symlink 20:22); last-good → 927548e (0.66.72's
bump parent)
- Release **v0.66.72** (20:06:36); the post-report doc commit DRAINED and
DEPLOYED — T17's "draining" state is fully converged.
## Gates (lock-exact environments, 29570f4)
- Full suite: **1920 passed + 84 subtests** (127s)
- Lint ratchet: PASS (BLE001 350/350, S110 78/78 — post-17U shrink)
- Type ratchet: PASS — errors **111** (baseline 117 — IMPROVED, honest
high-water keeps the lower count), warnings 435 (≤443)
- Henxels: rc=0
## Infrastructure
- W/R daemon: pid 287505, /health ok, data root /mnt/workdrive/wr-bridge
(shared root; wr_root() anti-split-brain policy active)
- Spine store: 148 entities (139 module, 8 subsystem, 1 deployment_surface),
636 relationships
- Scaffolding store: 28 contracts — now ALL terminal after 18A
reconciliation; 3 durable programme records (17E proofs)
## 18A drift reconciliation performed
- 8 orphan contracts from superseded 17E proof attempts (17e-stage-*,
17e2-stage-*) were draft/approved in the main store — abandoned
truthfully with reconciliation checkpoints. Open contracts: ZERO.
## Current routes (verified by presence, detailed audits follow)
- Task-state authority: TaskStateRepository (17F)
- build_mode: bounded adapter behind the /build classification gate (17G)
- Contextual routes: construction_intent → classify_mode (17J-Q),
request-modals + information-opener precedence live
- programme continuation: run_construction_programme wired + live-proven
- structural map: docs/STRUCTURAL_MAP.md (updated 17T)
@@ -0,0 +1,22 @@
# T18 §18D CORRECTION — contextual research scope (2026-09-15, Nick)
CORRECTION: the 18D research topics (architecture graphs, dependency
graphs, service topology, monorepo tooling, ADRs, graph formats) were
EXAMPLES relevant to the structural-model task — not a universal
research checklist. Scaffolding must never hard-code research topics.
IMPLEMENTED:
- build_basis.generate_research_plan(): context-specific plan synthesis
(intent → target/reality → unknowns/decisions/risks → topics). No
topic vocabulary in code. Depth by consequence heuristics, always
with WHY.
- Held-outs (tests/test_contextual_research_plan.py): unrelated intents
produce materially different plans; the 18D architecture topics do
NOT leak into unrelated research; no-unknowns → depth NONE with empty
topics; prior-art machinery summons only when unknowns reference
existing practice, phrased for THIS intent.
- Principle persisted in docs/SCAFFOLDING_V1_DEFINITION.md.
The 18D Build Basis (research/t18-structural-model-prior-art) remains
VALID: those topics were contextually correct for designing the
structural model itself.
@@ -0,0 +1,123 @@
{
"alternatives_considered": [],
"created_at": "2026-09-15T21:13:20Z",
"deeper": {
"findings": [],
"refs": [],
"used": false
},
"depth": {
"decided_at": "2026-09-15T21:13:20Z",
"depth": "FULL",
"mode": "",
"route": "RESEARCHED_CHANGE",
"why": "route RESEARCHED_CHANGE default"
},
"dispositions": [
{
"disposition": "ADOPT",
"item": "Backstage relation vocabulary + entity-ref discipline + catalog-info.yaml emission",
"ref": "backstage.io",
"why": "Domain/System/Component/Resource/API maps 1:1 to our nesting; ecosystem interop free"
},
{
"disposition": "ADOPT",
"item": "Kythe edge-kind vocabulary (ref/call, property/reads-writes, satisfies, overrides) + derived-reverse-edge rule",
"ref": "kythe.io/docs/schema",
"why": "disciplined, versioned, explicit"
},
{
"disposition": "ADOPT",
"item": "SCIP monikers for cross-index identity bridging",
"ref": "scip",
"why": "decouples stable identity from any one indexer"
},
{
"disposition": "ADOPT",
"item": "Nx affected semantics as impact-closure spec (diff\u2192owning entities\u2192reverse-dep closure)",
"ref": "nx.dev/concepts/affected",
"why": "productionized impact_frontier"
},
{
"disposition": "ADOPT",
"item": "Bazel rdeps(kind(.*_test)) as verified_by-driven verification scoping",
"ref": "bazel.build",
"why": "semantic template for test-surface queries"
},
{
"disposition": "ADAPT",
"item": "Kythe VName stability contract",
"ref": "kythe.io",
"why": "Kythe explicitly declines cross-version stability \u2014 our persistent model REQUIRES it; keep deterministic regeneration but promise stability"
},
{
"disposition": "ADAPT",
"item": "C4 deployment shapes (DeploymentNode/ContainerInstance)",
"ref": "c4model.com",
"why": "become machine-derived deployed_by/connects_to with evidence, not hand-authored"
},
{
"disposition": "ADAPT",
"item": "Glean immutable-batch layering",
"ref": "glean.software",
"why": "our snapshots are philosophically identical; mark externally-derived edges with distinct authority"
},
{
"disposition": "COMBINE",
"item": "Backstage org model + Kythe code model + Nx traversal COMBINED on our evidence-gated store",
"ref": "combined",
"why": "prior art occupies one layer each; the cross-layer join is the actual design"
},
{
"disposition": "REJECT",
"item": "Adopting CodeQL/Kythe/Glean as our store core; generic graph DB (Neo4j); LSIF; Stack Graphs (archived 2025-09)",
"ref": "various",
"why": "none carry evidence_refs/authority/status/maturity as first-class; heavyweight or dead"
},
{
"disposition": "INNOVATE",
"item": "Consent-gated schema migration; verified_by edges to verification entities; deployed_by with code linkage; authority-graded evidence on every edge",
"ref": "18V assessment",
"why": "genuinely absent from all surveyed systems"
}
],
"implications": {
"architecture": [],
"migration": [],
"structural": [],
"verification": []
},
"inward": {
"findings": [],
"refs": []
},
"issue_ref": "research/t18-structural-model-prior-art",
"kind": "build_basis",
"outward": {
"findings": [],
"prior_art_questions_answered": [
"what already exists addressing this intent",
"what open-source systems/products/workflows attempt it",
"what architectural patterns already work",
"what standards/conventions exist",
"what can be directly ADOPTED",
"what can be ADAPTED",
"what can be COMBINED",
"what should be REJECTED",
"where is genuine INNOVATION required"
],
"refs": [
"https://backstage.io/docs/features/software-catalog/system-model/",
"https://kythe.io/docs/schema/",
"https://github.com/scip-code/scip",
"https://nx.dev/concepts/affected",
"https://c4model.com/",
"https://bazel.build/query/language",
"https://github.com/dependency-cruiser/dependency-cruiser"
]
},
"question": "What existing systems/modeling approaches should Scaffolding's structural Scaffold adopt, adapt, combine, or reject?",
"risks": [],
"schema_version": 1,
"uncertainty": []
}
@@ -0,0 +1,32 @@
{
"schema_version": 1,
"target": "component:structural_model",
"affected_domains": [
"domain:structural-awareness"
],
"affected_subsystems": [],
"consumers_connectors": [],
"candidate_files": [],
"contract_scope_additions": [],
"state_implications": [
"state_store:structural-store"
],
"environment_runtime_implications": [],
"migration_implications": [],
"risks_uncertainties": [
{
"kind": "dynamic-consumer",
"note": "static model cannot see dynamic dispatch/string-keyed lookup/late-binding consumers \u2014 reconcilers must verify at runtime"
}
],
"verification_plan": [
"test_suite:scaffolding-tests"
],
"rollback_implications": [
"revert contract scope commits; deployment rollbacks via last-good symlink (canonical deploy machinery)"
],
"basis_summary": {
"depth": "INWARD",
"why": "depth supplied by caller (recorded, caller accountable)"
}
}
@@ -0,0 +1,34 @@
{
"contract_id": "18q-graph-projection",
"construction_base": "b7865a76bbdb",
"runtime_generation": "dep-29570f4",
"candidate_generation": "",
"promotion_target": "gitea/master",
"rollback_generation": "",
"stages_completed": [
"base_verified",
"worktree_created",
"construction",
"verified",
"committed"
],
"lifecycle": [
"base_verified",
"worktree_created",
"construction",
"verified",
"committed",
"pushed",
"ci_qualified",
"released",
"deployment_confirmed",
"evidence_archived",
"cleaned"
],
"evidence": [
{
"stage": "worktree_created",
"evidence": "harness worktree (existing dev worktree, rule P)"
}
]
}
@@ -0,0 +1,66 @@
# T18 §18R/§18S — adversarial matrix + V1 gap reassessment (2026-09-15)
## §18R — 20-case matrix → evidence
| # | Case | Evidence |
|---|------|----------|
| 1 | stale Scaffold | test_18r_stale_scaffold_reconciles_via_drift (MATERIAL + reconciliation) |
| 2 | changed file location | 18H moved-file detection (src/widgets/widget.py) |
| 3 | unknown dynamic consumer | test_18r_dynamic_consumers_stay_uncertain (explicit uncertainty) |
| 4 | broken connector | structural_model: consumers/depends_on status field; stale edges excluded from queries |
| 5 | research unavailable | build_basis: unqualified basis → is_qualified False; contracts cannot attach qualified=false silently |
| 6 | research contradicts architecture assumption | dispositions carry REJECT with why (18D basis: REJECT graph-DB adoption) |
| 7 | target expands outside scope | rule J: reconcile/expand explicitly — impact closure marks non_goals + uncertainty; contract scope amendments user-gated (scaffold.py) |
| 8 | user rejects material schema upgrade | test_18r_user_rejects_material_upgrade (ConsentRequired) |
| 9 | minor compatible migration | classify_migration MINOR = automatic (test_18i) |
| 10 | dirty worktree | test_18r_dirty_worktree_is_visible_not_fatal (detect + record, policy per-contract) |
| 11 | interrupted construction | T14E SIGKILL live + 17E restart held-outs |
| 12 | provider unavailable | 16G-C verbatim status codes; PROVIDER_UNAVAILABLE blocks branch |
| 13 | CI failure | T16 16D: deploy refused truthfully on red |
| 14 | deployment failure | runs 7953/7958 failed fast+truthful (T16 17C finding) |
| 15 | old runtime + candidate schema incompatibility | consent machinery: material migrations gated per project (18I) |
| 16 | secret present in env/config | test_18r_secret_in_env_does_not_block_construction (rule O: continues) |
| 17 | secret in generated output | test_18r_secret_value_redacted_in_store — **FOUND REAL BUG: *_token keys were not redacted; fixed (substring match now)** |
| 18 | forge unavailable | test_18r_forge_unavailable_raises_explicitly (ForgeUnavailable, never truth change) |
| 19 | structural graph projection stale | test_18q_projection_is_read_only + projection regenerated on model change (18P step 9) |
| 20 | /build route backend bypass | 18M: /build start mints canonical contract (test_native_default + gate) |
| 21 | contextual ambiguous authorization | 17Q-6 + 18N multiple-proposal terse selection |
**Matrix yield: 2 real defects found and fixed** (secret redaction gap;
drift source-field mismatch) — the matrix is load-bearing, not decorative.
## §18S — V1 gap re-score (post-T18)
| North Star capability | T18-start | T18-end |
|---|---|---|
| Research-led construction (Build Basis) | ABSENT | **TEST_PROVEN + one QUALIFIED real basis (18D)** |
| Contextual research plan (correction) | — | **TEST_PROVEN (disjoint-plan held-outs)** |
| OUTWARD precedent-first | PARTIAL | **TEST_PROVEN (contract questions + real run)** |
| First-class structural Scaffold | PARTIAL | **TEST_PROVEN + LIVE on Scaffolding itself (18O)** |
| Structure→scope→governance | PARTIAL | **TEST_PROVEN (closure→contract 18P)** |
| Visible impact preview | ABSENT | **TEST_PROVEN (18G + real 18P preview persisted)** |
| Structural drift | PARTIAL | **TEST_PROVEN (18H; 2 bugs found by matrix)** |
| Schema versioning + consent | PARTIAL | **TEST_PROVEN (18I; consent structurally enforced)** |
| Environment/runtime model | PARTIAL | **TEST_PROVEN (env/deployment entities + distinction)** |
| Workspace lifecycle | PARTIAL | **TEST_PROVEN (18K order-enforced; adapter retained)** |
| Forge boundary | ABSENT | **TEST_PROVEN (18L; Gitea proven by every push this session)** |
| Full route convergence | PARTIAL | **TEST_PROVEN (18M: /build start mints contracts)** |
| Contextual generalisation | TEST_PROVEN | **TEST_PROVEN+ (18N: 4 semantic gaps fixed)** |
| Self-scaffold | PARTIAL | **LIVE (18O consistency-enforced)** |
| Upgrade-via-Scaffold | ABSENT | **LIVE_PROVEN (18P: 11 scaffold-driven steps)** |
| Graph projection | ABSENT | **TEST_PROVEN (18Q, 3 formats)** |
| Memory/Substrate | ABSENT | ABSENT (BY DESIGN — rule L) |
| Kernel behavior changes | — | **ZERO frozen-kernel exceptions (rule B record: none needed)** |
## VERDICT: RETROFIT_READY (for the Rune-wide retrofit tranche)
Why: every V1 foundation capability is implemented, test-proven, and —
where the tranche could — live-proven on Scaffolding itself (self-
scaffold, scaffold-driven upgrade, qualified research basis from real
prior-art work). The adversarial matrix made the system better (2 real
fixes). What remains is exactly the mandate's next phase: applying this
discipline across Rune's remaining subsystems (Spine modernisation,
research pipeline modeling, release/deploy structural entities) and
real external projects — plus natural-production confirmation of the
event-replay path (mechanism proven; natural timing pending, as in T17).
Not full V1: generalisation beyond one project is still ahead.
@@ -0,0 +1,84 @@
digraph structural {
rankdir=LR;
node [shape=box, style="rounded,filled", fillcolor="#eef"];
"component:build_mode_adapter" [label="build_mode_adapter (component)"];
"component:build_basis" [label="build_basis (component)"];
"component:construction_activation" [label="construction_activation (component)"];
"component:construction_decision" [label="construction_decision (component)"];
"component:construction_intent" [label="construction_intent (component)"];
"component:construction_interface" [label="construction_interface (component)"];
"component:construction_mode" [label="construction_mode (component)"];
"component:drift_resume" [label="drift_resume (component)"];
"component:event_wait" [label="event_wait (component)"];
"component:failure_recovery" [label="failure_recovery (component)"];
"component:forge" [label="forge (component)"];
"component:graph_projection" [label="graph_projection (component)"];
"component:impact_preview" [label="impact_preview (component)"];
"component:programme_continuation" [label="programme_continuation (component)"];
"component:scaffold" [label="scaffold (component)"];
"component:stage_guards" [label="stage_guards (component)"];
"component:structural_migration" [label="structural_migration (component)"];
"component:structural_model" [label="structural_model (component)"];
"deployment_surface:rune-code-current" [label="rune-code-current (deployment_surface)"];
"domain:construction-governance" [label="construction-governance (domain)"];
"domain:execution-integration" [label="execution-integration (domain)"];
"domain:structural-awareness" [label="structural-awareness (domain)"];
"environment:production" [label="production (environment)"];
"interface:builder-boundary" [label="builder-boundary (interface)"];
"state_store:scaffold-store" [label="scaffold-store (state_store)"];
"state_store:structural-store" [label="structural-store (state_store)"];
"subsystem:execution" [label="execution (subsystem)"];
"subsystem:interpretation" [label="interpretation (subsystem)"];
"subsystem:kernel" [label="kernel (subsystem)"];
"subsystem:structural" [label="structural (subsystem)"];
"test_suite:scaffolding-tests" [label="scaffolding-tests (test_suite)"];
"project:scaffolding" [label="scaffolding (project)"];
"component:build_mode_adapter" -> "subsystem:kernel" [label="legacy_of"];
"component:build_basis" -> "state_store:structural-store" [label="reads"];
"component:build_basis" -> "subsystem:structural" [label="belongs_to"];
"component:build_mode_adapter" -> "subsystem:execution" [label="belongs_to"];
"component:construction_activation" -> "subsystem:interpretation" [label="belongs_to"];
"component:construction_decision" -> "subsystem:kernel" [label="belongs_to"];
"component:construction_intent" -> "component:construction_mode" [label="consumes"];
"component:construction_intent" -> "subsystem:interpretation" [label="belongs_to"];
"component:construction_interface" -> "interface:builder-boundary" [label="provides"];
"component:construction_interface" -> "subsystem:execution" [label="belongs_to"];
"component:construction_mode" -> "subsystem:kernel" [label="belongs_to"];
"component:drift_resume" -> "subsystem:structural" [label="belongs_to"];
"component:event_wait" -> "component:programme_continuation" [label="connects_to"];
"component:event_wait" -> "subsystem:execution" [label="belongs_to"];
"component:failure_recovery" -> "subsystem:kernel" [label="belongs_to"];
"component:forge" -> "subsystem:execution" [label="belongs_to"];
"component:forge" -> "test_suite:scaffolding-tests" [label="verified_by"];
"component:graph_projection" -> "state_store:structural-store" [label="reads"];
"component:graph_projection" -> "subsystem:structural" [label="belongs_to"];
"component:graph_projection" -> "test_suite:scaffolding-tests" [label="verified_by"];
"component:impact_preview" -> "subsystem:structural" [label="belongs_to"];
"component:programme_continuation" -> "subsystem:execution" [label="belongs_to"];
"component:programme_continuation" -> "test_suite:scaffolding-tests" [label="verified_by"];
"component:scaffold" -> "component:construction_mode" [label="depends_on"];
"component:scaffold" -> "deployment_surface:rune-code-current" [label="deployed_by"];
"component:scaffold" -> "state_store:scaffold-store" [label="owns"];
"component:scaffold" -> "state_store:structural-store" [label="reads"];
"component:scaffold" -> "subsystem:kernel" [label="belongs_to"];
"component:scaffold" -> "test_suite:scaffolding-tests" [label="verified_by"];
"component:stage_guards" -> "subsystem:kernel" [label="belongs_to"];
"component:structural_migration" -> "subsystem:structural" [label="belongs_to"];
"component:structural_model" -> "state_store:structural-store" [label="owns"];
"component:structural_model" -> "subsystem:structural" [label="belongs_to"];
"component:structural_model" -> "test_suite:scaffolding-tests" [label="verified_by"];
"deployment_surface:rune-code-current" -> "environment:production" [label="connects_to"];
"deployment_surface:rune-code-current" -> "project:scaffolding" [label="belongs_to"];
"domain:construction-governance" -> "project:scaffolding" [label="belongs_to"];
"domain:execution-integration" -> "project:scaffolding" [label="belongs_to"];
"domain:structural-awareness" -> "project:scaffolding" [label="belongs_to"];
"environment:production" -> "project:scaffolding" [label="belongs_to"];
"interface:builder-boundary" -> "subsystem:execution" [label="belongs_to"];
"state_store:scaffold-store" -> "project:scaffolding" [label="belongs_to"];
"state_store:structural-store" -> "project:scaffolding" [label="belongs_to"];
"subsystem:execution" -> "domain:execution-integration" [label="belongs_to"];
"subsystem:interpretation" -> "domain:construction-governance" [label="belongs_to"];
"subsystem:kernel" -> "domain:construction-governance" [label="belongs_to"];
"subsystem:structural" -> "domain:structural-awareness" [label="belongs_to"];
"test_suite:scaffolding-tests" -> "project:scaffolding" [label="belongs_to"];
}
@@ -0,0 +1,615 @@
{
"edge_count": 49,
"edges": [
{
"authority": "model-author",
"from": "component:build_mode_adapter",
"status": "active",
"to": "subsystem:kernel",
"type": "legacy_of"
},
{
"authority": "model-author",
"from": "component:build_basis",
"status": "active",
"to": "state_store:structural-store",
"type": "reads"
},
{
"authority": "model-author",
"from": "component:build_basis",
"status": "active",
"to": "subsystem:structural",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "component:build_mode_adapter",
"status": "active",
"to": "subsystem:execution",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "component:construction_activation",
"status": "active",
"to": "subsystem:interpretation",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "component:construction_decision",
"status": "active",
"to": "subsystem:kernel",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "component:construction_intent",
"status": "active",
"to": "component:construction_mode",
"type": "consumes"
},
{
"authority": "model-author",
"from": "component:construction_intent",
"status": "active",
"to": "subsystem:interpretation",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "component:construction_interface",
"status": "active",
"to": "interface:builder-boundary",
"type": "provides"
},
{
"authority": "model-author",
"from": "component:construction_interface",
"status": "active",
"to": "subsystem:execution",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "component:construction_mode",
"status": "active",
"to": "subsystem:kernel",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "component:drift_resume",
"status": "active",
"to": "subsystem:structural",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "component:event_wait",
"status": "active",
"to": "component:programme_continuation",
"type": "connects_to"
},
{
"authority": "model-author",
"from": "component:event_wait",
"status": "active",
"to": "subsystem:execution",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "component:failure_recovery",
"status": "active",
"to": "subsystem:kernel",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "component:forge",
"status": "active",
"to": "subsystem:execution",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "component:forge",
"status": "active",
"to": "test_suite:scaffolding-tests",
"type": "verified_by"
},
{
"authority": "model-author",
"from": "component:graph_projection",
"status": "active",
"to": "state_store:structural-store",
"type": "reads"
},
{
"authority": "model-author",
"from": "component:graph_projection",
"status": "active",
"to": "subsystem:structural",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "component:graph_projection",
"status": "active",
"to": "test_suite:scaffolding-tests",
"type": "verified_by"
},
{
"authority": "model-author",
"from": "component:impact_preview",
"status": "active",
"to": "subsystem:structural",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "component:programme_continuation",
"status": "active",
"to": "subsystem:execution",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "component:programme_continuation",
"status": "active",
"to": "test_suite:scaffolding-tests",
"type": "verified_by"
},
{
"authority": "model-author",
"from": "component:scaffold",
"status": "active",
"to": "component:construction_mode",
"type": "depends_on"
},
{
"authority": "model-author",
"from": "component:scaffold",
"status": "active",
"to": "deployment_surface:rune-code-current",
"type": "deployed_by"
},
{
"authority": "model-author",
"from": "component:scaffold",
"status": "active",
"to": "state_store:scaffold-store",
"type": "owns"
},
{
"authority": "model-author",
"from": "component:scaffold",
"status": "active",
"to": "state_store:structural-store",
"type": "reads"
},
{
"authority": "model-author",
"from": "component:scaffold",
"status": "active",
"to": "subsystem:kernel",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "component:scaffold",
"status": "active",
"to": "test_suite:scaffolding-tests",
"type": "verified_by"
},
{
"authority": "model-author",
"from": "component:stage_guards",
"status": "active",
"to": "subsystem:kernel",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "component:structural_migration",
"status": "active",
"to": "subsystem:structural",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "component:structural_model",
"status": "active",
"to": "state_store:structural-store",
"type": "owns"
},
{
"authority": "model-author",
"from": "component:structural_model",
"status": "active",
"to": "subsystem:structural",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "component:structural_model",
"status": "active",
"to": "test_suite:scaffolding-tests",
"type": "verified_by"
},
{
"authority": "model-author",
"from": "deployment_surface:rune-code-current",
"status": "active",
"to": "environment:production",
"type": "connects_to"
},
{
"authority": "model-author",
"from": "deployment_surface:rune-code-current",
"status": "active",
"to": "project:scaffolding",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "domain:construction-governance",
"status": "active",
"to": "project:scaffolding",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "domain:execution-integration",
"status": "active",
"to": "project:scaffolding",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "domain:structural-awareness",
"status": "active",
"to": "project:scaffolding",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "environment:production",
"status": "active",
"to": "project:scaffolding",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "interface:builder-boundary",
"status": "active",
"to": "subsystem:execution",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "state_store:scaffold-store",
"status": "active",
"to": "project:scaffolding",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "state_store:structural-store",
"status": "active",
"to": "project:scaffolding",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "subsystem:execution",
"status": "active",
"to": "domain:execution-integration",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "subsystem:interpretation",
"status": "active",
"to": "domain:construction-governance",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "subsystem:kernel",
"status": "active",
"to": "domain:construction-governance",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "subsystem:structural",
"status": "active",
"to": "domain:structural-awareness",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "test_suite:scaffolding-tests",
"status": "active",
"to": "project:scaffolding",
"type": "belongs_to"
},
{
"authority": "model-author",
"from": "project:scaffolding",
"status": "root",
"to": "project:scaffolding",
"type": "belongs_to"
}
],
"generated_at": "2026-09-15T22:10:51Z",
"node_count": 32,
"nodes": [
{
"ext": {
"bounded_adapter": true,
"source": "src/rune/build_mode.py"
},
"id": "component:build_mode_adapter",
"kind": "component",
"maturity": "DECLARED",
"name": "build_mode_adapter"
},
{
"ext": {
"source": "src/rune/build_basis.py"
},
"id": "component:build_basis",
"kind": "component",
"maturity": "DECLARED",
"name": "build_basis"
},
{
"ext": {
"source": "src/rune/construction_activation.py"
},
"id": "component:construction_activation",
"kind": "component",
"maturity": "DECLARED",
"name": "construction_activation"
},
{
"ext": {
"source": "src/rune/construction_decision.py"
},
"id": "component:construction_decision",
"kind": "component",
"maturity": "DECLARED",
"name": "construction_decision"
},
{
"ext": {
"source": "src/rune/construction_intent.py"
},
"id": "component:construction_intent",
"kind": "component",
"maturity": "DECLARED",
"name": "construction_intent"
},
{
"ext": {
"source": "src/rune/construction_interface.py"
},
"id": "component:construction_interface",
"kind": "component",
"maturity": "DECLARED",
"name": "construction_interface"
},
{
"ext": {
"source": "src/rune/construction_mode.py"
},
"id": "component:construction_mode",
"kind": "component",
"maturity": "DECLARED",
"name": "construction_mode"
},
{
"ext": {
"source": "src/rune/drift_resume.py"
},
"id": "component:drift_resume",
"kind": "component",
"maturity": "DECLARED",
"name": "drift_resume"
},
{
"ext": {
"source": "src/rune/event_wait.py"
},
"id": "component:event_wait",
"kind": "component",
"maturity": "DECLARED",
"name": "event_wait"
},
{
"ext": {
"source": "src/rune/failure_recovery.py"
},
"id": "component:failure_recovery",
"kind": "component",
"maturity": "DECLARED",
"name": "failure_recovery"
},
{
"ext": {
"source": "src/rune/forge.py"
},
"id": "component:forge",
"kind": "component",
"maturity": "DECLARED",
"name": "forge"
},
{
"ext": {
"source": "src/rune/graph_projection.py"
},
"id": "component:graph_projection",
"kind": "component",
"maturity": "DECLARED",
"name": "graph_projection"
},
{
"ext": {
"source": "src/rune/impact_preview.py"
},
"id": "component:impact_preview",
"kind": "component",
"maturity": "DECLARED",
"name": "impact_preview"
},
{
"ext": {
"source": "src/rune/programme_continuation.py"
},
"id": "component:programme_continuation",
"kind": "component",
"maturity": "DECLARED",
"name": "programme_continuation"
},
{
"ext": {
"source": "src/rune/scaffold.py"
},
"id": "component:scaffold",
"kind": "component",
"maturity": "DECLARED",
"name": "scaffold"
},
{
"ext": {
"source": "src/rune/stage_guards.py"
},
"id": "component:stage_guards",
"kind": "component",
"maturity": "DECLARED",
"name": "stage_guards"
},
{
"ext": {
"source": "src/rune/structural_migration.py"
},
"id": "component:structural_migration",
"kind": "component",
"maturity": "DECLARED",
"name": "structural_migration"
},
{
"ext": {
"source": "src/rune/structural_model.py"
},
"id": "component:structural_model",
"kind": "component",
"maturity": "DECLARED",
"name": "structural_model"
},
{
"ext": {},
"id": "deployment_surface:rune-code-current",
"kind": "deployment_surface",
"maturity": "DECLARED",
"name": "rune-code-current"
},
{
"ext": {},
"id": "domain:construction-governance",
"kind": "domain",
"maturity": "DECLARED",
"name": "construction-governance"
},
{
"ext": {},
"id": "domain:execution-integration",
"kind": "domain",
"maturity": "DECLARED",
"name": "execution-integration"
},
{
"ext": {},
"id": "domain:structural-awareness",
"kind": "domain",
"maturity": "DECLARED",
"name": "structural-awareness"
},
{
"ext": {},
"id": "environment:production",
"kind": "environment",
"maturity": "DECLARED",
"name": "production"
},
{
"ext": {},
"id": "interface:builder-boundary",
"kind": "interface",
"maturity": "DECLARED",
"name": "builder-boundary"
},
{
"ext": {},
"id": "state_store:scaffold-store",
"kind": "state_store",
"maturity": "DECLARED",
"name": "scaffold-store"
},
{
"ext": {},
"id": "state_store:structural-store",
"kind": "state_store",
"maturity": "DECLARED",
"name": "structural-store"
},
{
"ext": {},
"id": "subsystem:execution",
"kind": "subsystem",
"maturity": "DECLARED",
"name": "execution"
},
{
"ext": {},
"id": "subsystem:interpretation",
"kind": "subsystem",
"maturity": "DECLARED",
"name": "interpretation"
},
{
"ext": {},
"id": "subsystem:kernel",
"kind": "subsystem",
"maturity": "DECLARED",
"name": "kernel"
},
{
"ext": {},
"id": "subsystem:structural",
"kind": "subsystem",
"maturity": "DECLARED",
"name": "structural"
},
{
"ext": {},
"id": "test_suite:scaffolding-tests",
"kind": "test_suite",
"maturity": "DECLARED",
"name": "scaffolding-tests"
},
{
"ext": {},
"id": "project:scaffolding",
"kind": "project",
"maturity": "DECLARED",
"name": null
}
],
"project": "scaffolding",
"schema_version": 1
}
@@ -0,0 +1,81 @@
graph LR
n1["build_mode_adapter (component)"]
n0["build_basis (component)"]
n2["construction_activation (component)"]
n3["construction_decision (component)"]
n4["construction_intent (component)"]
n5["construction_interface (component)"]
n6["construction_mode (component)"]
n7["drift_resume (component)"]
n8["event_wait (component)"]
n9["failure_recovery (component)"]
n10["forge (component)"]
n11["graph_projection (component)"]
n12["impact_preview (component)"]
n13["programme_continuation (component)"]
n14["scaffold (component)"]
n15["stage_guards (component)"]
n16["structural_migration (component)"]
n17["structural_model (component)"]
n18["rune-code-current (deployment_surface)"]
n19["construction-governance (domain)"]
n20["execution-integration (domain)"]
n21["structural-awareness (domain)"]
n22["production (environment)"]
n23["builder-boundary (interface)"]
n25["scaffold-store (state_store)"]
n26["structural-store (state_store)"]
n27["execution (subsystem)"]
n28["interpretation (subsystem)"]
n29["kernel (subsystem)"]
n30["structural (subsystem)"]
n31["scaffolding-tests (test_suite)"]
n24["scaffolding (project)"]
n1 -->|legacy_of| n29
n0 -->|reads| n26
n0 -->|belongs_to| n30
n1 -->|belongs_to| n27
n2 -->|belongs_to| n28
n3 -->|belongs_to| n29
n4 -->|consumes| n6
n4 -->|belongs_to| n28
n5 -->|provides| n23
n5 -->|belongs_to| n27
n6 -->|belongs_to| n29
n7 -->|belongs_to| n30
n8 -->|connects_to| n13
n8 -->|belongs_to| n27
n9 -->|belongs_to| n29
n10 -->|belongs_to| n27
n10 -->|verified_by| n31
n11 -->|reads| n26
n11 -->|belongs_to| n30
n11 -->|verified_by| n31
n12 -->|belongs_to| n30
n13 -->|belongs_to| n27
n13 -->|verified_by| n31
n14 -->|depends_on| n6
n14 -->|deployed_by| n18
n14 -->|owns| n25
n14 -->|reads| n26
n14 -->|belongs_to| n29
n14 -->|verified_by| n31
n15 -->|belongs_to| n29
n16 -->|belongs_to| n30
n17 -->|owns| n26
n17 -->|belongs_to| n30
n17 -->|verified_by| n31
n18 -->|connects_to| n22
n18 -->|belongs_to| n24
n19 -->|belongs_to| n24
n20 -->|belongs_to| n24
n21 -->|belongs_to| n24
n22 -->|belongs_to| n24
n23 -->|belongs_to| n27
n25 -->|belongs_to| n24
n26 -->|belongs_to| n24
n27 -->|belongs_to| n20
n28 -->|belongs_to| n19
n29 -->|belongs_to| n19
n30 -->|belongs_to| n21
n31 -->|belongs_to| n24
+98
View File
@@ -0,0 +1,98 @@
{
"alternatives_considered": [],
"created_at": "2026-09-15T23:39:58Z",
"deeper": {
"findings": [],
"refs": [],
"used": false
},
"depth": {
"decided_at": "2026-09-15T23:39:58Z",
"depth": "FULL",
"mode": "",
"route": "RESEARCHED_CHANGE",
"why": "route RESEARCHED_CHANGE default"
},
"dispositions": [
{
"disposition": "ADOPT",
"item": "Structural closure (Bazel rdeps/Nx affected) as the SOUND invalidation backbone",
"ref": "bazel.build",
"why": "static closure over-approximates (20-50% precision) \u2014 drives RETEST MARKING, never verdicts"
},
{
"disposition": "ADOPT",
"item": "Generation-pinned confirmation state (merge-queue semantics: confirmation belongs to a generation, not forever)",
"ref": "github merge-queue",
"why": "history append-only, never auto-reverts, always names the verifying generation"
},
{
"disposition": "INNOVATE",
"item": "Feedback correction loop: predicted-vs-observed impact persisted per change; misses WIDEN future closures",
"ref": "netflix/facebook pattern",
"why": "no mainstream tool logs prediction accuracy per edge; substitutes for coverage instrumentation we don't control"
},
{
"disposition": "COMBINE",
"item": "Context packets: derived/budgeted code layer + thin curated behaviour-ledger layer; NOT hand-maintained prose (rots), NOT embedding-first (similarity\u2260causality)",
"ref": "aider.chat/docs/repomap",
"why": "aider repomap proves derived context; behaviour lines add intent/risk repomaps lack"
},
{
"disposition": "ADAPT",
"item": "Dynamic /testing queue with reopen semantics; process dependents after providers, topological order",
"ref": "gitlab flaky insights",
"why": "merge-queue eviction + Nx last-successful-commit anchoring avoid re-chasing fixed sets"
},
{
"disposition": "REJECT",
"item": "Mutation testing as continuous gate",
"ref": "mutmut",
"why": "too expensive per-change; retained as audit-time oracle idea only"
},
{
"disposition": "REJECT",
"item": "Embedding indexes as PRIMARY dependency mechanism",
"ref": "cursor/cody",
"why": "similarity is not causality; keep structural closure primary"
}
],
"implications": {
"architecture": [],
"migration": [],
"structural": [],
"verification": []
},
"inward": {
"findings": [],
"refs": []
},
"issue_ref": "research/t19-quicksand",
"kind": "build_basis",
"outward": {
"findings": [],
"prior_art_questions_answered": [
"what already exists addressing this intent",
"what open-source systems/products/workflows attempt it",
"what architectural patterns already work",
"what standards/conventions exist",
"what can be directly ADOPTED",
"what can be ADAPTED",
"what can be COMBINED",
"what should be REJECTED",
"where is genuine INNOVATION required"
],
"refs": [
"https://bazel.build/query/language",
"https://nx.dev/concepts/affected",
"https://netflixtechblog.com/practical-intelligent-test-selection-95423d2516d6",
"https://docs.pact.io",
"https://aider.chat/docs/repomap.html",
"https://docs.github.com/en/pull-requests/merging-a-pull-request-with-a-merge-queue"
]
},
"question": "How should Scaffolding safely iterate a large evolving codebase without quicksand, while minimizing LLM context and rediscovery cost?",
"risks": [],
"schema_version": 1,
"uncertainty": []
}
+85
View File
@@ -0,0 +1,85 @@
{
"packet": {
"schema_version": 1,
"intent": "add /testing command backed by testing_mode.aggregate",
"target": "component:rune_tui",
"target_kind": "component",
"owning_domain": "domain:execution-integration",
"local": {
"schema_version": 1,
"capability_id": "capability:tui-testing-command",
"name": "TUI /testing command",
"purpose": "aggregate outstanding validations across registered scaffolded projects; minimal command surface",
"component": "component:rune_tui",
"source_locations": [
"src/rune/rune_tui.py"
],
"contract": "/testing \u2192 aggregate; /testing <project> \u2192 filtered; items show behaviour/procedure/criteria/state",
"state_touched": "",
"dependencies": [],
"consumers": [],
"invariants": [
"testing_mode backend is authority; TUI renders"
],
"behaviour_specs": [
"behaviour:19l-testing-aggregate"
],
"maturity": "implemented",
"constraints": "",
"generation": "",
"limitations": []
},
"local_files": [
"src/rune/rune_tui.py"
],
"beneath_count": 0,
"connected": {
"dependencies": [],
"consumers": [],
"interface_mediated_consumers": [],
"interfaces_provided": [],
"state_owned": [],
"verification": [],
"deployment": [],
"uncertainty": [
{
"kind": "dynamic-consumer",
"note": "static model cannot see dynamic dispatch/string-keyed lookup/late-binding consumers \u2014 reconcilers must verify at runtime"
}
]
},
"basis_summary": null,
"generated_at": "2026-09-16T00:11:56Z"
},
"preview": {
"schema_version": 1,
"target": "component:rune_tui",
"affected_domains": [
"domain:execution-integration"
],
"affected_subsystems": [],
"consumers_connectors": [],
"candidate_files": [],
"contract_scope_additions": [],
"state_implications": [],
"environment_runtime_implications": [],
"migration_implications": [],
"risks_uncertainties": [
{
"kind": "dynamic-consumer",
"note": "static model cannot see dynamic dispatch/string-keyed lookup/late-binding consumers \u2014 reconcilers must verify at runtime"
}
],
"verification_plan": [],
"rollback_implications": [
"revert contract scope commits; deployment rollbacks via last-good symlink (canonical deploy machinery)"
],
"basis_summary": {
"depth": "INWARD",
"why": "route 'INWARD' unknown \u2014 INWARD floor (never silent NONE)",
"route": "INWARD",
"mode": "",
"decided_at": "2026-09-16T00:11:56Z"
}
}
}
+99
View File
@@ -0,0 +1,99 @@
{
"alternatives_considered": [],
"created_at": "2026-09-16T01:24:46Z",
"deeper": {
"findings": [],
"refs": [],
"used": false
},
"depth": {
"decided_at": "2026-09-16T01:24:46Z",
"depth": "FULL",
"mode": "",
"route": "RESEARCHED_CHANGE",
"why": "route RESEARCHED_CHANGE default"
},
"dispositions": [
{
"disposition": "ADOPT",
"item": "kind:name stable IDs with path-as-attribute + alias records on move",
"ref": "backstage.io",
"why": "Backstage entity-ref discipline; Kythe VName declines cross-version stability (we promise it); uid-style instability rejected"
},
{
"disposition": "ADOPT",
"item": "dynamic-uncertainty edges (static vs dynamic vs exotic taxonomy)",
"ref": "dependency-cruiser",
"why": "dependency-cruiser taxonomy validates our explicit dynamic edges; import-linter uncovered=unknown framing"
},
{
"disposition": "ADOPT",
"item": "strangler-fig: model per subsystem alongside legacy; bounded adapters as transitional architecture",
"ref": "martinfowler.com",
"why": "incremental retrofit without big-bang"
},
{
"disposition": "ADOPT",
"item": "two-tier coverage: manifest coverage vs relationship coverage; fail CI on empty-scope contracts",
"ref": "archunit.org",
"why": "ArchUnit failOnEmptyShould prevents vacuous passes; file-in-manifest \u2260 ownership-known"
},
{
"disposition": "COMBINE",
"item": "LSP call hierarchy for symbol precision where static",
"ref": "lsp spec",
"why": "with import-graph evidence; dynamic dispatch stays runtime-verified"
},
{
"disposition": "ADAPT",
"item": "characterization tests before contract enforcement on legacy adapters",
"ref": "feathers",
"why": "Feathers legacy pattern; one characterization test per adapter"
},
{
"disposition": "INNOVATE",
"item": "per-edge trust weights from predicted-vs-observed history",
"ref": "t19 basis",
"why": "impact_deltas.jsonl compounds into precision; no mainstream tool logs per-edge prediction accuracy"
}
],
"implications": {
"architecture": [],
"migration": [],
"structural": [],
"verification": []
},
"inward": {
"findings": [],
"refs": []
},
"issue_ref": "research/t20-retrofit",
"kind": "build_basis",
"outward": {
"findings": [],
"prior_art_questions_answered": [
"what already exists addressing this intent",
"what open-source systems/products/workflows attempt it",
"what architectural patterns already work",
"what standards/conventions exist",
"what can be directly ADOPTED",
"what can be ADAPTED",
"what can be COMBINED",
"what should be REJECTED",
"where is genuine INNOVATION required"
],
"refs": [
"https://kythe.io/docs/schema/",
"https://github.com/sourcegraph/scip/blob/main/scip.proto",
"https://backstage.io/docs/features/software-catalog/descriptor-format",
"https://import-linter.readthedocs.io/en/stable/",
"https://github.com/sverweij/dependency-cruiser/blob/main/doc/rules-reference.md",
"https://martinfowler.com/bliki/StranglerFigApplication.html",
"https://www.archunit.org/userguide/html/000_Index.html"
]
},
"question": "How to retrofit Rune's ~160 modules into a structural model incrementally, without big-bang rewrite or excessive granularity?",
"risks": [],
"schema_version": 1,
"uncertainty": []
}
@@ -0,0 +1,35 @@
# 20I — Spine modernization decision (2026-09-16)
## What Spine stores today
- 150 module entities (flat, all with source identities), 8 subsystems,
1 deployment_surface. NO domains, NO components-as-structural-units,
NO interfaces, NO test_suites, NO state stores, NO environments.
- Strengths: module-level structural facts (imports, tests, maturity
ladder), umbrella ingest, impact_frontier, byte-deterministic snapshots.
## Rune model (store/rune_model) today
- 55 entities: domains (9), subsystems (11), components (31), 1 interface,
2 test_suites. Construction-governance oriented.
## Authority boundary (rule E — defined, not duplicated)
| Data | Canonical home | Consumers |
|---|---|---|
| Module-level facts (imports, maturity, canary wiring) | **Spine** | umbrella gates, Henxels, structural health |
| Domain/subsystem/component nesting + purpose | **Rune model** | construction, impact, /testing narrowing |
| Capability ledgers | **Rune model** (capability_ledger.json) | surgical packets |
| Behaviour specs + validation state | **Rune model** (behaviour_ledger.json) | /testing, invalidation |
| Cross-store ID discipline | **shared** (kind:name) | both |
## Modernization performed (bounded, rule I)
1. Spine projectors now READ the Rune model for domain membership
(no copy): spine's subsystem entities gain an `ext.rune_domain`
pointer where the Rune model nests them — LINKED, not copied.
2. Drift rule: if a Spine module's source disappears while the Rune
model still claims it (or vice versa), detect_structural_drift flags
MATERIAL — neither store silently contradicts the other.
## What was NOT done
- No Spine schema migration (domains not forced into Spine).
- No bulk copy of Rune-model entities into Spine.
- No graph_projection authority claims (projection stays read-only).
+55
View File
@@ -0,0 +1,55 @@
[
{
"candidate": "component:build_mode_adapter",
"purpose": "worktree/ledger bounded adapter",
"callers": [
"rune_tui (gated)",
"tests"
],
"dynamic_risk": "low (no reflection entry)",
"replacement": "component:scaffold + workspace lifecycle",
"classification": "BOUNDED_ADAPTER"
},
{
"candidate": "component:task_persist",
"purpose": "persisted.json export ledger (T17: demoted)",
"callers": [
"auto_context",
"CLI only"
],
"dynamic_risk": "low",
"replacement": "TaskStateRepository (canonical)",
"classification": "MIGRATION_CANDIDATE"
},
{
"candidate": "canary_native + canary_full_system",
"purpose": "layered overlap, acceptable per 17I",
"callers": [
"CI",
"spine registry"
],
"dynamic_risk": "none",
"replacement": "keep layered",
"classification": "KEEP"
},
{
"candidate": "component:complexity_scorer",
"purpose": "complexity hint (prints, never gates)",
"callers": [
"rune_tui:2105"
],
"dynamic_risk": "none",
"replacement": "adaptive_router (authoritative)",
"classification": "RETIREMENT_CANDIDATE"
},
{
"candidate": "message_queue.detect_task_addition",
"purpose": "chat\u2192task queue with regex confidence",
"callers": [
"rune_tui:2070"
],
"dynamic_risk": "none",
"replacement": "construction_intent (semantic)",
"classification": "MIGRATION_CANDIDATE"
}
]
+29
View File
@@ -0,0 +1,29 @@
# 20P — CI/type-check determinism investigation (2026-09-16)
## Finding
With the lock at parity (propcache 0.5.4/yarl 1.25.1 pinned everywhere),
runner-user and nick-user pyright on the SAME venv give IDENTICAL
results (121 errors, 442 warnings, 211 files). The T14H-class skew is
NOT user-environment — it is LOCK-CONTENT skew: when requirements-ci.txt
pins drift between local and CI (propcache churned twice mid-T19/T20),
the two environments resolve different package versions.
## Root cause
`uv pip compile` regenerates against the live index; any upstream
release between a local compile and a CI run shifts resolution.
requirements-ci.txt pins the RESULT, but the baseline was being
re-baselined against whichever environment ran last.
## Fix applied
- Lock re-pinned to current (0.5.4/1.25.1) — verified parity (0 diff lines
between fresh compile and committed lock at python-version 3.11).
- Baseline set against CI's fresh-resolution arithmetic (454) — matches
the environment the gate actually runs in.
- RULE recorded: when the lock changes, re-baseline against a venv built
from the NEW lock, never against the old venv.
## Honest residual
Local pre-flight cannot perfectly predict CI when upstream releases land
between push and CI run. The structural fix (fully hermetic lockfile
with hashes) is a bounded item for the retrofit tranche, recorded as
behaviour VAL-RUNE-010.
+63
View File
@@ -0,0 +1,63 @@
# 20Q — surgical-context benchmarks across Rune domains (2026-09-16)
## Task 1: provider decision ownership (inference-providers domain)
- COLD (fresh explore agent): running in parallel — results appended below.
- SCAFFOLD-GUIDED: 1 context_packet call (0 file reads), answered from
capability ledger + structural edges: owner=rune_operator,
influences=adaptive_router+construction_mode, consumers=rune_tui.
Context: ~700-byte ledger entry + structural closure. No repo scan.
## Task 2: task-state canonical authority (state-resilience domain)
- SCAFFOLD-GUIDED: 0 file reads — ledger state_touched field answers
directly: "TaskStateRepository (canonical), operator.json (legacy
cache)". Invariant recorded: "route decided before execution".
## Honest assessment
The cold agents' parallel results will show whether raw discovery cost
differs. What the scaffold demonstrably provides regardless of speed:
- authority answers carry the VALIDATION state (VAL-* links),
- obligations (what to retest) come free with the answer,
- the answer is stable across sessions (no re-derivation each time).
## Structural facts
- Rune model: 107 entities, 94 relationships, 37% file coverage
(50/136 — honest: NOT "fully mapped"), 3 state stores, 9 domains.
- Capability ledgers: 4 representative (699-866 bytes each — compact).
- Behaviour ledger: 10 specs (8 AUTO_VERIFIED, 1 manual pending,
1 hardening item), 0 USER_CONFIRMED (none manufactured).
## Cold results (parallel fresh agents, received)
### Task 1: provider decision ownership
- COLD: **16 tool calls, 10 files opened** — rune_operator.py:1015-1066
(tier loop = final decider), model_routing rank() advisory,
work_manager route_batch, model_connect, tiered_executor, TUI
override, subsystem_health fallback — 9+ influencing modules mapped.
- SCAFFOLD-GUIDED: 1 packet call, 0 file reads. Answer: owner +
adaptive_router/construction_mode as dependencies, rune_tui consumer.
- **Assessment: cold found MORE breadth (9 influencers vs 2 deps) —
the model's dependency edges were COARSER than cold analysis.**
Honest: for "list every influencer" questions, cold grep+sweep wins.
The scaffold's edge set under-models advisory-only influencers
(rank() is advisory — not a depends_on edge by our vocabulary).
### Task 2: task-state canonical authority
- COLD: **11 tool calls, 2 files** — found canonical repo + caches +
RISK: ~10 non-canonical writers mutate RuneTask.status THEN mirror
via transition(); crash between = legacy ahead of truth;
_task_counters falls back to cache on empty repo.
- SCAFFOLD-GUIDED: 0 file reads — ledger answered canonical-vs-cache
directly but did NOT surface the write-ordering crash-window risk.
- **Assessment: cold found a REAL defect-shaped risk the ledger
lacked.** The ledger's invariants covered ownership, not failure
ordering. This is a genuine ledger-content gap, recorded.
## Verdict (honest, per mandate)
- Speed: scaffold-guided is faster on narrow authority questions
(1 vs 11-16 calls) but this repo is well-organized; on breadth
questions cold sweeps still win.
- The scaffold's structural edges are COARSER than code reality —
impact closure over-approximates narrowly (misses advisory-only
influencers) and ledgers lack failure-ordering detail.
- Improvement recorded: capability ledgers should carry a
"risk_notes" field for write-ordering/failure-window facts.
Loaded 100 of 190 files, more files were not shown because too many files have changed in this diff. Show more