Compare commits

...
85 Commits
Author SHA1 Message Date
conductor 71f051d928 chore(release): auto-bump version to 0.71.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 30s
CI Pipeline / lint (push) Failing after 28s
CI Pipeline / unit-tests (push) Successful in 2m47s
CI Pipeline / canary (push) Successful in 30s
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / type-check (push) Successful in 34s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / required-checks (push) Failing after 2s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 7s
deploy / deploy (push) Failing after 2m31s
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
CI Pipeline / best-practices (push) Successful in 12s
CI Pipeline / lint (push) Successful in 43s
CI Pipeline / canary (push) Successful in 36s
CI Pipeline / documentation (push) Successful in 8s
Auto-version and Release / release (push) Failing after 1m1s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / unit-tests (push) Successful in 2m53s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 7s
deploy / deploy (push) Successful in 3m36s
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))
Auto-version and Release / release (push) Successful in 22s
CI Pipeline / unit-tests (push) Successful in 2m50s
CI Pipeline / lint (push) Failing after 28s
deploy / deploy (push) Failing after 1m29s
CI Pipeline / canary (push) Successful in 31s
CI Pipeline / best-practices (push) Successful in 6s
CI Pipeline / documentation (push) Successful in 8s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / required-checks (push) Failing after 4s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 7s
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
CI Pipeline / best-practices (push) Successful in 12s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / lint (push) Failing after 37s
Auto-version and Release / release (push) Successful in 44s
CI Pipeline / canary (push) Successful in 37s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / unit-tests (push) Successful in 2m55s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 8s
deploy / deploy (push) Failing after 5m0s
- 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))
Auto-version and Release / release (push) Successful in 29s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 8s
CI Pipeline / lint (push) Successful in 44s
CI Pipeline / unit-tests (push) Successful in 2m54s
CI Pipeline / canary (push) Successful in 36s
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 10s
CI Pipeline / type-check (push) Successful in 34s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Successful in 2s
deploy / deploy (push) Failing after 6m19s
Promotion record: (unknown)
2026-09-16 05:09:17 +01:00
Nick abe24da81d chore(store): T21 evidence
CI Pipeline / canary (push) Successful in 39s
CI Pipeline / best-practices (push) Successful in 9s
Auto-version and Release / release (push) Failing after 1m0s
CI Pipeline / documentation (push) Successful in 9s
CI Pipeline / type-check (push) Successful in 32s
CI Pipeline / lint (push) Successful in 47s
CI Pipeline / unit-tests (push) Successful in 2m55s
CI Pipeline / gitea-offline (push) Successful in 20s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 6s
deploy / deploy (push) Successful in 4m14s
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))
Auto-version and Release / release (push) Successful in 21s
deploy / deploy (push) Failing after 2m31s
CI Pipeline / unit-tests (push) Successful in 2m46s
CI Pipeline / lint (push) Successful in 39s
CI Pipeline / canary (push) Successful in 31s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 6s
Promotion record: (unknown)
2026-09-16 05:02:04 +01:00
Nick e2f536f71a feat(t21): living capability specs + strict internal SemVer (21C/21D/21E)
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / gitea-offline (push) Successful in 20s
CI Pipeline / type-check (push) Successful in 50s
Auto-version and Release / release (push) Failing after 1m23s
CI Pipeline / lint (push) Successful in 43s
CI Pipeline / canary (push) Successful in 31s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / unit-tests (push) Successful in 2m59s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 6s
deploy / deploy (push) Successful in 5m58s
- 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))
Auto-version and Release / release (push) Successful in 16s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 13s
CI Pipeline / lint (push) Successful in 40s
CI Pipeline / unit-tests (push) Failing after 3m12s
CI Pipeline / canary (push) Successful in 29s
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 5s
deploy / deploy (push) Failing after 2m25s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / required-checks (push) Failing after 2s
Promotion record: (unknown)
2026-09-16 04:53:32 +01:00
Nick 124b77e95b feat(t21): wait-guard regression protection + MiMo controller hook (21B)
CI Pipeline / documentation (push) Successful in 7s
CI Pipeline / lint (push) Successful in 40s
CI Pipeline / canary (push) Successful in 39s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / best-practices (push) Successful in 6s
CI Pipeline / gitea-offline (push) Successful in 23s
Auto-version and Release / release (push) Successful in 1m19s
CI Pipeline / unit-tests (push) Successful in 2m54s
CI Pipeline / required-checks (push) Successful in 2s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 8s
deploy / deploy (push) Successful in 6m23s
- 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))
Auto-version and Release / release (push) Successful in 26s
CI Pipeline / required-checks (push) Successful in 2s
CI Pipeline / lint (push) Successful in 40s
CI Pipeline / unit-tests (push) Successful in 2m59s
CI Pipeline / canary (push) Successful in 31s
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 10s
CI Pipeline / type-check (push) Successful in 35s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 12s
deploy / deploy (push) Successful in 3m9s
Promotion record: (unknown)
2026-09-16 04:03:24 +01:00
Nick fcd835883e chore(store): T20 evidence
CI Pipeline / lint (push) Successful in 43s
CI Pipeline / canary (push) Successful in 39s
Auto-version and Release / release (push) Successful in 45s
CI Pipeline / type-check (push) Successful in 33s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 9s
CI Pipeline / unit-tests (push) Successful in 2m54s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 12s
deploy / deploy (push) Successful in 4m13s
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))
Auto-version and Release / release (push) Successful in 20s
deploy / deploy (push) Failing after 2m34s
CI Pipeline / unit-tests (push) Successful in 2m45s
CI Pipeline / lint (push) Failing after 26s
CI Pipeline / canary (push) Successful in 34s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 7s
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)
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / canary (push) Successful in 33s
Auto-version and Release / release (push) Successful in 46s
CI Pipeline / lint (push) Successful in 47s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / type-check (push) Successful in 32s
CI Pipeline / unit-tests (push) Successful in 2m52s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 5s
deploy / deploy (push) Successful in 5m32s
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))
Auto-version and Release / release (push) Successful in 29s
CI Pipeline / unit-tests (push) Successful in 2m48s
CI Pipeline / lint (push) Failing after 31s
CI Pipeline / canary (push) Successful in 36s
CI Pipeline / best-practices (push) Successful in 7s
deploy / deploy (push) Failing after 58s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / required-checks (push) Failing after 3s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 8s
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)
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / canary (push) Successful in 36s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / lint (push) Successful in 49s
Auto-version and Release / release (push) Failing after 54s
CI Pipeline / unit-tests (push) Successful in 2m54s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 8s
deploy / deploy (push) Successful in 5m48s
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))
Auto-version and Release / release (push) Successful in 19s
deploy / deploy (push) Failing after 2m32s
CI Pipeline / lint (push) Failing after 27s
CI Pipeline / unit-tests (push) Successful in 2m49s
CI Pipeline / canary (push) Successful in 33s
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 6s
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)
CI Pipeline / best-practices (push) Successful in 11s
CI Pipeline / documentation (push) Successful in 9s
CI Pipeline / canary (push) Successful in 35s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / type-check (push) Successful in 29s
Auto-version and Release / release (push) Failing after 1m17s
CI Pipeline / lint (push) Successful in 39s
CI Pipeline / unit-tests (push) Successful in 2m54s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 5s
deploy / deploy (push) Successful in 2m32s
- 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))
Auto-version and Release / release (push) Successful in 21s
CI Pipeline / lint (push) Failing after 31s
CI Pipeline / unit-tests (push) Successful in 2m45s
CI Pipeline / canary (push) Successful in 29s
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / required-checks (push) Failing after 1s
deploy / deploy (push) Failing after 6m29s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 7s
Promotion record: (unknown)
2026-09-16 03:02:15 +01:00
Nick 478ce45755 feat(retrofit): T20 0.67.0 MINOR release record (semantic policy)
Auto-version and Release / release (push) Successful in 27s
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 7s
CI Pipeline / lint (push) Failing after 30s
CI Pipeline / canary (push) Successful in 33s
CI Pipeline / gitea-offline (push) Successful in 21s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / unit-tests (push) Successful in 2m43s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 7s
deploy / deploy (push) Failing after 15m32s
2026-09-16 03:02:04 +01:00
Nick a96ed90b90 feat(retrofit): rune taxonomy + 20Q honesty fixes + 20W matrix + coverage (T20 20D-20W)
CI Pipeline / best-practices (push) Successful in 12s
CI Pipeline / documentation (push) Successful in 7s
CI Pipeline / canary (push) Successful in 39s
CI Pipeline / lint (push) Successful in 49s
Auto-version and Release / release (push) Successful in 57s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / unit-tests (push) Successful in 2m55s
CI Pipeline / gitea-offline (push) Successful in 23s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 5s
deploy / deploy (push) Successful in 7m35s
- 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))
Auto-version and Release / release (push) Successful in 34s
deploy / deploy (push) Failing after 48s
CI Pipeline / unit-tests (push) Successful in 2m45s
CI Pipeline / lint (push) Failing after 34s
CI Pipeline / canary (push) Successful in 31s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / required-checks (push) Failing after 2s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 9s
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)
CI Pipeline / best-practices (push) Successful in 12s
CI Pipeline / documentation (push) Successful in 11s
CI Pipeline / lint (push) Successful in 52s
Auto-version and Release / release (push) Failing after 1m2s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / canary (push) Successful in 33s
CI Pipeline / unit-tests (push) Failing after 2m51s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 7s
deploy / deploy (push) Failing after 5m47s
- 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))
Auto-version and Release / release (push) Successful in 15s
deploy / deploy (push) Failing after 1m53s
CI Pipeline / unit-tests (push) Successful in 2m42s
CI Pipeline / lint (push) Failing after 27s
CI Pipeline / canary (push) Successful in 27s
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
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)
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 7s
CI Pipeline / canary (push) Successful in 38s
Auto-version and Release / release (push) Successful in 40s
CI Pipeline / lint (push) Successful in 45s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / unit-tests (push) Successful in 2m51s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 5s
deploy / deploy (push) Successful in 5m3s
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))
Auto-version and Release / release (push) Successful in 15s
CI Pipeline / lint (push) Failing after 31s
deploy / deploy (push) Failing after 2m49s
CI Pipeline / unit-tests (push) Successful in 2m49s
CI Pipeline / canary (push) Successful in 30s
CI Pipeline / best-practices (push) Successful in 6s
CI Pipeline / documentation (push) Successful in 11s
CI Pipeline / type-check (push) Failing after 29s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 7s
Promotion record: (unknown)
2026-09-16 01:25:08 +01:00
Nick 80a1fd0a2d chore(manifest): regen after testing_mode
CI Pipeline / best-practices (push) Successful in 12s
CI Pipeline / documentation (push) Successful in 7s
CI Pipeline / canary (push) Successful in 35s
CI Pipeline / lint (push) Failing after 37s
CI Pipeline / type-check (push) Failing after 32s
CI Pipeline / gitea-offline (push) Successful in 18s
Auto-version and Release / release (push) Failing after 55s
CI Pipeline / unit-tests (push) Successful in 2m49s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 5s
deploy / deploy (push) Failing after 6m10s
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))
Auto-version and Release / release (push) Successful in 21s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 6s
CI Pipeline / lint (push) Successful in 37s
CI Pipeline / unit-tests (push) Successful in 2m41s
CI Pipeline / canary (push) Successful in 28s
deploy / deploy (push) Successful in 2m40s
CI Pipeline / best-practices (push) Successful in 6s
CI Pipeline / documentation (push) Successful in 10s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: (unknown)
2026-09-16 00:55:11 +01:00
Nick 94a4774853 feat(t19): project registry + capability/behaviour ledgers + iteration engine (19A-19I)
CI Pipeline / best-practices (push) Successful in 11s
CI Pipeline / canary (push) Successful in 34s
CI Pipeline / lint (push) Successful in 46s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / documentation (push) Successful in 4s
Auto-version and Release / release (push) Failing after 1m11s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / unit-tests (push) Successful in 2m50s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
deploy / deploy (push) Successful in 2m35s
- 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))
Auto-version and Release / release (push) Successful in 16s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 9s
CI Pipeline / lint (push) Successful in 44s
CI Pipeline / unit-tests (push) Successful in 2m48s
CI Pipeline / canary (push) Successful in 30s
deploy / deploy (push) Successful in 2m10s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Successful in 2s
Promotion record: (unknown)
2026-09-15 23:56:47 +01:00
Nick 99115889d7 chore(store): manifest regen
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / canary (push) Successful in 34s
CI Pipeline / lint (push) Successful in 49s
CI Pipeline / documentation (push) Successful in 18s
Auto-version and Release / release (push) Failing after 55s
CI Pipeline / type-check (push) Successful in 32s
CI Pipeline / unit-tests (push) Successful in 2m52s
CI Pipeline / gitea-offline (push) Successful in 22s
CI Pipeline / required-checks (push) Successful in 2s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 7s
deploy / deploy (push) Successful in 1m36s
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))
Auto-version and Release / release (push) Successful in 23s
CI Pipeline / unit-tests (push) Successful in 2m42s
deploy / deploy (push) Failing after 1m30s
CI Pipeline / lint (push) Failing after 29s
CI Pipeline / canary (push) Successful in 29s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / type-check (push) Failing after 29s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / required-checks (push) Failing after 2s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 5s
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)
CI Pipeline / best-practices (push) Successful in 11s
CI Pipeline / documentation (push) Successful in 9s
CI Pipeline / canary (push) Successful in 34s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / type-check (push) Failing after 33s
Auto-version and Release / release (push) Successful in 1m2s
CI Pipeline / lint (push) Failing after 27s
CI Pipeline / unit-tests (push) Successful in 2m49s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
deploy / deploy (push) Failing after 1m9s
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))
Auto-version and Release / release (push) Successful in 19s
deploy / deploy (push) Failing after 1m3s
CI Pipeline / unit-tests (push) Successful in 2m46s
CI Pipeline / lint (push) Failing after 26s
CI Pipeline / canary (push) Successful in 39s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / type-check (push) Failing after 29s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 7s
Promotion record: (unknown)
2026-09-15 23:34:23 +01:00
Nick 186fdd1410 chore(store): T18 self-scaffold + evidence
CI Pipeline / lint (push) Failing after 29s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / canary (push) Successful in 43s
Auto-version and Release / release (push) Successful in 49s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / type-check (push) Failing after 29s
CI Pipeline / unit-tests (push) Successful in 2m45s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
deploy / deploy (push) Failing after 4m37s
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))
Auto-version and Release / release (push) Successful in 19s
deploy / deploy (push) Failing after 57s
CI Pipeline / lint (push) Failing after 26s
CI Pipeline / unit-tests (push) Successful in 2m42s
CI Pipeline / canary (push) Successful in 29s
CI Pipeline / best-practices (push) Successful in 20s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 7s
Promotion record: (unknown)
2026-09-15 23:01:01 +01:00
Nick b7865a76bb chore(manifest): regenerate after forge registration
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / documentation (push) Successful in 7s
CI Pipeline / lint (push) Failing after 38s
CI Pipeline / canary (push) Successful in 38s
Auto-version and Release / release (push) Successful in 51s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / unit-tests (push) Successful in 2m54s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
deploy / deploy (push) Failing after 4m48s
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))
Auto-version and Release / release (push) Successful in 22s
deploy / deploy (push) Failing after 1m21s
CI Pipeline / lint (push) Failing after 27s
CI Pipeline / unit-tests (push) Failing after 3m0s
CI Pipeline / canary (push) Successful in 28s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 6s
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)
CI Pipeline / best-practices (push) Successful in 6s
CI Pipeline / documentation (push) Successful in 7s
CI Pipeline / gitea-offline (push) Successful in 19s
Auto-version and Release / release (push) Successful in 54s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / lint (push) Failing after 30s
CI Pipeline / canary (push) Successful in 34s
CI Pipeline / unit-tests (push) Failing after 2m48s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
deploy / deploy (push) Failing after 1m14s
- 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
CI Pipeline / best-practices (push) Successful in 9s
Auto-version and Release / release (push) Successful in 20s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / canary (push) Successful in 35s
CI Pipeline / lint (push) Successful in 43s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / unit-tests (push) Successful in 2m45s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 5s
deploy / deploy (push) Successful in 2m28s
2026-09-15 20:18:48 +01:00
Nick 927548e9a0 chore(store): T17 evidence records
Auto-version and Release / release (push) Successful in 23s
CI Pipeline / lint (push) Successful in 38s
CI Pipeline / canary (push) Successful in 29s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Successful in 2m36s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 6s
deploy / deploy (push) Successful in 2m5s
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))
Auto-version and Release / release (push) Successful in 15s
CI Pipeline / lint (push) Successful in 37s
CI Pipeline / unit-tests (push) Successful in 2m41s
CI Pipeline / canary (push) Successful in 31s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Failing after 4m35s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 5s
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)
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / documentation (push) Successful in 5s
Auto-version and Release / release (push) Successful in 39s
CI Pipeline / canary (push) Successful in 36s
CI Pipeline / lint (push) Successful in 45s
CI Pipeline / type-check (push) Successful in 32s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / unit-tests (push) Successful in 2m47s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 5s
deploy / deploy (push) Successful in 7m53s
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
conductor 6e2f5950bd chore(release): auto-bump version to 0.66.71 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 17s
deploy / deploy (push) Failing after 1m19s
CI Pipeline / unit-tests (push) Successful in 2m38s
CI Pipeline / lint (push) Successful in 35s
CI Pipeline / canary (push) Successful in 27s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Failing after 28s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
Promotion record: (unknown)
2026-09-15 19:49:43 +01:00
Nick c53c82bb35 chore(store): 17R proof records
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / documentation (push) Successful in 5s
Auto-version and Release / release (push) Successful in 31s
CI Pipeline / canary (push) Successful in 34s
CI Pipeline / lint (push) Successful in 43s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / type-check (push) Failing after 30s
CI Pipeline / unit-tests (push) Successful in 2m50s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
deploy / deploy (push) Failing after 4m36s
2026-09-15 19:49:18 +01:00
Nick 35adcd8cb1 feat(consolidation): task-state authority + /build classification gate (T17 17F/17G)
17F: TaskStateRepository.list_all() added; get_status task counters now
derive from the CANONICAL repo (created→pending mapping; in-memory
fallback only for a fresh empty store). operator.json's task list is
demoted to a legacy cache — never a second authority. 3 held-outs.

17G: '/build start' now runs the SAME canonical Scaffolding
classification as every other construction route — NOT_CONSTRUCTION
refuses, UNKNOWN surfaces probe questions, classification-unavailable
fails closed. BuildManager retained as a bounded adapter (worktree
isolation + resume ledger are its unique capabilities; contract
activation stays on the operator hook path with the ONE activate()).

Suite 1920 green; ratchets PASS.
2026-09-15 19:49:18 +01:00
conductor 810d0027c3 chore(release): auto-bump version to 0.66.70 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 15s
CI Pipeline / unit-tests (push) Successful in 2m37s
CI Pipeline / lint (push) Successful in 35s
CI Pipeline / canary (push) Successful in 28s
CI Pipeline / best-practices (push) Successful in 5s
deploy / deploy (push) Failing after 1m22s
CI Pipeline / documentation (push) Successful in 8s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
Promotion record: (unknown)
2026-09-15 19:38:59 +01:00
Nick 4839a62b70 chore(spine): rebuild after 17R contract
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / canary (push) Successful in 37s
Auto-version and Release / release (push) Successful in 42s
CI Pipeline / lint (push) Successful in 46s
CI Pipeline / type-check (push) Successful in 32s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Successful in 2m47s
CI Pipeline / required-checks (push) Successful in 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
deploy / deploy (push) Successful in 4m44s
2026-09-15 19:38:31 +01:00
Nick 15408841ac feat(context): request-modal handling + 17R conversational self-hosting proof
17R live finding: 'can you fix that?' is an imperative in interrogative
clothing — the terminal-? rule misrouted polite requests to DISCUSSION
and the first proof run proceeded on a non-load-bearing intent. Fixed:
request modals (can/could/would you, please) + construction verb =
ACTION with USER_APPROVED; information openers (how/what/why/should)
outrank modals ('how would you build X?' stays method discussion).
Held-outs added for both.

17R PROOF (load-bearing intent, end-to-end): conversational request →
USER_APPROVED ConstructionIntent → classify_mode EXISTING_PROJECT_CHANGE
→ contract 17r2-task-persist-load (approved→building) → async
verification under the real event bridge (resumed via event, verdict
success) → independent authority re-read PASS → verifying→closed with
evidence checkpoints. The fixed defect: task_persist.load() did not
exist — the TUI startup loader's AttributeError was silently swallowed
(dead startup task loading, 17F audit finding).

Suite 1917 green.
2026-09-15 19:38:31 +01:00
conductor c5c69de06f chore(release): auto-bump version to 0.66.69 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 18s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / lint (push) Successful in 34s
CI Pipeline / unit-tests (push) Successful in 2m43s
CI Pipeline / canary (push) Successful in 47s
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Failing after 1m29s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 8s
Promotion record: (unknown)
2026-09-15 19:27:31 +01:00
Nick fcde0b2ce3 feat(context): conversational construction interpretation layer (T17 17I-17Q)
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 7s
Auto-version and Release / release (push) Successful in 39s
CI Pipeline / canary (push) Successful in 32s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / type-check (push) Successful in 26s
CI Pipeline / lint (push) Successful in 41s
CI Pipeline / unit-tests (push) Successful in 2m42s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 5s
deploy / deploy (push) Successful in 5m20s
construction_intent.py — the MEANING layer per the §17O authority
boundary: resolves action-vs-discussion (interrogative STRUCTURE, not
vocabulary: 'how would you build X?' never authorizes), multi-turn
referents against live proposals (recency-first, ordinal selectors,
ambiguity → clarification never guessed, stale-context resistance with
content-word revival, relative-pronoun 'that' excluded), explicit
imperative demands (creation verb + indefinite article = new standalone
artifact; strong/direct trigger without unnecessary re-asking), and the
DISCUSSION→PROPOSAL→USER_APPROVED lifecycle (suggestions are never
authorization). Emits the full §17N structured ConstructionIntent;
feeds the ONE canonical classifier — no second classifier.

classify_mode: indefinite-article new-artifact evidence, with structural
precedence (a known existing target means article language describes a
CHANGE, not a new project — §5).

Held-outs: 22 cases including all 20 §17Q matrix items with unseen
paraphrases; live-defect fixes found BY the matrix (relative-that,
question/imperative 'do', turn accounting, stale detection).

Suite 1915 green; ratchets PASS; Henxels rc=0.
2026-09-15 19:27:04 +01:00
conductor 1f96055b69 chore(release): auto-bump version to 0.66.68 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 12s
deploy / deploy (push) Failing after 48s
CI Pipeline / unit-tests (push) Successful in 2m36s
CI Pipeline / lint (push) Successful in 35s
CI Pipeline / canary (push) Successful in 28s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
Promotion record: (unknown)
2026-09-15 19:11:06 +01:00
Nick 923a21ba0b chore(store): 17E programme proof records — durable programme + closed stage contracts
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / canary (push) Successful in 34s
CI Pipeline / documentation (push) Successful in 8s
CI Pipeline / lint (push) Successful in 44s
Auto-version and Release / release (push) Successful in 54s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / unit-tests (push) Successful in 2m45s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 5s
deploy / deploy (push) Successful in 4m16s
2026-09-15 19:10:46 +01:00
Nick 6348386869 fix(programme): truthful failure phase + typing cleanup (17E review)
Failed stages now land in phase=failed (legal building→failed
transition, evidence checkpoint on contract) instead of a swallowed
close attempt. S110/BLE001 eliminated — no exception swallowing in the
failure path. Ratchet-clean.
2026-09-15 19:10:46 +01:00
Nick 55002ad838 feat(programme): canonical continuation wiring + live 4-stage proof (T17 17D/17E)
programme_continuation was TEST-PROVEN but UNWIRED (16M finding). Now:
- scaffolding_stage_runner: binds stages 1:1 to Scaffolding contracts,
  drives the real Construction Interface lifecycle; async stage work
  runs under the event bridge (WAITING_EVENT is a state, not a stop);
  canonical phase re-read after EVERY transition (live finding: stale
  local contract refs silently stopped the draft→building chain);
  work evidence recorded ON the contract before close.
- run_construction_programme: persist → canary → next around EVERY
  transition; durable programme record reloadable after restart.
- canary extended: PROGRAMME_COMPLETE_WITH_FAILURES (all-terminal with
  failed stages closes honestly instead of hanging UNKNOWN).

LIVE PROOF (17E, real bridge, 4.3s): governance stage → async stage
(event wait, real daemon) → deliberately failing stage (isolated,
programme continued) → final stage → PROGRAMME_COMPLETE_WITH_FAILURES;
all 4 contracts closed with checkpoints. 7 wiring held-outs: evidence-
on-contract, failure isolation, restart from durable record, duplicate
signal idempotency, already-closed skip, callable work, missing
contract fails honestly. 15M verdict enum extended.
2026-09-15 19:10:46 +01:00
conductor 6684289a8f chore(release): auto-bump version to 0.66.67 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 13s
deploy / deploy (push) Failing after 49s
CI Pipeline / unit-tests (push) Successful in 2m40s
CI Pipeline / lint (push) Successful in 36s
CI Pipeline / canary (push) Successful in 26s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Successful in 4s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
Promotion record: (unknown)
2026-09-15 18:41:59 +01:00
Nick e25ad0f964 fix(bridge): re-land cross-user FIFO chmod + regression guards (T17 17C)
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / documentation (push) Successful in 6s
Auto-version and Release / release (push) Successful in 35s
CI Pipeline / canary (push) Successful in 36s
CI Pipeline / lint (push) Successful in 45s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Successful in 2m48s
CI Pipeline / required-checks (push) Successful in 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
deploy / deploy (push) Successful in 4m18s
Root cause of every post-T16 production wait sleeping to its deadline:
(a) the T16 os.chmod(fifo, 0o666) was lost in a rebase resolution —
FIFOs landed 644 (umask 022), the daemon (different user) got EACCES on
the resume write, rc=1 → RESUME_UNVERIFIED; same-user tests passed.
(b) waitstore _atomic_write (daemon snapshot 0b8f384) left mkstemp 0600
records the runner-user waiter could not read back.

Proofs: replay path exercised production-realistically against real
completed CI (event_replayed, 4.6s, authority re-read decided); full
cross-user delivery repro (runner user) now wakes in 2.05s; two
regression guards (mode assertion + cross-user delivery on the real
daemon). Daemon invariants 45/45 + 36/36.
2026-09-15 18:41:32 +01:00
Nick c9dee14c62 docs(t16): final tranche evidence — 16S/T/U/V/X/Y findings + 16Z convergence record
Auto-version and Release / release (push) Successful in 17s
CI Pipeline / lint (push) Successful in 40s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / canary (push) Successful in 30s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / unit-tests (push) Successful in 2m42s
CI Pipeline / required-checks (push) Successful in 2s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
deploy / deploy (push) Successful in 15m11s
2026-09-15 17:56:15 +01:00
conductor fe9d154778 chore(release): auto-bump version to 0.66.66 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 20s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
CI Pipeline / lint (push) Successful in 35s
CI Pipeline / unit-tests (push) Successful in 2m39s
CI Pipeline / canary (push) Successful in 31s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 15m15s
Promotion record: (unknown)
2026-09-15 17:39:29 +01:00
Nick 2a4f9c8367 chore(spine): rebuild after 16R contract registration
Auto-version and Release / release (push) Successful in 30s
CI Pipeline / lint (push) Successful in 41s
CI Pipeline / canary (push) Successful in 33s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Successful in 2m38s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 7s
deploy / deploy (push) Successful in 15m12s
2026-09-15 17:38:56 +01:00
Nick 7ddfa7eee7 fix(maintenance): canary-suite glob points at src/rune (T16 16Q/16R)
16Q: the job looped shared/canary_*.py — real canaries live in
src/rune/; it silently no-opped since the canaries moved.
16R: this bounded Rune-internal improvement was executed THROUGH the
retroactive Scaffolding flow — Need → contract 16R-maintenance-canary-
glob (approved→building→verifying→closed) with acceptance verified
independently (YAML parses, glob fixed, 24 canary files at target) —
proving the 16M/16N map drives actual construction, not just docs.
2026-09-15 17:38:56 +01:00
conductor 986b450bc2 chore(release): auto-bump version to 0.66.65 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 19s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / lint (push) Successful in 38s
CI Pipeline / unit-tests (push) Successful in 3m9s
CI Pipeline / canary (push) Successful in 33s
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
deploy / deploy (push) Successful in 15m16s
Promotion record: (unknown)
2026-09-15 17:34:28 +01:00
Nick b51afea0cb feat(reentry): durable event replay + drift-aware resume + 16O benchmark (T16 16C-fix/16O/16P)
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / canary (push) Successful in 35s
Auto-version and Release / release (push) Successful in 39s
CI Pipeline / lint (push) Successful in 46s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / unit-tests (push) Successful in 2m49s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Failing after 15m13s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 5s
- event_wait.find_matching_event(): replays matching completion events
  from the durable bridge event log at registration. Root-caused from
  TWO production cycles: on a serialized single-runner machine the CI
  wake always lands before/around deploy registration — a forward-only
  FIFO rendezvous can never deliver it (both cycles fell back to the
  900s deadline, truthfully). The event log exists precisely for this;
  the wake is still never the verdict.
- ci_event_wait: event_replayed mode short-circuits to the authority
  read; 2 new held-outs (9 total).
- drift_resume.py (16P): claim-vs-observation reconciliation with
  EXPECTED/BENIGN/MATERIAL/CONFLICTING/UNKNOWN classes; MATERIAL/
  CONFLICTING/UNKNOWN block dependent work; user-locked claims escalate
  to CONFLICTING (reality never silently overrides user-locked
  authority). Held-outs driven by the REAL 16O fixture: the VAT
  regression blocks resume; the checkpoint commit resumes clean.
- 16O benchmark persisted (store/evidence/t16/16O_benchmark.md): honest
  result — on a tiny fixture cold and scaffold-assisted re-entry cost
  about the same (8 vs 9 files); the scaffold's advantage is drift
  classification, recorded next actions, and intent, not raw step
  count. No fabricated win.

Suite 1884 green; ratchets PASS.
2026-09-15 17:34:00 +01:00
Nick c15840514f docs(structure): Rune structural map + 16M inventory + 16D live latency proof (T16 16M/16N/16D)
- docs/STRUCTURAL_MAP.md: fast re-entry surface — canonical routes,
  known duplicate routes, unfinished/blocked/deprecated, fresh-session
  quick answers. Companion to Spine (authority) and 16M inventory.
- store/evidence/t16/16M_inventory.md: full retroactive inventory —
  10 canonical systems + aux systems classified; duplicate routes
  priority-ordered (task-state ×4, build_mode legacy, provider ×5
  entry points, T14/T15 drivers, maintenance ledgers ×3, unwired
  programme_continuation); hard-coded path audit.
- store/evidence/t16/16D_latency_proof.md: live proof cycle 1 —
  c707a81 deploy ran event-driven end-to-end in production: watchdog
  re-arms on live CI, deadline expired exactly once at 900.92s as
  failure detection, final authority read success, promoted 16:18:17Z.
  Sleep-30 polling floor gone. Event-before-waiter ordering documented
  honestly (cycle 2 in flight proves the wake path).
2026-09-15 17:34:00 +01:00
Nick 2833175329 feat(scaffolding): future-Substrate seam — outbound projection + inbound command interface + mock client held-outs (T16 16I/16J/16K)
- construction_projection.py: stable SCHEMA_VERSION=1 public projection
  (all §16I mandate fields: phase/stage/waiting_state/correlation/
  canaries/verification/checkpoint/evidence/failure_class/completion/
  candidate_next_action). A projection, NOT a second authority — derived
  read-only from the canonical scaffold store + live wait bridge; degrades
  to BRIDGE_UNREACHABLE when the bridge is absent, never lies.
- construction_commands.py: bounded validated inbound commands
  (REQUEST_STATE/START/RESUME/ACKNOWLEDGE_BLOCKER/PROVIDE_APPROVAL/
  PROVIDE_CONTEXT — PAUSE/CANCEL deliberately unexposed: no paused phase
  in the lifecycle; abandonment needs user provenance). Every command
  carries identity/correlation, validates against LEGAL_TRANSITIONS,
  cannot mutate storage (returns validated intents; the Construction
  Interface executes), idempotent by command_id. Substrate asks;
  Scaffolding decides.
- tests/test_substrate_seam.py: 9 held-outs — MockSubstrateClient knows
  NOTHING of current.json/internal arrays; state via public seam only;
  event observation + correlation; valid RESUME accepted; invalid START
  refused; direct-store semantics inexpressible; restart survives on
  identities alone; Scaffolding fully usable without any client;
  idempotent replay; unknown command/construction refused; schema
  validated.

Suite 1878 green; ratchets PASS; Henxels rc=0.
2026-09-15 17:34:00 +01:00
conductor 4a06d9f11a chore(release): auto-bump version to 0.66.64 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 18s
CI Pipeline / unit-tests (push) Successful in 2m36s
CI Pipeline / lint (push) Successful in 36s
CI Pipeline / canary (push) Successful in 26s
CI Pipeline / best-practices (push) Successful in 20s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 11s
deploy / deploy (push) Successful in 15m14s
Promotion record: (unknown)
2026-09-15 17:02:38 +01:00
Nick c707a81933 fix(deploy): shared bridge data root for cross-user event-driven CI wait (T16 16C live defect)
CI Pipeline / best-practices (push) Successful in 19s
CI Pipeline / documentation (push) Successful in 8s
CI Pipeline / canary (push) Successful in 34s
CI Pipeline / lint (push) Successful in 46s
Auto-version and Release / release (push) Successful in 48s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / unit-tests (push) Successful in 2m46s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 5s
deploy / deploy (push) Successful in 15m16s
The deploy step runs as the runner user while the W/R daemon runs as
nick — defaulting wr_root() to the invoking user's home made FIFO
creation fail (PermissionError) and the deploy failed fast+truthfully on
both 0.66.63 pushes (runs 7953/7958). No false green; deployment
correctly stalled at 5bd8cca.

- shared data root /mnt/workdrive/wr-bridge (waits/events/resumes/
  leases migrated; daemon restarted on it; 46 waits intact)
- deploy.yml: RUNE_WR_ROOT/RUNE_WR_CODE exported + READINESS-BEFORE-
  ACTION write probe (§16U: clear bridge-misconfigured error instead of
  a FIFO-creation stack trace)
- event_wait: FIFOs and durable wait records relaxed to 0o666 — the
  bridge is cross-user by construction now

Suite 1869 green; ratchets PASS.
2026-09-15 17:02:04 +01:00
Nick 37deb6085c feat(provider): provider ownership boundary + PROVIDER_UNAVAILABLE recovery class (T16 16F/16G)
- provider_context.py: require_model() resolves the model from the
  ambient provider context; missing context fails LOUDLY
  (PROVIDER_UNRESOLVED, exit 3) — no silent default provider anywhere.
- t14/t15 builder drivers, evidence_probe, research executor/relay:
  hardcoded 'Verboo Code/glm-5.3-flash' defaults REMOVED (6+ sites);
  provider/model selection now belongs exclusively to the provider
  layer via the ambient context. No caller anywhere set MIMO_MODEL —
  every run silently used the code-chosen provider until now.
- failure_recovery: PROVIDER_UNAVAILABLE class (context-forced; real
  status/API code preserved verbatim in evidence; policy
  block_branch_preserve_error, 0 retries, no fallback — 16G-D).
- tests/test_provider_ownership.py: structural guard (provider names
  in Scaffolding code lines fail the suite; module list live-discovered)
  + 16G held-outs (verbatim codes, zero fallback attempts, resumable
  after block, provider preference never mutates scaffold truth).
- 16U root-cause completion: pyright skew 117⇄124 driven by ambient
  VIRTUAL_ENV/PATH leakage into pyright's interpreter resolution —
  the ratchet now neutralizes ambient-venv signals (PYTHONNOUSERSITE
  was necessary but not sufficient; reproduced and closed).
- spine reconciliation: T15 modules (event_wait, stage_guards,
  failure_recovery, programme_continuation) were missing belongs_to
  relationships in the tracked store while the generated manifest had
  them — registered with SCAFFOLD-TRANCHE-015 evidence; provider_context
  registered under capability; manifest regenerated via export-umbrellas.

Suite 1869 green; ratchets PASS; Henxels rc=0.
2026-09-15 17:02:04 +01:00
conductor affe852ff7 chore(release): auto-bump version to 0.66.63 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 22s
deploy / deploy (push) Failing after 7s
CI Pipeline / unit-tests (push) Successful in 2m57s
CI Pipeline / lint (push) Successful in 48s
CI Pipeline / canary (push) Successful in 30s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
Promotion record: (unknown)
2026-09-15 16:27:30 +01:00
Nick e1a71efeb9 feat(ci): event-driven CI qualification wait over the existing W/R bridge (T16 16C/16D/16E)
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / canary (push) Successful in 37s
CI Pipeline / lint (push) Successful in 46s
Auto-version and Release / release (push) Successful in 53s
deploy / deploy (push) Failing after 16s
CI Pipeline / unit-tests (push) Successful in 2m50s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / type-check (push) Successful in 26s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
- deploy.yml sleep-30 polling loop REPLACED: registers a gitea-identity
  PROCESS_EVENT wait, yields in select() on the FIFO, resumes on the
  ci.yml notify-event-bridge WAKE, then re-reads the canonical Gitea
  verdict (deploy_qualify.py). Event payload is never completion truth.
- ci.yml gains notify-event-bridge (always(), best-effort): a red CI
  wakes the waiter as loudly as a green one; bridge unreachable ->
  watchdog recovery covers the lost wake.
- event_wait.py: register_process_wait gains provider/event_type/sha
  identity (16E: ONE bridge primitive; builder + gitea are adapters);
  open_fifo_reader + caller-owned fifo_fd close the 16Y#10 race (event
  before waiter in select()); await_resume_event returns eof instead of
  busy-looping on a caller-owned fd.
- ci_event_wait.py watchdog: failure DETECTOR only — liveness-gated
  re-arm, one-shot authority recovery on lost events, truthful failure
  when no verdict exists; timestamps emitted for the 16D latency proof.
- tests: 8 held-outs on the REAL daemon (shared wr_daemon fixture in
  conftest — no duplicated helpers). Daemon-side: /process-event passes
  through provider/event_type/sha (defaults preserve T15); lease_path
  sanitizes slash-bearing session ids (resume delivery was impossible
  for them — found live, fixed in daemon snapshot 79434e0).

Suite 1861 green; ratchets PASS; Henxels rc=0.
2026-09-15 16:26:54 +01:00
conductor 5bd8cca941 chore(release): auto-bump version to 0.66.62 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 18s
CI Pipeline / unit-tests (push) Successful in 2m28s
CI Pipeline / lint (push) Successful in 39s
CI Pipeline / canary (push) Successful in 30s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 1m24s
Promotion record: (unknown)
2026-09-15 14:15:22 +01:00
Nick fd5f31bba2 fix(scaffolding): §15D tests use ephemeral ports, not fixed 8793
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / lint (push) Successful in 46s
Auto-version and Release / release (push) Successful in 55s
CI Pipeline / canary (push) Successful in 36s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Successful in 2m33s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 4m10s
CI failure on the 0.66.61 bump run (job 18584): the latency test got
watchdog/alive:False because its daemon could not bind the FIXED port
8793 — a stale TIME_WAIT socket from an earlier test in the same file
held it (verified locally: 8793 bindable only with SO_REUSEADDR while
no listener exists). The notifier's event POSTs went nowhere, so the
watchdog correctly reported PROCESS_LOST: the harness was at fault,
not the bridge. Daemon fixture now binds port 0 (ephemeral) and reads
back the assigned port. 3x consecutive green locally.
2026-09-15 14:14:44 +01:00
conductor 8a2a4f7b71 chore(release): auto-bump version to 0.66.61 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 19s
CI Pipeline / unit-tests (push) Failing after 4m18s
CI Pipeline / lint (push) Successful in 35s
CI Pipeline / canary (push) Successful in 28s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / required-checks (push) Failing after 1s
deploy / deploy (push) Failing after 3m28s
Promotion record: (unknown)
2026-09-15 13:42:21 +01:00
Nick b2d6802dba fix(scaffolding): §15K test no longer shells out to git worktree on CI
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / canary (push) Successful in 38s
CI Pipeline / lint (push) Successful in 45s
Auto-version and Release / release (push) Failing after 1m11s
CI Pipeline / unit-tests (push) Successful in 2m34s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / required-checks (push) Successful in 0s
deploy / deploy (push) Successful in 49s
test_15k_build_manager_has_no_parallel_construction_state invoked
BuildManager.start_build for real, which runs 'git worktree add' against
the HARDCODED production path /mnt/workdrive/rune — a real VCS mutation
inside a unit test. Locally it polluted ~/.rune/work/builds; on the CI
runner it fails outright (dubious-ownership: runner checkout is a
different user). The test now redirects BUILDS_DIR/WORKTREE_DIR into tmp
and stubs create_worktree — the property under test (no canonical
construction state from the /build vehicle) is orthogonal to the git op.
This was the CI unit-tests failure on 193d819 (1 failed, 1852 passed).
2026-09-15 13:41:43 +01:00
conductor 3ebbf9a9b1 chore(release): auto-bump version to 0.66.60 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 32s
deploy / deploy (push) Failing after 1m10s
CI Pipeline / unit-tests (push) Failing after 5m19s
CI Pipeline / lint (push) Successful in 39s
CI Pipeline / canary (push) Successful in 35s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Failing after 1s
Promotion record: (unknown)
2026-09-15 13:29:02 +01:00
Nick 193d81961d fix(scaffolding): event_wait importlib-clean loading, zero pyright warnings
CI Pipeline / documentation (push) Successful in 6s
Auto-version and Release / release (push) Successful in 34s
CI Pipeline / canary (push) Successful in 36s
CI Pipeline / lint (push) Successful in 40s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / unit-tests (push) Failing after 2m36s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / required-checks (push) Failing after 1s
deploy / deploy (push) Failing after 5m41s
_load_waitstore now resolves the bridge's own waitstore.py via
importlib.util.spec_from_file_location (no dynamic bare import, no
sys.path mutation) with per-root caching. Verified: 0 errors/0 warnings
in event_wait.py; per-file stash comparison shows my 8 touched files add
zero warnings over baseline (rune_operator 5/5, rune_tui 307/307
identical). The 443→445 CI warning delta is pre-existing environment
skew in untouched files, not T15 regression.
2026-09-15 13:25:39 +01:00
138 changed files with 22021 additions and 1205 deletions

No files matched your search

BIN
View File
Binary file not shown.
+22
View File
@@ -322,3 +322,25 @@ jobs:
echo "::error::Required CI checks not green — refusing to publish required-checks success"
echo "lint=${{ needs.lint.result }} unit-tests=${{ needs['unit-tests'].result }} canary=${{ needs.canary.result }} best-practices=${{ needs['best-practices'].result }} documentation=${{ needs.documentation.result }} type-check=${{ needs['type-check'].result }} gitea-offline=${{ needs['gitea-offline'].result }}"
exit 1
notify-event-bridge:
# T16 §16C: post the CI completion WAKE to the local event bridge so
# event-driven waiters (deploy.yml → ci_event_wait.py) resume on the
# event instead of sleep-polling. ALWAYS runs (success or failure —
# a red CI must wake the waiter just as loudly as a green one). The
# wake is best-effort and never decides the verdict: the waiter
# re-reads the canonical Gitea state via deploy_qualify.py.
name: CI Pipeline / notify-event-bridge
runs-on: rune-ci-desktop
needs: [required-checks]
if: always()
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
fetch-depth: 1
- name: Wake event bridge (wake only — never completion truth)
env:
CI_SHA: ${{ gitea.sha }}
CI_RESULT: ${{ needs.required-checks.result }}
RUNE_EVENT_BRIDGE_URL: ${{ vars.RUNE_EVENT_BRIDGE_URL || 'http://127.0.0.1:8791' }}
run: python3 scripts/notify_event_bridge.py
+25 -15
View File
@@ -19,28 +19,38 @@ jobs:
with:
fetch-depth: 0
- name: Wait for CI qualification on this exact SHA
- name: Wait for CI qualification on this exact SHA (event-driven, T16 §16C)
# T16 §16C/§16D: the old sleep-30 → query → repeat loop is gone.
# The waiter registers a correlation on the W/R event bridge and
# yields in select() until ci.yml's notify-event-bridge job posts
# the completion WAKE; the verdict is then re-read from canonical
# Gitea (deploy_qualify.py). The 900s deadline is a FAILURE
# DETECTOR (lost-event recovery), never the scheduler.
env:
GITEA_SERVER: ${{ gitea.server_url }}
GITEA_REPO: ${{ gitea.repository }}
GITEA_TOKEN: ${{ secrets.ROBOT_TOKEN }}
# Shared bridge data root: the deploy step runs as the runner
# user while the daemon runs as nick — the rendezvous state
# (waits/events/resumes/leases) must be shared. §16C live
# finding: defaulting to the invoking user's home made the
# controller unable to create its FIFO (PermissionError) and
# the deploy failed fast+truthfully on both 0.66.63 pushes.
RUNE_WR_ROOT: /mnt/workdrive/wr-bridge
RUNE_WR_CODE: /home/nick/.local/share/mimo-wait-resume
run: |
DEPLOY_SHA=$(git rev-parse HEAD)
export DEPLOY_SHA
echo "Qualifying $DEPLOY_SHA"
DEADLINE=$((SECONDS + 900))
while [ $SECONDS -lt $DEADLINE ]; do
STATUS=$(python3 scripts/deploy_qualify.py)
echo "required-checks: $STATUS"
if [ "$STATUS" = "success" ]; then exit 0; fi
if [ "$STATUS" = "failure" ]; then
echo "::error::CI failed on $DEPLOY_SHA — deployment refused"
exit 1
fi
sleep 30
done
echo "::error::CI qualification timeout on $DEPLOY_SHA — deployment refused"
exit 1
echo "Qualifying $DEPLOY_SHA (event-driven)"
# READINESS BEFORE ACTION (§16U): fail with a clear message if
# the bridge rendezvous is not writable in THIS user's context,
# instead of a stack trace at FIFO creation time.
if ! touch /mnt/workdrive/wr-bridge/resumes/.write-probe 2>/dev/null; then
echo "::error::RUNE_WR_ROOT not writable by $(id -un) — bridge data root misconfigured"
exit 1
fi
rm -f /mnt/workdrive/wr-bridge/resumes/.write-probe
python3 scripts/ci_event_wait.py --sha "$DEPLOY_SHA" --deadline 900
- name: Stage, verify, promote (monotonic)
env:
+4 -1
View File
@@ -22,9 +22,12 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Run all canaries
# T16 §16Q reconciliation: the glob looped shared/canary_*.py —
# real canaries live in src/rune/ (canary_native.MODULES is the
# registry authority). The old job silently no-opped.
run: |
FAILED=0
for canary in shared/canary_*.py; do
for canary in src/rune/canary_*.py; do
if [ -f "$canary" ]; then
echo "Running $canary..."
python3 "$canary" || FAILED=$((FAILED + 1))
+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.66.59**
**Current release: v0.71.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.**
+19 -2
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-14T06:08:20Z",
"generated_at": "2026-09-16T03:45:21Z",
"umbrellas": {
"capability": {
"description": "Tool selection and execution authority",
@@ -12,11 +12,13 @@
"capability_broker",
"connection_pool",
"evidence_probe",
"forge",
"forge_client",
"intent_continuity",
"local_tool_runtime",
"message_queue",
"openai_compat_transport",
"provider_context",
"recall_uplift",
"route_risk_flags",
"tool_executor",
@@ -215,30 +217,45 @@
"attention_capsule",
"auto_context",
"auto_tasks",
"behaviour_ledger",
"build_basis",
"capability_fit",
"capability_ledger",
"cognitive_budgeter",
"complexity_scorer",
"construction_activation",
"construction_commands",
"construction_decision",
"construction_depth",
"construction_intent",
"construction_interface",
"construction_mode",
"construction_pattern",
"construction_patterns_builtin",
"construction_projection",
"context_broker",
"context_pressure",
"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",
"witness"
],
"depends_on": [
@@ -251,4 +268,4 @@
]
}
}
}
}
+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).
+73
View File
@@ -0,0 +1,73 @@
# Rune Structural Map
_Maintained structural projection — the fast re-entry surface (T16 §16N).
Companion to the Spine store (`store/spine/current.json`, structural
authority) and `store/evidence/t16/16M_inventory.md` (full classification
record). Update this map when authority or canonical routes change; do
not copy the repo here._
## What is Rune?
A self-hosting coding agent: TUI client over an operator core, with a
structural self-model (Spine), a construction authority (Scaffolding),
an event-driven wait/resume bridge (W/R), a research pipeline, and a
fail-closed release/deployment pipeline that builds, tests, releases,
and deploys Rune itself to `/mnt/workdrive/rune-code/current`.
## Where things happen (canonical routes only)
| Concern | Authority | State |
|---|---|---|
| Structural self-model | `src/rune/spine.py` | `store/spine/current.json` |
| 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` — 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>/` |
| Task completion truth | `verification_layer.py` + `orchestration/conversation_state.py` | durable event store + checkpoints |
| Routing (task class) | `src/rune/adaptive_router.py` | `routes/route.jsonl` |
| Canary registry | `src/rune/canary_native.MODULES` | observed by Spine |
| Structural health | `henxels_checks.py` + `henxels.yaml` | repo root |
## Known non-canonical / duplicate routes (do not extend; reconcile via 16Q)
- `build_mode.BuildManager` (legacy /build) — superseded by builder_bridge
+ event-driven sessions; still wired.
- `scripts/t14_builder_session.py` — superseded by t15 event-driven driver.
- Provider selection entry points ×5 (`model_routing.rank`,
`model_connect route`, `model_registry get`, `provider_registry`,
`work_manager`) — operator is the de-facto final decider.
- Task-ish state ×4 (task-tool DB, `task_persist` persisted.json,
`memory_store.working`, orchestration event store).
- Maintenance ledgers ×3 (scaffold maintenance_item, tui_backlog, drift_ledger).
## Unfinished / blocked / deprecated
- 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
`~/.local/share/mimo-wait-resume` with its own git snapshot —
packaging debt, no CI coverage of the deployed copy.
- Substrate: NOT BUILT. Scaffolding exposes the seam
(`construction_projection.py`, `construction_commands.py`); mock
client proves compatibility (`tests/test_substrate_seam.py`).
## Fresh-session quick answers
- Is a claim true? → Spine store + evidence_refs, never commit messages.
- Is construction allowed? → scaffold contract phase + LEGAL_TRANSITIONS.
- How does a long wait work? → register on the W/R bridge, yield on the
FIFO event; watchdog is failure detection, never scheduling; the event
is a WAKE — authority is re-read after resume.
- Who picks the provider? → the user layer, via ambient context;
construction never does (`test_provider_ownership.py` enforces).
- What runs where? → CI on `rune-ci-desktop` runner; deploy stages to
`/mnt/workdrive/rune-code/<sha>`; data home pinned at
`/mnt/workdrive/rune` (RUNE_HOME); implementation worktree
`/mnt/workdrive/issue161-harness`.
+2 -2
View File
@@ -1,4 +1,4 @@
{
"BLE001": 354,
"S110": 80
"BLE001": 350,
"S110": 78
}
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.66.59"
version = "0.71.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"
]
}
+2 -2
View File
@@ -70,7 +70,7 @@ 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
+262
View File
@@ -0,0 +1,262 @@
#!/usr/bin/env python3
"""ci_event_wait.py — T16 §16C/§16D: event-driven CI qualification wait.
Replaces the deploy.yml sleep-30 → query → repeat polling loop with the
EXISTING W/R event bridge (src/rune/event_wait.py — the SAME primitive the
builder adapter uses; §16E: one bridge, adapters differ only in identity
and authority probe).
Flow (mandate §16C):
push / CI workflow starts
↓
register correlation (provider="gitea", event_type="ci_completed",
run_ref="ci-<sha>", sha=<sha>)
↓
WAITING_FOR_EVENT (await_resume_event: select() on FIFO, no floor)
↓
ci.yml final job POSTs /process-event (the WAKE)
↓
daemon matches → resume_cmd fires → FIFO token → controller resumes
↓
canonical Gitea state INDEPENDENTLY re-read (deploy_qualify.py verdict)
↓
verdict
The event is a WAKE, never truth. The final verdict always comes from
deploy_qualify.py (required-checks context + exact-SHA ci.yml run, both
legs). Watchdog deadline is a FAILURE DETECTOR, never the scheduler:
- event arrives → authority re-read; pending → bounded re-arm until the
overall deadline (ack-window semantics, same as the daemon's wake sweep).
- event lost/suppressed → watchdog expires → ONE-SHOT authority query:
terminal verdict → recovered truthfully (no invented wait);
CI still running (non-terminal Gitea runs for the SHA) → re-arm;
nothing alive and no verdict → truthful failure (deployment refused).
Exit codes: 0 = qualified (success), 1 = refused (failure/timeout).
Output: one JSON line with the §16D latency-proof timestamps.
"""
from __future__ import annotations
import json
import os
import subprocess
import sys
import time
from pathlib import Path
_SCRIPT_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(_SCRIPT_DIR.parent / "src"))
from rune import event_wait as ew # noqa: E402
REPO_ROOT = _SCRIPT_DIR.parent
QUALIFY_SCRIPT = _SCRIPT_DIR / "deploy_qualify.py"
def authority_verdict() -> str:
"""ONE-SHOT canonical Gitea verdict via deploy_qualify.py — the only
completion truth. Never parses the event payload."""
proc = subprocess.run(
[sys.executable, str(QUALIFY_SCRIPT)],
capture_output=True, text=True, timeout=120,
env=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:
"""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
bounded schedule, not a polling loop — 3 reads max, then back to the
event wait / watchdog."""
verdict = "pending"
for attempt in range(max_tries):
verdict = authority_verdict()
if verdict != "pending":
return verdict
if attempt < max_tries - 1:
time.sleep(gap_secs)
return verdict
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)
def run(*, sha: str, deadline_secs: int,
watchdog_secs: int = 300,
daemon_url: str = ew.DEFAULT_DAEMON_URL) -> int:
op = f"ci-{sha}"
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)
# Event replay (T16 §16C live finding): on a serialized runner the
# completion event usually arrives BEFORE the deploy job 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}",
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))
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()
mode = ("event_replayed" if verdict != "pending"
else "authority_unresolved")
verdict_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
out = {
"sha": sha,
"registered_at": registered_at,
"event_received_at": event_at,
"controller_resumed_at": resumed_at,
"verdict_at": verdict_at,
"mode": mode,
"rearm_reason": "completion event replayed from durable log "
"(arrived before registration)",
"verdict": verdict,
"wall_secs": round(time.time() - t0, 3),
}
print(json.dumps(out))
return 0 if verdict == "success" else 1
rec = ew.register_process_wait(
op, project="rune",
objective=f"event-driven CI qualification for {sha}",
watched_for="Gitea CI completion event (wake; verdict re-read "
"from canonical Gitea)",
provider="gitea", event_type="ci_completed", sha=sha,
watchdog_secs=min(watchdog_secs, deadline_secs))
registered_at = rec["created_at"]
deadline = time.monotonic() + deadline_secs
verdict = "pending"
mode = "event"
event_at = ""
resumed_at = ""
rearm_reason = ""
try:
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":
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()
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")
break
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:
mode = "watchdog_authority_recovered"
break
finally:
os.close(fifo_fd)
if verdict == "pending":
if mode == "event":
mode = "deadline_pending"
verdict = authority_verdict() # final one-shot truth
verdict_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
out = {
"sha": sha,
"registered_at": registered_at,
"event_received_at": event_at,
"controller_resumed_at": resumed_at,
"verdict_at": verdict_at,
"mode": mode,
"rearm_reason": rearm_reason,
"verdict": verdict,
"wall_secs": round(time.time() - t0, 3),
}
print(json.dumps(out))
return 0 if verdict == "success" else 1
def main() -> int:
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])
if not sha:
print(json.dumps({"error": "no sha (DEPLOY_SHA or --sha required)"}))
return 1
return run(sha=sha, deadline_secs=deadline)
if __name__ == "__main__":
sys.exit(main())
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""notify_event_bridge.py — T16 §16C: ci.yml's final job posts the CI
completion WAKE to the local W/R event bridge (daemon /process-event).
The event is a WAKE, never completion truth: the waiting controller
(ci_event_wait.py) re-reads the canonical Gitea verdict via
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.
"""
from __future__ import annotations
import json
import os
import sys
import time
import urllib.request
BRIDGE_URL = os.environ.get("RUNE_EVENT_BRIDGE_URL",
"http://127.0.0.1:8791")
def main() -> int:
sha = os.environ.get("CI_SHA", "")
result = os.environ.get("CI_RESULT", "") # needs.<job>.result
if not sha:
print("notify-event-bridge: no CI_SHA — nothing to wake", flush=True)
return 0
payload = {
"operation_id": f"ci-{sha}",
"provider": "gitea",
"event_type": "ci_completed",
"sha": sha,
# exit_code is informational only; the wake is not the verdict
"exit_code": 0 if result == "success" else 1,
"completed_at": time.time(),
}
for attempt in range(2):
try:
req = urllib.request.Request(
BRIDGE_URL + "/process-event",
data=json.dumps(payload).encode(),
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=5) as r:
resp = json.loads(r.read())
print(f"notify-event-bridge: woke bridge "
f"(matched={resp.get('matched')})", flush=True)
return 0
except Exception as exc: # noqa: BLE001 — wake is best-effort by design; failure is logged and the waiter's watchdog recovery covers a lost wake (§16D contract)
if attempt == 0:
time.sleep(2)
else:
print(f"notify-event-bridge: bridge unreachable ({exc}); "
f"watchdog recovery covers a lost wake", flush=True)
return 0
if __name__ == "__main__":
sys.exit(main())
+2 -1
View File
@@ -181,7 +181,8 @@ def run_stage_with_relay(tree, out_dir: str) -> dict:
err_path = os.path.join(out_dir, f"stage_{stage}.mimo.err.log")
mimo_bin = os.environ.get("MIMO_BIN", "/var/lib/gitea-desktop-runner/bin/mimo")
mimo_model = os.environ.get("MIMO_MODEL", "Verboo Code/glm-5.3-flash")
from rune.provider_context import require_model
mimo_model = require_model() # T16 §16F: provider layer owns selection
mimo_agent = os.environ.get("MIMO_AGENT", "build")
cmd = [
+12 -6
View File
@@ -793,7 +793,8 @@ class StageExecutor:
prompt = chr(10).join(pp)
mimo_bin = os.environ.get("MIMO_BIN", "/var/lib/gitea-desktop-runner/bin/mimo")
mimo_model = os.environ.get("MIMO_MODEL", "Verboo Code/glm-5.3-flash")
from rune.provider_context import require_model
mimo_model = require_model() # T16 §16F: provider layer owns selection
mimo_agent = os.environ.get("MIMO_AGENT", "build")
events_path = os.path.join(self.artifact_dir, "stage_OUTWARD.events.jsonl")
err_path = os.path.join(self.artifact_dir, "stage_OUTWARD.mimo.err.log")
@@ -933,7 +934,8 @@ class StageExecutor:
prompt = chr(10).join(pp)
mimo_bin = os.environ.get("MIMO_BIN", "/var/lib/gitea-desktop-runner/bin/mimo")
mimo_model = os.environ.get("MIMO_MODEL", "Verboo Code/glm-5.3-flash")
from rune.provider_context import require_model
mimo_model = require_model() # T16 §16F: provider layer owns selection
mimo_agent = os.environ.get("MIMO_AGENT", "build")
events_path = os.path.join(self.artifact_dir, "stage_APPLICABILITY.events.jsonl")
err_path = os.path.join(self.artifact_dir, "stage_APPLICABILITY.mimo.err.log")
@@ -1137,7 +1139,8 @@ class StageExecutor:
prompt = chr(10).join(pp)
mimo_bin = os.environ.get("MIMO_BIN", "/var/lib/gitea-desktop-runner/bin/mimo")
mimo_model = os.environ.get("MIMO_MODEL", "Verboo Code/glm-5.3-flash")
from rune.provider_context import require_model
mimo_model = require_model() # T16 §16F: provider layer owns selection
mimo_agent = os.environ.get("MIMO_AGENT", "build")
events_path = os.path.join(self.artifact_dir, "stage_ADVERSARIAL.events.jsonl")
err_path = os.path.join(self.artifact_dir, "stage_ADVERSARIAL.mimo.err.log")
@@ -1345,7 +1348,8 @@ class StageExecutor:
prompt = chr(10).join(pp).replace("{n}", str(self.issue))
mimo_bin = os.environ.get("MIMO_BIN", "/var/lib/gitea-desktop-runner/bin/mimo")
mimo_model = os.environ.get("MIMO_MODEL", "Verboo Code/glm-5.3-flash")
from rune.provider_context import require_model
mimo_model = require_model() # T16 §16F: provider layer owns selection
mimo_agent = os.environ.get("MIMO_AGENT", "build")
events_path = os.path.join(self.artifact_dir, "stage_INNOVATION.events.jsonl")
err_path = os.path.join(self.artifact_dir, "stage_INNOVATION.mimo.err.log")
@@ -1534,7 +1538,8 @@ class StageExecutor:
prompt = chr(10).join(pp)
mimo_bin = os.environ.get("MIMO_BIN", "/var/lib/gitea-desktop-runner/bin/mimo")
mimo_model = os.environ.get("MIMO_MODEL", "Verboo Code/glm-5.3-flash")
from rune.provider_context import require_model
mimo_model = require_model() # T16 §16F: provider layer owns selection
mimo_agent = os.environ.get("MIMO_AGENT", "build")
events_path = os.path.join(self.artifact_dir, "stage_SYNTHESIS.events.jsonl")
err_path = os.path.join(self.artifact_dir, "stage_SYNTHESIS.mimo.err.log")
@@ -1671,7 +1676,8 @@ class StageExecutor:
prompt = chr(10).join(pp).replace("{n}", str(self.issue))
mimo_bin = os.environ.get("MIMO_BIN", "/var/lib/gitea-desktop-runner/bin/mimo")
mimo_model = os.environ.get("MIMO_MODEL", "Verboo Code/glm-5.3-flash")
from rune.provider_context import require_model
mimo_model = require_model() # T16 §16F: provider layer owns selection
mimo_agent = os.environ.get("MIMO_AGENT", "build")
events_path = os.path.join(self.artifact_dir, "stage_QUALIFY.events.jsonl")
err_path = os.path.join(self.artifact_dir, "stage_QUALIFY.mimo.err.log")
+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:
+12 -2
View File
@@ -44,8 +44,18 @@ USER_REQUEST = (
"list and complete practice items."
)
EXPECTED_SCOPE = ["scheduler.js", "scheduler.test.js", "package.json"]
BUILDER_MODEL = os.environ.get("T14_BUILDER_MODEL",
"Verboo Code/glm-5.3-flash") # PL1 provider policy default
# T16 §16F: NO silent default provider. The surrounding provider
# layer sets T14_BUILDER_MODEL (falling back to the ambient MIMO_MODEL
# context); construction never names a provider.
import sys as _sys
_sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parent.parent / "src"))
from rune.provider_context import ProviderUnresolved, require_model # noqa: E402
try:
BUILDER_MODEL = os.environ.get("T14_BUILDER_MODEL", "").strip() \
or require_model() # ambient provider context (MIMO_MODEL)
except ProviderUnresolved as _exc:
_sys.stderr.write(str(_exc) + "\n")
raise SystemExit(3) # 3 = PROVIDER_UNRESOLVED (no silent fallback)
BUILDER_CMD = "mimo"
+11 -1
View File
@@ -43,8 +43,18 @@ from rune import event_wait as ew # noqa: E402
from rune import scaffold as sc # noqa: E402
from rune import structural_store as ss # noqa: E402
from rune.event_wait import DEFAULT_DAEMON_URL # noqa: E402
from rune.provider_context import ProviderUnresolved, require_model # noqa: E402
BUILDER_MODEL = os.environ.get("T15_BUILDER_MODEL", "Verboo Code/glm-5.3-flash")
# T16 §16F: NO silent default provider — resolve from the ambient provider
# context (T15_BUILDER_MODEL, falling back to MIMO_MODEL); loud failure
# with exit code 3 when the provider layer has not selected one.
try:
BUILDER_MODEL = os.environ.get("T15_BUILDER_MODEL", "").strip() \
or require_model()
except ProviderUnresolved as _exc:
import sys as _sys
_sys.stderr.write(str(_exc) + "\n")
raise SystemExit(3) # 3 = PROVIDER_UNRESOLVED (no silent fallback)
DAEMON_URL = os.environ.get("T15_DAEMON_URL", DEFAULT_DAEMON_URL)
+15 -1
View File
@@ -31,7 +31,21 @@ def main() -> int:
# PYTHONNOUSERSITE=1 pins both contexts to the same effective
# environment (verified: identical 117-error arithmetic). Local
# preflight now predicts CI instead of diverging from it.
env = dict(os.environ, PYTHONNOUSERSITE="1")
# §16U ROOT-CAUSE COMPLETION (T16): PYTHONNOUSERSITE alone is NOT
# sufficient when a DIFFERENT venv is active (e.g. an ad-hoc CI venv):
# the pyright wrapper resolves the ambient VIRTUAL_ENV/PATH
# interpreter and analyzes against ITS site-packages — reproduced
# live (117 vs 124 on the same tree, driven by venv-activation state
# alone). The CI runner resolves the AMBIENT python (no venv is
# activated in the type-check job), so the ratchet neutralizes every
# ambient-venv signal before invoking pyright — analysis context is
# now identical to CI regardless of the calling shell.
env = {k: v for k, v in os.environ.items()
if k not in ("VIRTUAL_ENV",)}
env["PYTHONNOUSERSITE"] = "1"
env["PATH"] = os.pathsep.join(
p for p in env.get("PATH", "").split(os.pathsep)
if "ci-venv" not in p and ".type-venv" not in p and "venv/bin" not in p)
result = subprocess.run(
cmd + [str(ROOT / "src" / "rune"), "--outputjson"],
capture_output=True,
+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.59"
__version__ = "0.71.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())}
+287
View File
@@ -0,0 +1,287 @@
"""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 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.59"' in rune_version,
'__version__ = "0.71.0"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
+355
View File
@@ -0,0 +1,355 @@
"""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}")
hist = {"at": _now(), "event": "material_change",
"note": note[:200], "generation": generation,
"prior_maturity": rec["maturity"]}
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 []),
})
rec["updated_at"] = _now()
return {"old": old, "new": new, "kind": kind, "reason": reason}
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],
}
+173
View File
@@ -0,0 +1,173 @@
"""construction_commands.py — T16 §16J: bounded validated inbound
command interface for future Substrate integration.
The future Substrate ASKS; Scaffolding DECIDES. Every command:
- carries identity (command_id, correlation_id, construction_id)
- is validated by Scaffolding against the existing lifecycle
(LEGAL_TRANSITIONS — no new lifecycle invented)
- cannot directly mutate storage (commands return validated intents;
the Construction Interface executes them through its own governed
path — the caller never writes the store)
- is idempotent where applicable (repeat delivery of the same
command_id returns the same recorded outcome)
Only commands that fit EXISTING semantics safely are exposed. PAUSE and
CANCEL are intentionally NOT exposed: the lifecycle has no paused phase
and abandonment needs user provenance — a future Substrate asking for
them gets a refusal, not a half-measure.
"""
from __future__ import annotations
import time
import uuid
from rune.scaffold import LEGAL_TRANSITIONS
COMMANDS = (
"REQUEST_STATE", "START", "RESUME", "ACKNOWLEDGE_BLOCKER",
"PROVIDE_APPROVAL", "PROVIDE_CONTEXT",
)
# Command → the interface operation it asks for (the interface still
# decides: validation failures return refused, never raise into storage).
_COMMAND_TO_OP = {
"REQUEST_STATE": "resume", # read-only state projection
"START": "transition", # draft → approved (then building)
"RESUME": "resume", # re-read canonical state
"ACKNOWLEDGE_BLOCKER": "transition", # blocked → building (retry path)
"PROVIDE_APPROVAL": "transition", # draft → approved with provenance
"PROVIDE_CONTEXT": "amend_scope", # context arrives as scope amendment
}
# In-memory idempotency ledger: command_id → recorded outcome. The
# Scaffolding lifecycle is the authority; this ledger only makes repeat
# delivery return the same answer (no double transition).
_outcomes: dict[str, dict] = {}
def command(command_type: str, *, construction_id: str,
correlation_id: str = "", command_id: str = "",
payload: dict | None = None,
scaffold=None) -> dict:
"""Validate an inbound command. Returns a validated intent envelope:
{"accepted": True, "op": <interface op>, "args": {...},
"command_id": ..., "idempotent_replay": bool}
or {"accepted": False, "reason": ...}. The caller executes the intent
through the Construction Interface — it never touches storage."""
payload = payload or {}
command_id = command_id or f"cmd-{uuid.uuid4().hex[:8]}"
if command_id in _outcomes:
prior = dict(_outcomes[command_id])
prior["idempotent_replay"] = True
return prior
if command_type not in COMMANDS:
return _refuse(command_id, construction_id, correlation_id,
f"unknown command {command_type!r} "
f"(supported: {', '.join(COMMANDS)})")
if not construction_id:
return _refuse(command_id, construction_id, correlation_id,
"construction_id is required")
intent = _validate(command_type, construction_id,
correlation_id, command_id, payload, scaffold)
_outcomes[command_id] = intent
return intent
def _refuse(command_id: str, construction_id: str,
correlation_id: str, reason: str) -> dict:
return {"accepted": False, "reason": reason,
"command_id": command_id, "construction_id": construction_id,
"correlation_id": correlation_id,
"recorded_at": time.strftime("%Y-%m-%dT%H:%M:%SZ",
time.gmtime()),
"idempotent_replay": False}
def _validate(command_type: str, construction_id: str,
correlation_id: str, command_id: str,
payload: dict, scaffold) -> dict:
"""Validate against the CURRENT canonical state — the lifecycle
decides, not the requester. The scaffold (canonical store handle) is
supplied by the serving layer; the client only ever sees the returned
envelope, never the store."""
if scaffold is None:
return _refuse(command_id, construction_id, correlation_id,
"no serving scaffold bound (server-side wiring)")
doc = scaffold.load()
contract = None
for e in doc.get("entities", []):
if e.get("id") == f"contract:{construction_id}":
contract = e
break
if contract is None:
return _refuse(command_id, construction_id, correlation_id,
f"unknown construction {construction_id!r}")
phase = contract.get("phase", "")
if command_type == "REQUEST_STATE":
return _accept(command_id, construction_id, correlation_id,
"resume", {"contract_id": construction_id})
if command_type == "RESUME":
if phase in ("closed", "abandoned"):
return _refuse(command_id, construction_id, correlation_id,
f"construction is {phase} — resume is invalid")
return _accept(command_id, construction_id, correlation_id,
"resume", {"contract_id": construction_id})
if command_type == "START":
if phase != "draft":
return _refuse(command_id, construction_id, correlation_id,
f"START requires draft phase (current: {phase})")
return _accept(command_id, construction_id, correlation_id,
"transition",
{"contract_id": construction_id,
"phase": "approved"})
if command_type == "PROVIDE_APPROVAL":
if phase != "draft":
return _refuse(command_id, construction_id, correlation_id,
f"approval applies to draft (current: {phase})")
if payload.get("approved_by") != "user":
# lifecycle rule: approval provenance must be the user —
# Substrate cannot approve on the user's behalf
return _refuse(command_id, construction_id, correlation_id,
"approval provenance must be user "
"(Substrate cannot approve for the user)")
return _accept(command_id, construction_id, correlation_id,
"transition",
{"contract_id": construction_id,
"phase": "approved",
"approved_by": "user"})
if command_type == "ACKNOWLEDGE_BLOCKER":
if phase not in ("failed", "building", "verifying"):
return _refuse(command_id, construction_id, correlation_id,
f"no blockable phase (current: {phase})")
return _accept(command_id, construction_id, correlation_id,
"transition",
{"contract_id": construction_id,
"phase": "building"})
if command_type == "PROVIDE_CONTEXT":
paths = payload.get("add_paths") or []
if not paths:
return _refuse(command_id, construction_id, correlation_id,
"PROVIDE_CONTEXT requires add_paths")
return _accept(command_id, construction_id, correlation_id,
"amend_scope",
{"contract_id": construction_id,
"add_paths": paths,
"reason": payload.get("reason", "substrate context"),
"authority": "user"
if payload.get("approved_by") == "user" else "substrate"})
return _refuse(command_id, construction_id, correlation_id,
"unreachable validator branch")
def _accept(command_id: str, construction_id: str, correlation_id: str,
op: str, args: dict) -> dict:
return {"accepted": True, "op": op, "args": args,
"command_id": command_id, "construction_id": construction_id,
"correlation_id": correlation_id,
"recorded_at": time.strftime("%Y-%m-%dT%H:%M:%SZ",
time.gmtime()),
"idempotent_replay": False}
+555
View File
@@ -0,0 +1,555 @@
"""construction_intent.py — T17 §17J-§17N: the conversational
interpretation layer of contextual construction.
AUTHORITY BOUNDARY (§17O):
THIS LAYER owns: meaning, referent resolution, target resolution,
action-vs-discussion, conversational authorization, user constraints.
SCAFFOLDING (construction_mode.classify_mode) owns: construction
classification, depth, contracts, scope, lifecycle.
This module NEVER deepens or reclassifies — it resolves what the user
MEANT and whether they AUTHORIZED it, then hands a structured
ConstructionIntent to Scaffolding, which runs the ONE canonical
classifier (classify_mode). No second classifier lives here.
Not a phrase table: explicit demands are recognized by imperative
grammar + construction verbs; questions are recognized by
interrogative structure; referents are resolved against the live
conversation context (pending proposals, recency, topic drift).
Paraphrases resolve because the criteria are structural, not lexical
incantations.
"""
from __future__ import annotations
import re
import time
import uuid
from dataclasses import dataclass, field
# ── conversation context (the layer's memory, NOT Scaffolding state) ────
@dataclass
class Proposal:
"""A pending construction proposal awaiting user authorization.
Rune suggesting a change is NOT authorization to mutate (§17L)."""
proposal_id: str
summary: str
created_turn: int
superseded: bool = False
@dataclass
class ConversationContext:
"""Active conversational/project context for referent resolution.
Recency-aware, proposal-aware, stale-context-resistant (§17M)."""
turn: int = 0
recent_turns: list = field(default_factory=list) # [{role, text}]
pending_proposals: list = field(default_factory=list) # [Proposal]
active_project: str | None = None
active_task_id: str | None = None
def next_turn(self) -> int:
self.turn += 1
return self.turn
def record_turn(self, role: str, text: str) -> None:
self.recent_turns.append({"role": role, "text": text,
"turn": self.turn})
self.turn += 1 # each recorded exchange advances the context
def add_proposal(self, summary: str) -> Proposal:
p = Proposal(proposal_id=f"prop-{uuid.uuid4().hex[:8]}",
summary=summary,
created_turn=self.turn)
self.pending_proposals.append(p)
return p
def supersede_proposal(self, proposal_id: str) -> None:
for p in self.pending_proposals:
if p.proposal_id == proposal_id:
p.superseded = True
def live_proposals(self) -> list:
return [p for p in self.pending_proposals if not p.superseded]
# ── the structured intent (§17N) ─────────────────────────────────────────
APPROVAL_STATES = ("DISCUSSION", "PROPOSAL", "USER_APPROVED",
"CONSTRUCTION_STARTED")
# approval-state lifecycle legality (§17L): authorization may only be
# granted from PROPOSAL or DISCUSSION (direct demand); CONSTRUCTION_STARTED
# and mutations are Scaffolding's side of the boundary.
LEGAL_APPROVAL_TRANSITIONS = {
"DISCUSSION": {"PROPOSAL", "USER_APPROVED"},
"PROPOSAL": {"USER_APPROVED"},
"USER_APPROVED": {"CONSTRUCTION_STARTED"},
"CONSTRUCTION_STARTED": set(),
}
@dataclass
class ConstructionIntent:
"""Structured meaning-resolution output (§17N). Scaffolding decides
mode/depth from this; this layer never does."""
intent_id: str
objective: str
requested_effect: str
resolved_target: str | None
resolved_referent: str | None
origin_context_id: str | None
action_intent: str # ACTION | DISCUSSION
approval_state: str # DISCUSSION | PROPOSAL | USER_APPROVED
confidence: float
ambiguity: str # NONE | REFERENT | TARGET | SCOPE
consequence_level: str # LOW | MEDIUM | HIGH
relevant_constraints: list = field(default_factory=list)
classification_evidence: list = field(default_factory=list)
probe_questions: list = field(default_factory=list)
def to_dict(self) -> dict:
return {
"intent_id": self.intent_id,
"objective": self.objective,
"requested_effect": self.requested_effect,
"resolved_target": self.resolved_target,
"resolved_referent": self.resolved_referent,
"origin_context_id": self.origin_context_id,
"action_intent": self.action_intent,
"approval_state": self.approval_state,
"confidence": self.confidence,
"ambiguity": self.ambiguity,
"consequence_level": self.consequence_level,
"relevant_constraints": self.relevant_constraints,
"classification_evidence": self.classification_evidence,
"probe_questions": self.probe_questions,
}
# ── structural lexical criteria (grammar classes, not incantations) ─────
# §17M stale-context resistance: a proposal older than this many turns
# is stale unless the user's text explicitly references its content.
STALE_GAP_TURNS = 2
_NEW_ARTICLE = re.compile(r"\b(me a|me an|a|an|another)\b")
_CREATION_VERBS = {"build", "create", "make", "write", "develop"}
_CONSTRUCTION_VERBS = {
"build", "implement", "add", "replace", "fix", "create", "make",
"write", "port", "migrate", "refactor", "integrate", "remove",
"extend", "wire", "ship",
}
_DEMONSTRATIVES = {"that", "this", "it", "those", "these"}
_OPTION_REFERENTS = re.compile(
r"\b(first|second|third|other|latter|former)\s+(option|approach|one)\b")
_QUESTION_MARKERS = re.compile(
r"^\s*(how|what|why|when|where|who|which|should|could|would|can|"
r"is|are|did|do you|do we|do they|does it|does this|does that)\b|"
r"\?\s*$", re.IGNORECASE)
_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' 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|"
r"carry on|proceed|approved|lgtm|ship it)\b[\s.,!]*$", re.IGNORECASE)
_AFFIRMATION_OPENERS = re.compile(
r"^\s*(yes|yeah|yep|yup|ok|okay|sure|agreed|absolutely|indeed|"
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|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
of the construction verb inside it (§17K)."""
if "?" in text and not re.match(r"^\s*(build|implement|add|fix|make|"
r"create|write|replace|remove)\b",
text, re.IGNORECASE):
return True
return bool(_QUESTION_MARKERS.match(text))
def _imperative_verb(text: str) -> str | None:
"""Leading imperative construction verb — the strong/direct demand
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
def resolve_construction_intent(
text: str,
context: ConversationContext,
*,
origin_context_id: str | None = None,
) -> ConstructionIntent:
"""Resolve one user turn into a structured ConstructionIntent.
Criteria (§17J), in precedence order:
1. interrogative structure → DISCUSSION (never mutation); analysis
of an improvement question may RECORD a proposal for later.
2. bare approval ("go ahead") or demonstrative authorization
("build that") → resolve referent against LIVE proposals,
recency-first; multiple equally plausible → clarification;
none live → clarification (stale-context resistant).
3. explicit imperative with resolved target → USER_APPROVED
direct demand — no unnecessary re-asking (§17K).
4. everything else → DISCUSSION; construction language without
resolved target → ambiguity flagged, never guessed (§17M).
"""
context.next_turn()
context.record_turn("user", text)
t = text.strip()
low = t.lower()
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)
# stay discussion; request modals authorize when the target resolves.
# information openers outrank modals: "how would you build X?" asks
# about METHOD; "can you fix that?" requests ACTION
information_opener = bool(re.match(
r"^\s*(how|what|why|when|where|which)\b", low))
request_modal = (not information_opener) and bool(re.search(
r"\b(can you|could you|would you|will you|please)\b", low))
if request_modal:
# modal + a construction verb somewhere in the turn
request_modal = bool(_tokens(low) & _CONSTRUCTION_VERBS)
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}")
# demonstrative referent: "fix that" with no live proposal —
# the antecedent is the defect described in THIS turn
return ConstructionIntent(
intent_id=f"intent-{uuid.uuid4().hex[:8]}",
objective=t, requested_effect=f"fix/act: {t}",
resolved_target=target,
resolved_referent="this-turn-topic",
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)
ev.append("request modal but no resolvable target")
probes.append("where should that change land — which project?")
return _intent(t, "ACTION", "DISCUSSION", 0.5, "TARGET",
ev, probes, context, origin_context_id)
# 1. questions are discussion — even with construction verbs inside
if _is_question(t):
ev.append("interrogative structure → discussion, not authorization")
analysis = bool(_ANALYSIS_MARKERS.search(t))
if analysis:
ev.append("analysis/advice request — a proposal may be recorded "
"for later authorization")
return ConstructionIntent(
intent_id=f"intent-{uuid.uuid4().hex[:8]}",
objective=t, requested_effect="analysis/discussion",
resolved_target=context.active_project,
resolved_referent=None, origin_context_id=origin_context_id,
action_intent="DISCUSSION", approval_state="DISCUSSION",
confidence=0.85, ambiguity="NONE",
consequence_level="LOW", classification_evidence=ev)
# 2. authorization of a prior proposal (bare approval or demonstrative)
bare_approval = bool(_GO_AHEAD.match(t))
imperative = _imperative_verb(t)
dem_tokens = _tokens(low) & _DEMONSTRATIVES
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 \
bool(_OPTION_REFERENTS.search(low))
option_ref = bool(_OPTION_REFERENTS.search(low))
approval_idiom = bool(_APPROVAL_IDIOM.search(low))
affirmation = bool(_AFFIRMATION_OPENERS.match(t))
authorized = (bare_approval or approval_idiom or
(affirmation and (demonstrative or option_ref)) or
(imperative and (demonstrative or option_ref)) or
(option_ref and imperative is not None))
if authorized:
referent = _resolve_referent(low, context)
if referent is None:
# "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)")
probes.append("which one — " + " or ".join(
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)")
ev.append("prior proposal + explicit authorization → USER_APPROVED")
return ConstructionIntent(
intent_id=f"intent-{uuid.uuid4().hex[:8]}",
objective=target_prop.summary,
requested_effect=f"construct: {target_prop.summary}",
resolved_target=context.active_project,
resolved_referent=target_prop.proposal_id,
origin_context_id=origin_context_id,
action_intent="ACTION", approval_state="USER_APPROVED",
confidence=0.9, ambiguity="NONE",
consequence_level=_consequence(t, context),
relevant_constraints=_constraints(context),
classification_evidence=ev)
# 3. explicit imperative construction demand with resolved target
if imperative:
# non-constructible object check is Scaffolding's classifier's
# job (§17O) — this layer records the demand structure only
ev.append(f"imperative construction verb: {imperative} — strong/"
f"direct demand signal (§17K)")
target = context.active_project
if target:
ev.append(f"target resolved from active context: {target}")
return _intent(t, "ACTION", "USER_APPROVED", 0.9, "NONE",
ev, probes, context, origin_context_id,
requested_effect=f"construct: {t}",
consequence_level=_consequence(t, context))
# no active project: a CREATION verb + indefinite article signals
# a NEW standalone artifact — the demand is complete without an
# existing target ("build me a program that...")
if imperative in _CREATION_VERBS and _NEW_ARTICLE.search(low):
ev.append("creation verb + indefinite article → new standalone "
"artifact demand; 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}")
ev.append("imperative demand but no resolvable target — "
"clarification, never guessing")
probes.append("build where — which project/system?")
return _intent(t, "ACTION", "DISCUSSION", 0.5, "TARGET",
ev, probes, context, origin_context_id)
# 4. default: discussion / context statement
ev.append("no authorization structure, no imperative demand → "
"discussion")
return _intent(t, "DISCUSSION", "DISCUSSION", 0.7, "NONE",
ev, probes, context, origin_context_id)
def _resolve_referent(low: str, context: ConversationContext) -> dict | None:
"""Recency-first referent resolution over LIVE proposals (§17M).
Stale-context-resistant: proposals older than STALE_GAP_TURNS are
ignored unless the user's text explicitly references their content.
Returns {"proposal": Proposal} or {"ambiguous": True,
"candidates": [...]} or None."""
live = context.live_proposals()
if not live:
return None
fresh = [p for p in live
if context.turn - p.created_turn <= STALE_GAP_TURNS
or _references(p.summary, low)]
if not fresh:
return None # all live proposals are stale for this turn
live = fresh
# option-selectors pick among live proposals by ordinal
m = _OPTION_REFERENTS.search(low)
if m:
ordinals = {"first": 0, "second": 1, "third": 2, "other": 1,
"latter": len(live) - 1, "former": 0}
idx = ordinals.get(m.group(1).lower())
if idx is not None and idx < len(live):
return {"proposal": live[idx]}
if len(live) > 1:
return {"ambiguous": True, "candidates": live}
# multiple live proposals + no ordinal → ambiguous unless recency
# is decisive (only one proposal younger than the last topic shift)
if len(live) > 1:
# recency heuristic: if the newest proposal is at least 2 turns
# newer than the runner-up, recency decides; otherwise ask
newest, runner = live[-1], live[-2]
if newest.created_turn - runner.created_turn >= 2:
return {"proposal": newest}
return {"ambiguous": True, "candidates": live}
return {"proposal": live[0]}
def _references(summary: str, low: str) -> bool:
"""Does the user's text explicitly reference the proposal's content?
Distinctive content words (len>4) appearing in the text revive a
stale proposal (explicit > recency)."""
words = {w for w in _tokens(summary) if len(w) > 4}
return bool(words & set(_tokens(low)))
def _consequence(text: str, context: ConversationContext) -> str:
low = text.lower()
if any(w in low for w in ("replace", "migrate", "rewrite", "delete",
"remove all", "subsystem")):
return "HIGH"
if any(w in low for w in ("add", "extend", "port", "integrate")):
return "MEDIUM"
return "LOW"
def _constraints(context: ConversationContext) -> list:
"""User/project constraints recorded in recent turns (light capture;
the constraint STORE is future Substrate's — §17O boundary)."""
out = []
for turn in context.recent_turns[-6:]:
txt = turn.get("text", "").lower()
if "must not" in txt or "don't" in txt or "never" in txt or \
"no hardcoded" in txt:
out.append(turn["text"][:120])
return out
def _intent(objective: str, action_intent: str, approval_state: str,
confidence: float, ambiguity: str, ev: list, probes: list,
context: ConversationContext, origin_context_id: str | None,
requested_effect: str = "",
consequence_level: str | None = None) -> ConstructionIntent:
return ConstructionIntent(
intent_id=f"intent-{uuid.uuid4().hex[:8]}",
objective=objective,
requested_effect=requested_effect or objective,
resolved_target=context.active_project,
resolved_referent=None,
origin_context_id=origin_context_id,
action_intent=action_intent,
approval_state=approval_state,
confidence=confidence, ambiguity=ambiguity,
consequence_level=consequence_level
or ("LOW" if action_intent == "DISCUSSION" else "MEDIUM"),
relevant_constraints=_constraints(context),
classification_evidence=ev, probe_questions=probes)
+24 -1
View File
@@ -293,9 +293,32 @@ def classify_mode(
file_scoped = True
break
# T17 §17Q: indefinite-article new-artifact evidence — "a small CLI
# that renames photos", "build me a program that..." — is
# new-lifecycle language even without explicit standalone keywords.
# Guarded: requires a CREATION verb or artifact noun, and yields to
# local/scope/file evidence (fix a bug / add a flag never counts).
_CREATION_VERBS = {"build", "create", "make", "write", "develop"}
_ARTIFACT_NOUNS = {"program", "cli", "tool", "app", "script", "service",
"library", "package", "module", "dashboard",
"website", "bot", "generator", "tracker", "game",
"application", "command"}
article_standalone = False
if re.search(r"\b(a|an|another)\s+[a-z]", obj.lower()):
creation_verb = bool(_tokens(obj.lower()) & _CREATION_VERBS)
artifact_noun = bool(_tokens(obj.lower()) & _ARTIFACT_NOUNS)
# structural evidence outranks lexical newness (§5): a known
# existing target means article language describes a CHANGE
# ("add a flag to the deploy script"), not a new project
if (creation_verb or artifact_noun) and not local and not scoped \
and not file_scoped and not target_project \
and project_exists is not True:
article_standalone = True
ev.append("indefinite-article new-artifact language")
# 3. PROJECT_BUILD: standalone/new-lifecycle language AND no existing
# project for the target (project existence materially matters, §5)
if standalone:
if standalone or article_standalone:
ev.append(f"standalone/new-lifecycle language: {standalone[:3]}")
if project_exists is True:
# §5: existing project preferred unless evidence says otherwise —
+135
View File
@@ -0,0 +1,135 @@
"""construction_projection.py — T16 §16I: stable versioned PUBLIC
projection of construction state for future Substrate integration.
This is a PROJECTION, never a second authority. All fields are derived
(read-only) from the canonical scaffold store (Scaffold.load) plus the
live wait bridge state (event_wait.operation_wait). A consumer (the
future Substrate, the mock client in tests) must know NOTHING about
current.json layout or internal entity arrays — only this schema.
Schema versioning: SCHEMA_VERSION is bumped on any field change; consumers
negotiate by reading schema_version first. Unknown future fields are
ignored by readers (forward-tolerant); removed fields bump the major.
"""
from __future__ import annotations
from typing import Any
SCHEMA_VERSION = 1
# The mandate's required projection fields (§16I). None may carry live
# provider/account secrets or internal storage layout.
FIELDS = (
"schema_version", "project_id", "originating_goal_id",
"construction_id", "contract_id",
"mode", "depth", "phase", "stage",
"operation_id", "waiting_state", "correlation_id",
"builder_state", "blockers", "approval_required",
"readiness_canary", "liveness_canary", "exit_canary",
"verification_state", "checkpoint", "evidence_refs",
"failure_class", "completion_state", "candidate_next_action",
)
def _contract_entity(doc: dict, contract_id: str) -> dict | None:
for e in doc.get("entities", []):
if e.get("id") == f"contract:{contract_id}":
return e
return None
def project(
scaffold, contract_id: str, *,
originating_goal_id: str = "",
operation_id: str = "",
correlation_id: str = "",
stage: str = "",
builder_state: str = "",
verification_state: str = "",
checkpoint: str = "",
failure_class: str = "",
completion_state: str = "",
) -> dict:
"""Build the stable public projection for one contract. `scaffold` is
a Rune Scaffold instance (the canonical store); all live fields are
derived from it. Optional live fields (waiting_state etc.) are filled
from the event bridge when operation_id is supplied."""
doc = scaffold.load()
c = _contract_entity(doc, contract_id) or {}
ids = c.get("identities", {})
ext = c.get("ext", {})
trace = ext.get("decision_trace", {})
waiting_state = ""
live_correlation = correlation_id
if operation_id:
try:
from rune import event_wait as ew
rec = ew.operation_wait(operation_id)
if rec is not None:
waiting_state = rec.get("status", "")
live_correlation = correlation_id or rec.get("wait_id", "")
except OSError: # bridge data root unreadable → degrade, never lie
waiting_state = "BRIDGE_UNREACHABLE"
except RuntimeError: # waitstore not resolvable in this context
waiting_state = "BRIDGE_UNREACHABLE"
checkpoints = c.get("checkpoints", [])
evidence_refs = []
for cp in checkpoints:
ev = cp.get("evidence", "")
if ev:
evidence_refs.append(ev[:120])
approval = trace.get("approval", {})
approval_required = bool(
approval.get("required") or approval.get("state") == "REQUIRED"
or c.get("phase") == "awaiting_approval")
blockers: list[str] = []
blk = ids.get("blocker")
if isinstance(blk, dict):
blockers.append(f"{blk.get('class', 'BLOCKED')}: "
f"{blk.get('evidence', '')[:120]}")
elif isinstance(blk, str) and blk:
blockers.append(blk)
return {
"schema_version": SCHEMA_VERSION,
"project_id": getattr(scaffold, "project_id", ""),
"originating_goal_id": originating_goal_id,
"construction_id": contract_id,
"contract_id": contract_id,
"mode": ids.get("construction_mode", "") or "",
"depth": ids.get("depth", "") or "",
"phase": c.get("phase", ""),
"stage": stage,
"operation_id": operation_id,
"waiting_state": waiting_state,
"correlation_id": live_correlation,
"builder_state": builder_state,
"blockers": blockers,
"approval_required": approval_required,
"readiness_canary": ext.get("readiness_canary", ""),
"liveness_canary": ext.get("liveness_canary", ""),
"exit_canary": ext.get("exit_canary", ""),
"verification_state": verification_state,
"checkpoint": checkpoint,
"evidence_refs": evidence_refs,
"failure_class": failure_class,
"completion_state": completion_state,
"candidate_next_action": ids.get("next_action", ""),
}
def validate(projection: dict) -> list[str]:
"""Check a projection against the schema. Returns a list of errors
(empty = valid). Consumers use this before trusting a projection."""
errors = []
if projection.get("schema_version") != SCHEMA_VERSION:
errors.append(f"schema_version: expected {SCHEMA_VERSION}, got "
f"{projection.get('schema_version')!r}")
for f in FIELDS:
if f not in projection:
errors.append(f"missing field: {f}")
return errors
+118
View File
@@ -0,0 +1,118 @@
"""drift_resume.py — T16 §16P: drift-aware resume for months-later
project re-entry.
Months-later resume must NOT blindly trust the last checkpoint. The
16O benchmark proved both cold and scaffold-assisted paths converge on
the same principle: checkpoint claims NEVER substitute for reality.
Flow:
load last verified scaffold state (expectations)
↓
inspect current repo identity/state (observations)
↓
compare expected vs actual → DriftRecord list
↓
unchanged: resume
drift: classify EXPECTED / BENIGN / MATERIAL / CONFLICTING /
UNKNOWN — MATERIAL/UNKNOWN can block dependent work
Reality wins unless the claim carries explicit user-locked authority
(the one scaffold exception, already enforced by layout_contract).
"""
from __future__ import annotations
from typing import Callable
EXPECTED = "EXPECTED" # checkpoint older than reality; benign aging
BENIGN = "BENIGN" # difference with no bearing on the claims
MATERIAL = "MATERIAL" # reality contradicts a scaffold claim
CONFLICTING = "CONFLICTING" # two authorities disagree; needs user
UNKNOWN = "UNKNOWN" # cannot decide — treated as blocking
BLOCKING_CLASSES = (MATERIAL, CONFLICTING, UNKNOWN)
def classify_drift(claim: dict, observation: dict) -> dict:
"""Classify one claim-vs-observation pair. Deterministic:
verification evidence beats prose; explicit beats inferred.
claim: {"claim": str, "kind": str, "verified_at": str,
"user_locked": bool}
observation: {"actual": str|None, "checked_by": str,
"matches": bool|None}
"""
if observation.get("matches") is True:
return {"claim": claim.get("claim", ""),
"class": EXPECTED if claim.get("stale_ok") else "CONFIRMED",
"evidence": observation.get("checked_by", "")}
if observation.get("matches") is None:
return {"claim": claim.get("claim", ""), "class": UNKNOWN,
"evidence": f"unverifiable via {observation.get('checked_by', '?')}"}
if claim.get("user_locked"):
return {"claim": claim.get("claim", ""), "class": CONFLICTING,
"evidence": ("reality differs from a USER-LOCKED claim "
"— user arbitration required")}
return {"claim": claim.get("claim", ""), "class": MATERIAL,
"evidence": (f"reality differs: {observation.get('actual', '')}"
f" (checked by {observation.get('checked_by', '?')})")}
def reconcile(claims: list[dict], observer: Callable[[dict], dict]) -> dict:
"""Reconcile a set of checkpoint claims against reality.
observer(claim) → observation dict (runs the actual check: test
command, file read, git query — never trusts the claim text).
Returns {"resume": bool, "drifts": [...], "blocking": [...]}. The
resume decision: drift-aware, not blind — ANY blocking-class drift
stops dependent work until reconciled."""
drifts = [classify_drift(c, observer(c)) for c in claims]
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
+173 -49
View File
@@ -47,6 +47,7 @@ waits against Gitea (the event is the authority).
from __future__ import annotations
import importlib.util
import json
import os
import select
@@ -55,6 +56,7 @@ import subprocess
import sys
import time
from pathlib import Path
from typing import Any
DEFAULT_WR_ROOT = Path.home() / ".local" / "share" / "mimo-wait-resume"
FALLBACK_WR_ROOT = Path("/home/nick/.local/share/mimo-wait-resume")
@@ -93,63 +95,106 @@ for attempt in range(3):
def wr_root() -> Path:
"""Resolve the bridge DATA root. T17 17T finding: two live roots
existed (the shared /mnt/workdrive/wr-bridge and the legacy
~/.local/share/mimo-wait-resume) and an unpinned consumer silently
read the stale one. Policy now: explicit env → shared root if it
exists (the canonical production root) → legacy home (warn loudly —
a stale-store split-brain must be visible, never silent)."""
import sys as _sys
env = os.environ.get("RUNE_WR_ROOT")
if env:
return Path(env)
shared = Path("/mnt/workdrive/wr-bridge")
if shared.exists():
if DEFAULT_WR_ROOT.exists():
_sys.stderr.write(
"event_wait: RUNE_WR_ROOT unset — using shared bridge "
f"root {shared} while legacy root {DEFAULT_WR_ROOT} also "
"exists; pin RUNE_WR_ROOT to silence\n")
return shared
if DEFAULT_WR_ROOT.exists():
return DEFAULT_WR_ROOT
return FALLBACK_WR_ROOT
def _load_waitstore():
# waitstore.py lives in the DAEMON CODE dir. In production WR_ROOT is
# the daemon dir (code + data together); in isolated test environments
# WR_ROOT is a data-only dir, so the code dir is resolved separately:
# RUNE_WR_CODE env → WR_ROOT → known install locations.
for cand in (os.environ.get("RUNE_WR_CODE"), str(wr_root()),
# waitstore instances cached per data root (isolated tests use per-test
# tmp roots; the daemon uses its install dir). Loading via importlib.util
# keeps the dynamic import pyright-clean — no unresolvable-import warning.
_waitstore_cache: dict[str, Any] = {}
def _load_waitstore() -> Any:
"""Load the bridge's OWN waitstore module (never a copy), bound to
the adapter's data root. waitstore.py lives in the DAEMON CODE dir;
in production WR_ROOT is that dir (code + data together); in isolated
test environments WR_ROOT is a data-only dir, so the code dir is
resolved separately: RUNE_WR_CODE env → WR_ROOT → known installs."""
root = wr_root()
# waitstore binds its DATA dir from WR_ROOT at import time. Pin
# WR_ROOT to the adapter's authority so adapter-side and daemon-side
# state agree — otherwise a mismatch silently splits the wait store
# from the event matcher and continuation dies as a watchdog timeout.
os.environ["WR_ROOT"] = str(root)
cached = _waitstore_cache.get(str(root))
if cached is not None:
return cached
code_dir = None
for cand in (os.environ.get("RUNE_WR_CODE"), str(root),
str(FALLBACK_WR_ROOT)):
if cand and (Path(cand) / "waitstore.py").is_file():
if cand not in sys.path:
sys.path.insert(0, cand)
code_dir = Path(cand)
break
# waitstore binds its DATA dir from WR_ROOT at import time. The
# adapter's authority for the data dir is RUNE_WR_ROOT (wr_root());
# pin WR_ROOT to match so adapter-side and daemon-side state agree —
# otherwise a mismatch silently splits the wait store from the event
# matcher and continuation dies as a watchdog timeout.
os.environ["WR_ROOT"] = str(wr_root())
import waitstore # the bridge's own durable store — NOT a copy
# if the data root CHANGED since the module bound it (isolated tests
# per-test tmp roots; daemon restarts), re-bind by reloading — the
# module-level WAITS/EVENTS paths are derived from WR_ROOT at import
import importlib
root = wr_root()
if str(getattr(waitstore, "WAITS", "").parent) != str(root):
waitstore = importlib.reload(waitstore)
return waitstore
if code_dir is None:
raise RuntimeError(
"waitstore.py not found in known W/R roots "
"(RUNE_WR_CODE, WR_ROOT, ~/.local/share/mimo-wait-resume)")
spec = importlib.util.spec_from_file_location(
"waitstore", str(code_dir / "waitstore.py"))
if spec is None or spec.loader is None:
raise RuntimeError(f"cannot load waitstore from {code_dir}")
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
_waitstore_cache[str(root)] = mod
return mod
def register_process_wait(operation_id: str, *, contract_id: str = "",
project: str = "", objective: str = "",
watched_for: str = "",
provider: str = "builder",
event_type: str = "builder_exit",
sha: str = "",
watchdog_secs: int | None = None) -> dict:
"""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."""
the bridge can resume a Python controller, not just MiMo sessions.
provider/event_type are the adapter's event identity on the shared
bridge (16E: ONE wait primitive; builder / Gitea-CI / future adapters
differ ONLY in these identity fields + their liveness probe — the
registration, FIFO rendezvous, matching, idempotency, restart
recovery, and watchdog semantics are the same code for all).
Defaults preserve the T15 builder semantics."""
ws = _load_waitstore()
fifo = fifo_path(operation_id)
fifo.parent.mkdir(parents=True, exist_ok=True)
if fifo.exists():
fifo.unlink()
os.mkfifo(fifo)
# Do NOT unlink+recreate an existing fifo: a caller that opened its
# read end before registering (open_fifo_reader, §16Y #10 race fix)
# would be orphaned onto the old inode while the daemon's resume
# writer opened the new one. The rendezvous is create-if-missing.
if not fifo.exists():
os.mkfifo(fifo)
os.chmod(fifo, 0o666) # cross-user bridge (see open_fifo_reader)
writer = ("import sys,json,time,os;"
"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)")
rec = ws.new_wait(
session_id=f"controller-{operation_id}",
provider="builder",
event_type="builder_exit",
provider=provider,
event_type=event_type,
sha=sha,
repo=project or "construction",
run_ref=operation_id,
expected="success",
@@ -168,6 +213,13 @@ def register_process_wait(operation_id: str, *, contract_id: str = "",
"watchdog_secs": watchdog_secs
or int(os.environ.get("WR_WATCH_SECS", "600"))}
ws.save(rec)
# cross-user bridge (T16 §16C live finding): the daemon reconciler may
# run under a different account than the registering controller — the
# durable record must be readable/writable on both sides
try:
os.chmod(ws.WAITS / f"{rec['wait_id']}.json", 0o666)
except OSError:
pass # single-user deployment: nothing to relax
return rec
@@ -175,6 +227,29 @@ def fifo_path(operation_id: str) -> Path:
return wr_root() / "resumes" / f"{operation_id}.fifo"
def open_fifo_reader(operation_id: str) -> int:
"""Open the operation's FIFO read end (O_NONBLOCK) BEFORE the wait is
registered. §16Y adversarial #10: an event that arrives after
registration but before the controller enters select() would find no
reader — the resume write fails (ENXIO) and the wait lands
RESUME_UNVERIFIED while the controller sleeps until the watchdog.
Holding the reader open across the registration window closes that
race for every adapter; the fd is passed to await_resume_event and
closed by the caller."""
fifo = fifo_path(operation_id)
fifo.parent.mkdir(parents=True, exist_ok=True)
if fifo.exists():
fifo.unlink()
os.mkfifo(fifo)
# CROSS-USER BRIDGE (T17 17C root-cause, re-landed): the T16 chmod was
# lost in a rebase resolution — FIFOs landed 644 (666 & umask 022),
# and the daemon running as a DIFFERENT user got EACCES on the resume
# write (rc=1 → RESUME_UNVERIFIED) while same-user tests passed.
# chmod AFTER mkfifo is the only reliable act — umask never applies.
os.chmod(fifo, 0o666)
return os.open(fifo, os.O_RDONLY | os.O_NONBLOCK)
def run_builder_under_events(builder_cmd: list[str], operation_id: str, *,
daemon_url: str = DEFAULT_DAEMON_URL):
"""Launch the REAL builder under the event notifier (separate process
@@ -246,10 +321,18 @@ def liveness(operation_id: str, pgid: int | None = None) -> bool:
def await_resume_event(operation_id: str, *, watchdog_secs: int,
pgid: int | None = None,
max_rearms: int = 20) -> dict:
max_rearms: int = 20,
fifo_fd: int | None = None) -> dict:
"""YIELD until the bridge resumes this controller. Blocks in select()
on the FIFO — no sleep, no polling.
fifo_fd: a pre-opened read end from open_fifo_reader() (§16Y #10 race
fix — the fd must already exist when the wait becomes registered).
Caller-owned fds are never closed here; on EOF (writer gone, no
token) the call returns {"kind": "eof"} instead of busy-looping on an
always-readable fd — the caller falls back to the durable waitstore
record via resume_or_recover/operation_wait.
Watchdog semantics (§15D): the deadline is a FAILURE DETECTOR, never
the scheduler. On expiry, liveness is inspected ONCE (pgid membership,
then operation-specific cmdline evidence — real builders regroup their
@@ -258,17 +341,21 @@ def await_resume_event(operation_id: str, *, watchdog_secs: int,
build outlived a single 900s deadline, and node regrouping hid the
processes from the pgid scan — neither is a failure of continuation.
"""
fifo = fifo_path(operation_id)
# idempotent re-arm: a prior await consumed+unlinked the fifo; the
# rendezvous point is (re)created so watchdog re-arm and recovery
# paths can wait again on the same operation
fifo.parent.mkdir(parents=True, exist_ok=True)
if not fifo.exists():
os.mkfifo(fifo)
for arm in range(max_rearms + 1):
deadline = time.monotonic() + watchdog_secs
owned_fd = fifo_fd is None
if owned_fd:
fifo = fifo_path(operation_id)
# idempotent re-arm: a prior await consumed+unlinked the fifo; the
# rendezvous point is (re)created so watchdog re-arm and recovery
# paths can wait again on the same operation
fifo.parent.mkdir(parents=True, exist_ok=True)
if not fifo.exists():
os.mkfifo(fifo)
fd = os.open(fifo, os.O_RDONLY | os.O_NONBLOCK)
try:
else:
fd = fifo_fd
try:
for arm in range(max_rearms + 1):
deadline = time.monotonic() + watchdog_secs
while True:
remaining = deadline - time.monotonic()
if remaining <= 0:
@@ -278,7 +365,12 @@ def await_resume_event(operation_id: str, *, watchdog_secs: int,
continue
data = os.read(fd, 4096)
if not data:
continue
if owned_fd:
continue
# caller-owned fd at EOF: writer is gone without
# delivering a token — never a busy loop; the durable
# record decides what actually happened
return {"kind": "eof"}
try:
token = json.loads(data.decode())
except ValueError:
@@ -287,15 +379,16 @@ def await_resume_event(operation_id: str, *, watchdog_secs: int,
rec = ws.load(token.get("wait_id", ""))
return {"kind": "event", "wait": rec,
"resumed_at": token.get("resumed_at")}
finally:
# deadline expired: liveness decides re-arm vs PROCESS_LOST
alive = liveness(operation_id, pgid)
if not alive:
return {"kind": "watchdog", "alive": False,
"rearms": arm}
return {"kind": "watchdog", "alive": liveness(operation_id, pgid),
"rearms": max_rearms}
finally:
if owned_fd:
os.close(fd)
# deadline expired: liveness decides re-arm vs PROCESS_LOST
alive = liveness(operation_id, pgid)
if not alive:
return {"kind": "watchdog", "alive": False,
"rearms": arm}
return {"kind": "watchdog", "alive": liveness(operation_id, pgid),
"rearms": max_rearms}
def operation_wait(operation_id: str) -> dict | None:
@@ -332,6 +425,37 @@ def resume_or_recover(operation_id: str) -> dict:
return {"kind": "still_waiting", "wait": rec}
def find_matching_event(provider: str, event_type: str, run_ref: str) -> dict | None:
"""Search the bridge's durable event log for an event matching this
identity. T16 §16C live finding: on a serialized single-runner
machine the deploy job ALWAYS starts after ci.yml completes — the
completion event lands before the waiter registers, and a pure
forward-only FIFO rendezvous can never deliver it (both production
cycles fell back to the 900s deadline). Events are durable precisely
so they can be replayed: a waiter that finds a matching event at
registration goes straight to the authority read instead of sleeping
for a wake that already happened."""
import json as _json
ws = _load_waitstore()
events_dir = ws.EVENTS
if not events_dir.is_dir():
return None
best = None
for f in sorted(events_dir.glob("*.json"),
key=lambda p: p.stat().st_mtime, reverse=True):
try:
rec = _json.loads(f.read_text())
except (OSError, ValueError):
continue
ev = rec.get("event", {})
if (ev.get("provider") == provider
and ev.get("event_type") == event_type
and str(ev.get("run_ref", "")) == run_ref):
best = ev # newest match wins
break
return best
def post_process_event(operation_id: str, exit_code: int, *,
daemon_url: str = DEFAULT_DAEMON_URL) -> dict:
"""Direct event injection (test seam + manual reconciliation). The
+2 -1
View File
@@ -192,7 +192,8 @@ def _default_runner(prompt: str, artifact_path: str) -> None:
import subprocess
mimo_bin = os.environ.get("MIMO_BIN", "/var/lib/gitea-desktop-runner/bin/mimo")
mimo_model = os.environ.get("MIMO_MODEL", "Verboo Code/glm-5.3-flash")
from rune.provider_context import require_model
mimo_model = require_model() # T16 §16F: provider layer owns selection
cmd = [
mimo_bin,
"run",
+13 -1
View File
@@ -21,10 +21,11 @@ INTERMITTENT = "INTERMITTENT"
KNOWN_PROCESS_DEFECT = "KNOWN_PROCESS_DEFECT"
PERMANENT = "PERMANENT"
APPROVAL_REQUIRED = "APPROVAL_REQUIRED"
PROVIDER_UNAVAILABLE = "PROVIDER_UNAVAILABLE"
UNKNOWN = "UNKNOWN"
CLASSES = (TRANSIENT, INTERMITTENT, KNOWN_PROCESS_DEFECT, PERMANENT,
APPROVAL_REQUIRED, UNKNOWN)
APPROVAL_REQUIRED, PROVIDER_UNAVAILABLE, UNKNOWN)
# recovery policy per class: (max_retries, action)
POLICY = {
@@ -33,6 +34,9 @@ POLICY = {
KNOWN_PROCESS_DEFECT: (1, "apply_evidenced_repair_then_retry"),
PERMANENT: (0, "do_not_retry"),
APPROVAL_REQUIRED: (0, "block"),
# T16 §16F: provider failures NEVER auto-retry and NEVER fall back
# to another provider — preserve the real error, block the branch.
PROVIDER_UNAVAILABLE: (0, "block_branch_preserve_error"),
UNKNOWN: (1, "bounded_evidence_probe_then_block_or_escalate"),
}
@@ -74,6 +78,14 @@ def classify_failure(error_text: str, *, context: dict | None = None) -> dict:
return {"class": KNOWN_PROCESS_DEFECT,
"evidence": f"known defect: {known_defect}",
"policy": POLICY[KNOWN_PROCESS_DEFECT]}
# T16 §16F: an explicit provider-context failure wins over every
# generic marker — the real status/API code is preserved verbatim in
# the evidence, the branch blocks, and no fallback is attempted.
if context.get("provider_error"):
return {"class": PROVIDER_UNAVAILABLE,
"evidence": (f"provider error preserved: {error_text}"
if error_text else "provider error"),
"policy": POLICY[PROVIDER_UNAVAILABLE]}
if any(m in text for m in _APPROVAL_MARKERS):
return {"class": APPROVAL_REQUIRED,
"evidence": "approval marker present",
+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
@@ -241,6 +241,13 @@ class TaskStateRepository:
with self._lock:
return self._tasks.get(task_id)
def list_all(self) -> list[TaskStateRecord]:
"""Every tracked task record (T17 17F: user-facing counters and
listings derive from THIS authority, not the legacy operator
cache)."""
with self._lock:
return list(self._tasks.values())
def list_open(self, channel_id: str) -> list[TaskStateRecord]:
with self._lock:
return sorted(self._open_in_channel(channel_id), key=lambda t: t.updated_at)
-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",
}
+184
View File
@@ -22,6 +22,7 @@ import json
import time
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, cast
# stage states
@@ -101,6 +102,15 @@ class Programme:
"next_action": None,
"reason": (f"{len(approval)} stage(s) need user "
f"authorization")}
# T17 §17E: all stages terminal but some FAILED — the programme
# may still close (programme-level acceptance decides), never
# hang in UNKNOWN_INTERRUPTION.
if all(s.state in TERMINAL for s in self.stages):
failed = [s.stage_id for s in self.stages if s.state == FAILED]
return {"verdict": "PROGRAMME_COMPLETE_WITH_FAILURES",
"next_action": None,
"reason": (f"all stages terminal; failed: {failed} — "
f"programme-level acceptance decides")}
return {"verdict": "UNKNOWN_INTERRUPTION", "next_action": None,
"reason": "no stage runnable and none terminal"}
@@ -163,3 +173,177 @@ def run_programme(prog: Programme, stage_runner, *,
stage.state = stage_runner(stage) # runner decides the new state
log.append({"stage": target, "new_state": stage.state})
return log
# ── T17 §17D: production wiring — Scaffolding-integrated driver ─────────
#
# The 15M canary proved the RULE; this adapter makes it CANONICAL. One
# programme = a list of stages; each stage maps 1:1 to a Scaffolding
# contract (stage_id == contract_id). Stages run through the Construction
# Interface lifecycle (approved→building→verifying→closed); asynchronous
# stage work runs under the event bridge (WAITING_EVENT is a state, not
# a stop). Persisted to the scaffold store's programme area — reloadable
# after restart with no transcript.
DEFAULT_PROGRAMME_AREA = "programmes"
def _programme_base(scaffold_store: Path) -> Path:
return Path(scaffold_store) / DEFAULT_PROGRAMME_AREA
def scaffolding_stage_runner(interface, *, scaffold_store: Path,
daemon_url: str = "",
watchdog_secs: int = 900):
"""Return a stage_runner for run_programme bound to the Construction
Interface. The stage's work is read from stage.result["work"] — a
callable (interface, stage) → dict with {"ok": bool, "evidence": str,
"async_cmd": [argv] optional}. When async_cmd is present the work
runs under the event bridge and the driver resumes on the EVENT;
canonical verification still decides completion truth. Stages without
work complete as governance-only transitions."""
from rune import event_wait as ew # local import: optional dependency
def _phase(contract_id: str) -> str:
# ALWAYS re-read: transition() saves through its own doc copy, so
# a locally-held contract dict goes stale after any transition
# (T17 17E live finding — the draft→approved→building chain
# silently stopped after the first step on a stale reference).
doc = interface.scaffold.load()
c = interface.scaffold.contract(doc, contract_id)
return (c or {}).get("phase", "")
def run(stage: ProgrammeStage) -> str:
contract_id = stage.stage_id
phase = _phase(contract_id)
if phase == "":
stage.result["error"] = f"no contract {contract_id}"
return FAILED
if phase == "closed":
return COMPLETE # idempotent re-entry (duplicate signal case)
if phase == "draft":
interface.transition(contract_id, "approved",
evidence_ref="programme-stage-start")
if _phase(contract_id) == "approved":
interface.transition(contract_id, "building",
evidence_ref="programme-stage-start")
work = cast("Any", stage.result.get("work"))
if work is None:
interface.transition(contract_id, "verifying",
evidence_ref="governance-only-stage")
interface.close(
contract_id,
observed_evidence=[{"source": "programme-driver",
"detail": "governance-only stage: "
"no work, closure by "
"programme acceptance"}],
scope_files=[])
return COMPLETE
# work is either a callable (interface, stage) → outcome dict, or
# a literal outcome spec dict ({"ok", "evidence", "async_cmd"?})
outcome: dict = (dict(cast("dict", work(interface, stage)))
if callable(work) else dict(cast("dict", work)))
async_cmd = outcome.get("async_cmd")
if async_cmd:
op = f"stage-{contract_id}-{int(time.time())}"
ew.register_process_wait(
op, contract_id=contract_id,
project=getattr(interface.scaffold, "project_id", ""),
objective=stage.objective,
watched_for="stage work completion event",
watchdog_secs=watchdog_secs)
notifier = ew.run_builder_under_events(
async_cmd, op, daemon_url=daemon_url or ew.DEFAULT_DAEMON_URL)
stage.result["operation_id"] = op
stage.result["notifier_pgid"] = notifier.pid
result = ew.await_resume_event(op, watchdog_secs=watchdog_secs,
pgid=notifier.pid)
if result["kind"] != "event":
stage.result["error"] = f"stage event wait: {result['kind']}"
interface.transition(contract_id, "failed",
evidence_ref="stage-event-lost")
return FAILED
rec: dict = result.get("wait") or {}
outcome["evidence"] = (f"event resume; wait {rec.get('wait_id')} "
f"verdict={rec.get('verdict')}")
if not outcome.get("ok"):
stage.result["error"] = outcome.get("evidence", "work failed")
# truthful failure: record the evidence ON the contract and
# drive it to the failed phase via the legal transition map
# (building→failed is legal; no exception swallowing)
doc = interface.scaffold.load()
c = interface.scaffold.contract(doc, contract_id)
if c is not None:
c.setdefault("checkpoints", []).append({
"at": time.strftime("%Y-%m-%dT%H:%M:%SZ",
time.gmtime()),
"phase": c.get("phase", ""),
"evidence": "programme-stage-failed",
"note": (outcome.get("evidence") or "work failed")[:200],
})
interface.scaffold.save(doc)
phase = c.get("phase", "")
if phase == "building":
interface.transition(contract_id, "failed",
evidence_ref="programme-stage-failed")
return FAILED
# completion truth: work evidence recorded ON the contract, then
# closed (evidence lives on the durable object, not the log)
if _phase(contract_id) == "building":
interface.transition(contract_id, "verifying",
evidence_ref="programme-stage-verify")
doc = interface.scaffold.load()
c = interface.scaffold.contract(doc, contract_id)
if c is not None:
c.setdefault("checkpoints", []).append({
"at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"phase": "verifying",
"evidence": "programme-stage-work",
"note": (outcome.get("evidence") or "work completed")[:200],
})
interface.scaffold.save(doc)
interface.close(
contract_id,
observed_evidence=[{"source": "programme-driver",
"detail": outcome.get("evidence", "ok")}],
scope_files=[])
return COMPLETE
return run
def run_construction_programme(prog: Programme, interface, *,
scaffold_store: Path,
daemon_url: str = "",
watchdog_secs: int = 900,
max_transitions: int = 100) -> list[dict]:
"""Canonical multi-stage construction continuation (T17 §17D):
run_programme with the Scaffolding-bound runner; persists the
programme around every transition so a restart resumes from the
durable record, never a transcript. Returns the transition log."""
runner = scaffolding_stage_runner(
interface, scaffold_store=scaffold_store,
daemon_url=daemon_url, watchdog_secs=watchdog_secs)
log: list[dict] = []
base = _programme_base(scaffold_store)
for _ in range(max_transitions):
prog.save(base)
canary = prog.continuation_canary()
log.append({"canary": canary})
verdict, action = canary["verdict"], canary["next_action"]
if action is None:
break
kind, target = action
if kind == "fallback":
log.append({"fallback_started": target})
break
stage = next(s for s in prog.stages if s.stage_id == target)
if stage.state == COMPLETE:
continue # duplicate continuation signal: idempotent
stage.state = ELIGIBLE
new_state = runner(stage)
stage.state = new_state
log.append({"stage": target, "new_state": new_state})
prog.save(base)
prog.save(base)
return log
+112
View File
@@ -0,0 +1,112 @@
"""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 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)
+48
View File
@@ -0,0 +1,48 @@
"""provider_context.py — T16 §16F: provider ownership boundary.
ARCHITECTURE RULE (T16 mandate, permanent):
SCAFFOLDING DOES NOT OWN PROVIDER SELECTION.
Provider, model, provider fallback, provider source, and provider account
belong to the surrounding Rune/TUI/provider layer. Construction code never
names a provider. The ONLY supported way for a driver/probe (builder
session, research stage, evidence probe — all PROVIDER_LAYER callers) to
learn which model to use is the ambient provider context, i.e. the
environment the surrounding layer sets.
works → continue
missing/unresolvable → fail LOUDLY with PROVIDER_UNRESOLVED
provider call fails → preserve the REAL error and status/API code,
classify BLOCKED/PROVIDER_UNAVAILABLE, stop the dependent branch
NO silent default provider. NO automatic fallback. NO preference mutation.
(Automatic provider fallback is DEFERRED to the future multi-source /
local-model architecture.)
"""
from __future__ import annotations
import os
class ProviderUnresolved(RuntimeError):
"""Raised when no provider context is available and a silent default
would otherwise have been selected. Deliberately loud: provider
selection is a user-layer decision, never a code default."""
def require_model(env_var: str = "MIMO_MODEL") -> str:
"""Resolve the current model from the ambient provider context.
The surrounding provider layer (TUI / operator / session environment)
owns this variable. A driver or probe MUST NOT carry its own default:
a missing value is a configuration gap the user layer must fix, not
something construction code may paper over by picking a provider."""
model = (os.environ.get(env_var) or "").strip()
if not model:
raise ProviderUnresolved(
f"PROVIDER_UNRESOLVED: {env_var} is not set — provider/model "
f"selection belongs to the provider layer (set {env_var} in "
f"the calling environment); no silent default is permitted "
f"(T16 §16F)")
return model
+36 -3
View File
@@ -740,12 +740,39 @@ class RuneOperator:
json.dump(self.state.to_dict(), f, indent=2)
def get_status(self) -> Dict[str, Any]:
"""Get Rune's current status."""
"""Get Rune's current status. T17 17F: task counters derive from
the CANONICAL TaskStateRepository — operator.json's task list is
a legacy cache, never a second authority."""
counters = self._task_counters()
return {
"identity": self.state.identity,
"role": self.state.role,
"current_task": self.state.current_task,
"active_model": self.state.active_model,
**counters,
}
def _task_counters(self) -> Dict[str, int]:
"""Counters from the canonical repo; repo vocabulary mapped to
the legacy counter buckets (created→pending)."""
buckets = {"pending": 0, "running": 0, "completed": 0}
try:
records = self._task_state.list_all()
except Exception: # noqa: BLE001 — repo unavailable: in-memory fallback
records = []
if records:
for r in records:
if r.status in ("created", "pending"):
buckets["pending"] += 1
elif r.status == "running":
buckets["running"] += 1
elif r.status == "completed":
buckets["completed"] += 1
return {"tasks_pending": buckets["pending"],
"tasks_running": buckets["running"],
"tasks_completed": buckets["completed"]}
# no repo records (fresh store): fall back to the in-memory set
return {
"tasks_pending": len([t for t in self.state.tasks if t.status == "pending"]),
"tasks_running": len([t for t in self.state.tasks if t.status == "running"]),
"tasks_completed": len([t for t in self.state.tasks if t.status == "completed"]),
@@ -1558,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")
@@ -1578,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).
+146 -2
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.59
Version: v0.71.0
Features: 41 (24 original + 17 new native commands)
Usage:
@@ -1697,9 +1697,37 @@ 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:
return getattr(_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 +1884,7 @@ class RuneApp(App):
# Show version info
self.chat_channel.add_message(
"system",
f"[dim]Rune v0.66.59 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.71.0 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours
@@ -3112,6 +3140,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:
@@ -3141,6 +3221,69 @@ class RuneApp(App):
elif arg.startswith("start "):
description = arg[6:]
# T17 17G: /build start goes through the SAME canonical
# Scaffolding classification as every other construction
# route — no independent construction state machine.
# NOT_CONSTRUCTION refuses; UNKNOWN surfaces the probe
# questions; PROJECT_BUILD/EXISTING_PROJECT_CHANGE
# activate the contract, then delegate to BuildManager
# as a bounded adapter (worktree isolation + resume
# ledger remain its unique capabilities).
try:
from rune.construction_interface import ConstructionInterface
iface = ConstructionInterface(builder="tui-build-cmd")
envelope = iface.classify(description)
state = envelope.get("state") or {}
mode_name = state.get("mode", "UNKNOWN")
probes = state.get("probe_questions", [])
except Exception as exc: # noqa: BLE001 — classification
mode_name = "UNKNOWN" # unavailable: refuse, fail closed
probes = []
self._ch().add_message("system",
f"[red]Construction classification unavailable ({exc}) — refusing to start a build[/red]")
return
if mode_name in ("NOT_CONSTRUCTION", "UNKNOWN"):
msg = (f"[yellow]'/build start' refused: classified "
f"{mode_name}.[/yellow]")
for q in probes[:3]:
msg += f"\n - {q}"
self._ch().add_message("system", msg)
return
# 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]")
@@ -3421,6 +3564,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,
+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]
+8
View File
@@ -52,6 +52,14 @@ def _save(data):
os.replace(tmp_path, LEDGER_PATH)
def load() -> dict:
"""Public read of the persisted task ledger (T17 17R: the TUI
startup loader called task_persist.load() which did not exist —
the AttributeError was silently swallowed and startup loading was
dead code). Returns the raw ledger doc: {"tasks": [...]}."""
return _load()
def export(tasks_json):
"""Save a list of tasks (from task tool output) to the ledger.
+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
+73
View File
@@ -0,0 +1,73 @@
"""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")
STATUS_POLL_RE = re.compile(
"|".join(
(
r"actions/runs",
r"commits/.*/status",
r"required.checks",
r"deploy_qualify",
r"releases/tags",
r"readlink.*rune-code/current",
)
),
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."""
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()]
for i, segment in enumerate(segments):
m = LONG_SLEEP_RE.search(segment)
if not m:
continue
secs = int(m.group(1))
if secs < THRESHOLD_SECS:
continue
# the regression pattern is sleep THEN check: the status poll may
# be in the same segment or the immediately following ones (the
# "sleep; curl status" / "sleep && fetch && readlink" shapes)
lookahead = " ".join(segments[i:i + 3])
if STATUS_POLL_RE.search(lookahead):
return {
"classification": "UNJUSTIFIED_SLEEP",
"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": ""}
+48
View File
@@ -0,0 +1,48 @@
# T16 Stage 16A — exact baseline (verified 2026-09-15 ~15:40 local)
Fresh session ses_-ffe5f5a82d0b5ffeLrGt5rMRv. All facts verified independently this session; none carried from prior-session memory without re-verification.
## Identity / release chain
- gitea/master: 5bd8cca9410b = auto-bump 0.66.62 (2026-09-15 14:15:22 +0100)
- Releases: v0.66.62 published 14:15:51; v0.66.60 (13:29), v0.66.61 (13:42) same day
- Deployed: /mnt/workdrive/rune-code/current -> 5bd8cca9410b (symlink mtime 14:20)
- last-good -> fd5f31b (0.66.61-lineage fix commit)
- CONVERGED: release tag, deployed current, and master tip are the same SHA. No manual refresh used.
## Inherited T15 open item — RESOLVED on master
- 15Z CI failure (196cf19: pyright 445 vs 443; unit-tests job truncated) — fixed by:
- 193d819 "event_wait importlib-clean loading, zero pyright warnings" (= be87dc3 rebased/pushed)
- b2d6802 "§15K test no longer shells out to git worktree on CI"
- fd5f31b "§15D tests use ephemeral ports, not fixed 8793"
- These drove releases 0.66.60 → 0.66.61 → 0.66.62, each deployed. Nothing inherited is left open.
## Working copies
- /mnt/workdrive/issue161-harness: master tip, clean — THE code worktree for T16.
- /mnt/workdrive/rune (RUNE_HOME): branch pinned-live @ 90c7db1 (2026-09-08), 652 commits behind master, HEAD is a clean ancestor (fast-forwardable). Expected: RUNE_HOME is the data-continuity home; runtime launches from rune-code/current. Still: stale and drifting (16P material).
- 39 stale build-20260915-* worktrees, all cut at 90c7db1 (stale base) — /home/nick/.rune/worktrees/. Cleanup candidate (16W).
## Gates (all run in lock-exact environments, master tip 5bd8cca)
- Full suite: 1854 passed + 84 subtests, 0 failures (113s)
- Lint ratchet: PASS (BLE001 354/354, S110 80/80 at baseline). Raw `ruff check` = 12 errors, all within committed lint-baseline (mandate's "8 pre-existing" is now 12 — drift, 16W item).
- Type-check ratchet: PASS — errors 117 (= baseline), warnings 437 (≤ 443). CRITICAL METHOD NOTE: must run in `.type-venv` recreated from requirements-ci.txt (lock-exact). Stale /tmp/ci-venv (pyright 1.1.414 but stale env) false-failed 124 vs 117. Environment-exactness is a repeat failure class (16U).
- Henxels: rc=0.
## Wait/resume infrastructure
- W/R daemon RUNNING: pid 3756873, `python3 daemon.py 8791` (webhook receiver on 0.0.0.0:8791), restarted 14:08 today.
- Daemon lives at /home/nick/.local/share/mimo-wait-resume (daemon.py, waitstore.py, verify.py, own tests) with its OWN git snapshot @ 4811070 "T15 §15D/§15E: per-wait resume_cmd override + PROCESS_EVENT wait kind + /process-event endpoint". The daemon is NOT part of the master tree — src/rune/ has event_wait.py (client-side adapter) but no daemon.py. (16M inventory finding: out-of-repo deployment, separate snapshot.)
- Wait store: 45 records — states: COMPLETED 19, RESUME_UNVERIFIED 10, FAILED 6, READY_TO_RESUME 6, WAITING_EXTERNAL 4. Kinds: EXACT_WORKFLOW 24, RELEASE_DEPLOY 8, PROCESS_EVENT 7 (+6 pre-kind records with wait_kind absent).
- 4 WAITING_EXTERNAL + 6 READY_TO_RESUME + 10 RESUME_UNVERIFIED = outstanding waits to reconcile or leave truthfully parked.
## Scaffolding stores
- Repo store/scaffold/current.json: spine-shaped (entities/relationships/observations), no projects key.
- store/scaffold/projects/: EMPTY. T14/T15 project scaffolds lived in ephemeral /tmp stores (e.g. /tmp/t15r-store). Durable evidence: store/evidence/t14/, store/evidence/t15/ (+ t16/ created this session).
## T15 maturity — CORRECTED RECORD (16A mandate)
- Programme continuation MECHANISM (stage_guards, event waits, canaries): INTEGRATED / TEST-PROVEN. T15's own test evidence stands — not erased.
- Uninterrupted REAL-SESSION programme continuation: NOT YET LIVE-PROVEN. The prior session's actual user experience was repeated manual "continue" despite the mechanism being tested green. This fresh T16 session is the live experiment (Stage 16B).
- Builder event-driven wait: CANONICAL DEFAULT (bounded live proof, T15).
- CI/deployment wait: STILL POLLING FALLBACK (sleep-N → query patterns remain in release/CI verification paths) — the 16C/D target. Verified by inspection during 16A setup; full inventory in 16C.
## Provider/model handling (initial scan, full audit = 16F)
- Known hardcode surface to classify: Verboo, glm-5.3-flash, mimo-v2.5-pro, Xioami, BUILDER_MODEL, DEFAULT_MODEL, MIMO_MODEL, --model/-m.
- T14/T15 real-builder runs pinned "Verboo Code/glm-5.3-flash" at the DRIVER layer (t14_builder_session.py), not inside Scaffolding proper — classification pending 16F audit.
+44
View File
@@ -0,0 +1,44 @@
# T16 Stage 16B — live continuation diagnostic (spans the whole tranche)
fresh_session = true
session = ses_-ffe5f5a82d0b5ffeLrGt5rMRv
tranche_start = 2026-09-15 (T16 mandate received at session open)
## Response-boundary ledger
| # | boundary | last completed | next eligible stage started automatically? | user intervention required? |
|---|----------|----------------|---------------------------------------------|------------------------------|
| 1 | session open (checkpoint rebuild from prior-session T16 mandate) | none | 16A started this session without user prompt | NO |
## Stage log
- 16A: STARTED 2026-09-15 (this session, unprompted continuation from mandate). COMPLETED ~15:45 local. Baseline + T15 maturity correction persisted (store/evidence/t16/16A_baseline.md + project MEMORY.md). Next stage (16C) selected and started immediately in the same assistant turn — no user intervention.
## Verdict so far
Running experiment. Required live proof: ≥4 substantial sequential stages completing and the next starting automatically without user intervention. A LIVE_CONTINUATION_FAILURE is recorded the moment this assistant reaches a normal response boundary while eligible authorized work remains.
## Stage log update (16:00Z)
- 16A: COMPLETED (persisted + readback done). No user intervention.
- 16B diagnostic: created and maintained continuously. No user intervention.
- 16C: COMPLETED (event-driven CI wait adapter implemented, 8 held-outs, workflows wired, daemon passthrough landed). NO user intervention — began immediately after 16A in the same assistant turn.
- 16D: IN PROGRESS — first live cycle exposed a real defect (runner user vs bridge data root; deploy failed fast+truthfully on 7953/7958 — no false green). Root-caused, shared root + readiness probe shipped (c707a81, pushed 16:02Z); latency proof pending the next cycle. This is itself a 16U datum: "wrong environment" was caught by production before a canary could — the readiness probe now added prevents recurrence.
- 16E: COMPLETED (one primitive: builder + gitea adapters share register/FIFO/match/idempotency/watchdog; identity selects events — 16E held-out).
- 16F/16G: COMPLETED (provider_context seam, PROVIDER_UNAVAILABLE class, 7 silent provider defaults removed, structural guard + 6 held-outs; committed 37deb60).
Response boundaries so far: ZERO user interventions. Continuation has been automatic at every stage boundary.
## Stage log update (16:35Z)
- 16D: COMPLETED — two real production cycles proved the watchdog/
deadline truthfulness path (deploy promoted from the deadline path,
zero false greens, sleep-30 floor gone); event-before-waiter root-
caused and fixed via durable replay (b51afea pushed 16:34Z).
- 16E: COMPLETED (one primitive, adapters by identity).
- 16F/16G: COMPLETED. 16I/J/K: COMPLETED. 16M: COMPLETED. 16N: COMPLETED
(docs/STRUCTURAL_MAP.md). 16O: COMPLETED (honest result — no step-count
win on tiny fixtures; qualitative advantages recorded). 16P: COMPLETED
(drift_resume.py + real-fixture held-outs).
- Next: 16Q (reconciliation), 16R (self-hosted change), 16U-V, 16W-Y, 16Z.
- USER INTERVENTIONS TO DATE: ZERO. Every stage transition has been
automatic within continuous assistant execution.
+44
View File
@@ -0,0 +1,44 @@
# T16 §16D — live CI/deploy latency proofs (2026-09-15)
## Cycle 1: c707a81 (fix commit) — watchdog/deadline-recovery path
- registered_at: 16:03:16Z (deploy job start)
- event_received_at: (none — ci.yml notify ran BEFORE deploy registered;
event-before-waiter ordering, the workflow-level sibling of 16Y #10)
- controller behavior: watchdog expiry → ci_alive() True (runs genuinely
non-terminal) → truthful re-arms; no false green at any point
- verdict_at: 16:18:16Z (mode deadline_pending → final authority read
success), wall_secs 900.92 — the deadline is a FAILURE DETECTOR; it
expired exactly once and the final one-shot authority read decided
- PROMOTED: c707a81 at 16:18:17Z (current moved; last-good → previous)
- Sleep-30 polling floor: GONE (old loop would have polled 30 times in
this window; new adapter slept in select(), zero poll queries — the
only Gitea reads were one-shot authority verdicts on expiry)
## Cycle 2: 4a06d9f (auto-bump 0.66.64) — true event path
- waiter registered while CI still in progress; process observed
sleeping in select() (S state, 700s elapsed, zero CPU) at inspection
- expected: notify-event-bridge POST on CI completion → mid-wait wake →
resume latency « 30s floor (proof line appended when the cycle closes)
## Event-before-waiter finding (new, honest)
The wake event cannot be delivered when CI finishes before the deploy
job registers its correlation. This ordering is inherent (deploy.yml
starts after checkout; ci.yml may finish earlier). The durable-bridge
recovery covers it: the waiter's watchdog + one-shot authority read.
A future improvement could register the correlation BEFORE CI completes
(e.g. a preceding workflow step) — deferred; watchdog recovery is
truthful and the deadline bound held exactly.
## Cycle 2 postmortem + replay fix (17:35Z)
Cycle 2 (4a06d9f) also ended deadline_pending at 901.0s: the completion
events DID reach the daemon (16:07:44/16:08:21, after registration at
16:07:06) but never resumed the waiter. ROOT CAUSE: on a serialized
single-runner machine the deploy job starts only after ci.yml's job
slot frees — the wake lands before/while the waiter is between
registration arms; the FIFO rendezvous is forward-only.
FIX: durable event replay — find_matching_event() scans the bridge's
event log at registration; a matching event short-circuits straight to
the authority read (event_replayed mode). 2 held-outs added (9 total).
NOTE: the replay fix lands on the NEXT cycle (it cannot prove itself on
the cycle that exposed it); cycles 1-2 remain valid proof of the
watchdog/deadline truthfulness path.
+54
View File
@@ -0,0 +1,54 @@
# T16 Stage 16M — Retroactive Rune inventory (2026-09-15)
Sources: two parallel read-only exploration sweeps + 16A/16C hands-on verification. Classification scale per mandate. This is the raw material for the 16N structural map.
## The ten canonical systems
| System | Modules | Canonical state | Classification | Notes |
|---|---|---|---|---|
| Spine | spine.py (695L) over structural_store.py (650L, zero Rune constants) | store/spine/current.json, snapshots byte-deterministic | HEALTHY | maturity ladder evidence-gated; CLI JSON-canonical |
| Research pipeline | scripts/research_task_tree.py (authority), research_stage_executor.py, research_event_relay.py | research/issue-N/ artifacts + Gitea labels (labels=projection) | HEALTHY | stage gates per artifact; coverage thinner than Spine |
| ADAPT/router | adaptive_router.py (625L, "canonical route authority ADAPT-2"), route_risk_flags.py | routes/route.jsonl append-only | HEALTHY | capability_broker/recall_router/warm_router/model_routing are SEPARATE routing domains — same word, different authority; DO NOT MERGE |
| Completion Truth | verification_layer.py, orchestration/conversation_state.py (TaskStateRepository over DurableEventStore), records.py | event-store log + atomic checkpoints | UNCLEAR_AUTHORITY / MISSING_SEAM | no enforced seam that a task cannot close COMPLETED while verifiers fail — highest-value gap found |
| Release/deploy | deploy_stage/deploy_promotion/deploy_qualify/create_release/auto_bump + workflows | deploy root + current/last-good + _build_identity.json | HEALTHY | monotonic promotion fail-closed; 16C event-driven wait now added |
| Provider layer | model_routing ("one authoritative router #29 P1-06"), model_connect, model_registry, provider_registry, work_manager, standardized_providers, openai_compat_transport | model_routing_profiles.json keyed (provider,model) | DUPLICATE_ROUTE / UNCLEAR_AUTHORITY | ≥5 selection entry points; the OPERATOR is the final decider, not any registry. 16F removed construction-side selection; provider-layer consolidation = 16Q candidate |
| W/R event bridge | event_wait.py + out-of-repo daemon.py/waitstore.py/verify.py | WR_ROOT waits/events/leases/resumes | HEALTHY / OPTIONAL_BUT_SHOULD_BE_DEFAULT | one primitive, builder+gitea adapters (16E proven); daemon lives OUT-OF-REPO with its own git snapshot — packaging/deployment debt |
| TUI/backend | rune_tui.py (3885L) ← rune_operator.py (2604L) + microkernel + orchestration | conversation/task repositories | HEALTHY | one-way direction enforced by canary_tui_drift; import via sys.path shim (fragile, keep test) |
| build_mode (legacy /build) | build_mode.py BuildManager + plugins/build_mode/phases | ~/.rune/work/builds + worktrees | LEGACY / DUPLICATE_ROUTE | superseded by builder_bridge + event-driven sessions (T14/T15); still wired → two ways to run a build; REPO_DIR="/mnt/workdrive/rune" stale constant |
| Canaries | 24 canary_*.py + canary_native.MODULES registry | none (stateless) | HEALTHY | native/full_system/orchestration_foundation partially overlap (layered, acceptable); no test enforces MODULES completeness |
## Auxiliary systems
| System | Classification | Notes |
|---|---|---|
| Scaffolding itself | HEALTHY | provider-blind since 16F (structural guard); phase machine governed |
| Maintenance machinery | UNCLEAR_AUTHORITY | three overlapping routes: scaffold maintenance_item entities, tui_backlog ledger, drift_ledger — no central module, NO tests |
| Task/intent state | DUPLICATE_ROUTE | task-tool session DB vs task_persist persisted.json vs memory_store.working vs orchestration event store — four task-ish stores |
| Henxels | HEALTHY | henxels.yaml + 191L checks; contract-tested |
| Plugin surfaces | MISSING_SEAM | phases exist as files; no generic loader; single hardcoded plugin |
| Learning/process records | DUPLICATE_ROUTE | memory_store vs graph_memory vs warm_neuron_store vs curator vs process_experience — no single authority doc |
| programme_continuation | MISSING_SEAM | run_programme() has NO production caller (proof-only, T15 §15M); night_mode LEGACY aspirational; cron_engine MISSING_VERIFICATION |
| maintenance.yml | LEGACY | canary-suite job loops shared/canary_*.py — real canaries live in src/rune/; the job likely no-ops |
## Duplicate routes / bypasses (16Q raw material, priority-ordered)
1. **task-state quadruplication** — 4 stores hold task-ish state; truth hierarchy unclear
2. **build_mode vs builder_bridge** — two build paths, legacy still wired
3. **provider selection ×5 entry points** — operator is de-facto authority
4. **T14 vs T15 builder drivers** — T15 is the event-driven successor; T14 parallel-runs
5. **maintenance ledgers ×3** — scaffold entities, backlog, drift ledger
6. **maintenance.yml canary glob wrong dir** — stale no-op job
7. **programme_continuation unwired** — the "continue problem" machinery exists but nothing calls it in production
8. **memory stores ×5** — overlapping recall surfaces
## Hard-coded dev paths in production code (16W targets)
- build_mode.REPO_DIR + 5 complexity_scorer defaults → /mnt/workdrive/rune (stale for harness)
- deploy_promotion.DEPLOY_ROOT → /mnt/workdrive/rune-code (fine: that IS the deploy root, but should be env-derived — it is)
- two canaries sys.path.insert(/home/nick/.config/opencode/scripts)
- memory/system_registry.json points at /mnt/workdrive/rune — STALE repo root
- event_wait FALLBACK_WR_ROOT → /home/nick/... (fallback only; shared root now env-set in deploy.yml)
## Honest overall
Rune's structural health is better than the mandate's tone suggests: Spine, release/deploy, ADAPT, W/R, TUI boundary, canaries, Henxels, and Scaffolding are all HEALTHY with real verification. The debt concentrates in: completion-truth enforcement seam, task-state multiplication, provider-layer entry-point spread, legacy build_mode still wired, and unwired programme continuation (the very continuation defect this tranche experiments on).
+45
View File
@@ -0,0 +1,45 @@
# T16 §16O — long-gap re-entry benchmark (2026-09-15)
Fixture: /mnt/workdrive/t16-reentry — Python invoice-ledger project, 2
stages (1 closed+verified, 2 blocked PROVIDER_UNAVAILABLE with recorded
next action), then POST-CHECKPOINT DRIFT injected: (a) VAT regression in
report.py breaking 2 tests, (b) unrecorded discount.py module, (c) 2
unrecorded commits, (d) committed __pycache__ + no .gitignore (unplanned
extras). Two fresh-identity subagents, no transcript, no briefing beyond
method.
## Measured
| Metric | PATH A cold rediscovery | PATH B scaffold-assisted |
|---|---|---|
| Files opened | 8 | 9 |
| Commands run | 4 | 6 |
| Explore/verify cycles | ~5 | ~8 |
| Drifts found | 4/4 planted + pycache + gitignore | 4/4 planted (+pytest_cache receipt); did not enumerate pycache/gitignore |
| False assumptions corrected | 2 (pytest path; comments-might-be-stale) | 3 (pytest path; assumed green still held; assumed repo at checkpoint commit) |
| Drift CLASSIFICATION | none (flat findings) | EXPECTED/MATERIAL applied correctly, per-claim CONFIRMED/DRIFTED |
| Next action quality | correct, self-derived | correct + explicitly adjusted from recorded next action |
| Confidence calibration | honest (blocked=medium) | honest (drift completeness medium-high) |
## Honest verdict
1. On a TINY fixture, cold rediscovery is CHEAP and nearly as accurate —
the scaffold did not save significant effort at this scale. No
fabricated win: file/command counts are comparable (8 vs 9, 4 vs 6).
2. The scaffold's measurable advantages are QUALITATIVE and scale with
project size: (a) structured per-claim reconciliation instead of flat
discovery, (b) recorded next action + blocker evidence that survives
model turnover, (c) drift CLASSES enabling the 16P material/expected
gating decision, (d) intent (non_goals: "no hardcoded FX rates")
that cold inspection cannot infer.
3. Both paths needed a test run to catch the regression — checkpoint
claims NEVER substitute for reality (the 16P principle, proven by
both paths independently).
4. Path A's unplanned extras (pycache/gitignore) show cold discovery
also finds things outside the scaffold's frame — the scaffold is a
starting point, not a boundary.
Recommendation: adopt scaffold-assisted re-entry as the DEFAULT for
multi-session projects (16P flow), keep cold verification of claims as
a mandatory reconciliation step. Re-benchmark on a realistically-sized
repo before claiming step-efficiency wins (OVERFLOW 3 candidate).
+91
View File
@@ -0,0 +1,91 @@
# T16 §16S/§16T/§16U/§16V/§16X/§16Y — consolidated findings (2026-09-15)
## §16S — live programme continuation proof
Evidence: 16B_continuity_log.md. Stages 16A→16B→16C→16D→16E→16F/16G→
16I/J/K→16M→16N→16O→16P→16Q/16R executed in ONE continuous assistant
session with ZERO user "continue" interventions; every stage transition
was automatic. ≥4 substantial sequential stages completed with the next
starting automatically (requirement exceeded: 12+ stages).
**LIVE-PROVEN (bounded, this session).** Caveat recorded honestly: the
session is long; a context-boundary failure would still be possible in
even longer programmes — the mechanism (stage_guards + programme_
continuation canary) is what carried continuation, now live-proven.
## §16T — session-freshness finding
Previous long session (T15): user typed "continue" repeatedly DESPITE
the mechanism being test-proven. Fresh T16 session: zero interventions
across 12+ stages. Verdict: **MIXED** — old-session degradation is
SUPPORTED as the parsimonious explanation (same mechanism, same model
class, different session freshness → different behavior), but n=1 and
the prior session also carried much larger context; a systemic harness
contribution cannot be excluded. Not called corruption; no evidence of
it.
## §16U — interface-first error prevention (moved-left repairs)
- pyright ambient-venv skew (117⇄124): root-caused; ratchet now
neutralizes VIRTUAL_ENV/PATH before invoking pyright — environment
mismatch is now detected-at-setup instead of post-hoc.
- deploy readiness probe: RUNE_WR_ROOT writability checked BEFORE the
wait (the PermissionError class caught twice in production is now a
clear setup-time error).
- provider PROVIDER_UNRESOLVED: missing provider context fails loudly
before any construction run instead of silently using a default.
- lesson-class recorded: "wrong environment" failures were caught by
production twice before a canary existed — readiness-before-action is
the general fix shape.
## §16V — canary + Henxels coverage vs Rune boundaries
- Henxels rc=0 all session; umbrella manifest gate caught every new
module (provider_context, construction_projection, construction_
commands, drift_resume) — the completeness statement works.
- Found + fixed: maintenance.yml canary glob pointed at shared/ (silent
no-op) — 16Q/16R.
- Gaps recorded (16M): no MODULES-completeness test for canary registry;
maintenance machinery lacks tests; daemon out-of-repo has no CI cover.
- Not duplicated: Henxels = structural statements; canaries = import/
exercise; tests = behavior. Boundary held.
## §16X — backlogs (backend → future projection/control needs)
Construction projection (16I) already exposes: project, contract, phase,
stage, waiting_state, correlation, builder, blockers, verification,
next_action. TUI/Substrate control needs mapped to 16J commands:
START/RESUME/PROVIDE_APPROVAL/PROVIDE_CONTEXT + REQUEST_STATE.
Drift display: drift_resume classes are the vocabulary.
DEBT (recorded, not built): PAUSE needs a lifecycle phase if wanted;
goal-level (why-is-this-active) linkage belongs to future Substrate.
## §16Y — adversarial matrix (30 cases) — where each is held
1 old-context absent → 16O both paths; 2 scaffold reconstructs → 16O Path B
+ 16P; 3 provider unavailable → 16G-C; 4 no fallback → 16G-D (policy 0);
5 real status preserved → 16G-C verbatim; 6 duplicate event → 15E tests;
7 stale event → 15E tests; 8 missing webhook → 16D cycles 1-2 (deadline
recovery, promoted truthfully); 9 duplicate webhook → 15E double-resume
tests; 10 event-before-waiter → REPLAY FIX (b51afea) + live finding;
11 controller restart → 15E restart tests + resume_or_recover; 12 mock
invalid command → 16K refusal tests; 13 direct-state semantics → 16K
inexpressible test; 14 stale scaffold → 16P VAT regression blocks; 15
months-later drift → 16O/16P; 16 incomplete work after gap → 16O stage-2;
17 no-activity≠completion → watchdog/PROCESS_LOST semantics (15D/Q);
18 duplicate route → 16M inventory + 16Q fix; 19-21 TUI request classes →
classify_mode (T10-15 suites); 22 manual provider switch → 16G-G
byte-identical scaffold; 23 approval gate → LEGAL_TRANSITIONS + 16J
user-provenance refusal; 24 Henxels defect → 16Q canary glob (found+fixed
by inventory); 25 false-green → stage_guards verify_test_truth (15F-I)
+ deploy refusals observed live; 26 completion replay → match only on
WAITING_EXTERNAL; 27 stale publisher → T14B authority re-read; 28 CI
completion event → 16C suite; 29 stage-completion-≠-programme → 15M
canary + this session's behavior; 30 final completion → 16Z report
condition (this document; programme-complete only when the release
cycle converges).
Gaps honestly recorded: adversarial cases 22-27 rely on T14/T15-era
suites (not re-run live this tranche); cases 1-2 depend on subagent
honesty (both paths cross-checked against planted ground truth).
## §16Z — final release convergence (17:55Z)
Deployed current -> 986b450 (T16 16O/P commit) at 17:54Z, promoted by
the event-driven pipeline itself; releases 0.66.63→0.66.66 auto-published
and deployed through T16 commits; zero manual release refresh. Run 7991
(2a4f9c8) in flight at report time; durable waits remain parked and the
pipeline drains autonomously. Suite 1884 green; ratchets PASS; Henxels
rc=0 at last full gate run.
+35
View File
@@ -0,0 +1,35 @@
# T17 §17F/§17G/§17H audits (2026-09-15, from parallel read-only sweeps)
## 17F — task-state surfaces
1. TaskStateRepository (orchestration/conversation_state.py:111) — CANONICAL
(durable JSONL + hot checkpoint, legal transitions, wired through composition)
2. RuneState.tasks in operator.json (rune_operator.py:377) — DUPLICATE
(double-written on every send_task; the one true duplicate authority pair)
3. task_persist.py persisted.json — LEGACY/QUEUE (TUI startup load is dead code:
calls load() which does not exist, silently swallowed)
4. memory_store working table — CACHE (single consumer: its own CLI)
5. auto_tasks.db — QUEUE (self-contained, no bridge)
6. work_manager batch meta — QUEUE (task-adjacent, TUI /work only)
CONSOLIDATION: #1 authority; migrate #2 writers (send_task → repo only,
derive get_status/list_tasks), fix #3 dead load, keep #4-6 as bounded caches/queues.
## 17G — build_mode
Unique: VCS worktree isolation (create/merge/delete) — NOT in Scaffolding;
phase ledger/resume. NOT unique: DAG batching (work_manager), correction
loop (counter only), complexity assess (placeholder). Plugin BuildStateMachine
= second parallel orchestrator (DUPLICATE, only assess.py referenced).
T15 classification lives in operator hook path; TUI /build start bypasses
it and mints unclassified state.
SMALLEST MIGRATION: _cmd_build start → classify first (same classify_mode),
refuse NOT_CONSTRUCTION/UNKNOWN with probes, activate contract, then delegate
to BuildManager as bounded adapter (worktree + resume ledger). Delete plugin
duplicate.
## 17H — W/R daemon ownership (verdict)
INTENTIONAL SHARED INFRASTRUCTURE with packaging debt. The bridge serves
multiple consumers (deploy waiter as runner user, daemon as nick, MiMo
sessions). Smallest clean migration: keep out-of-repo deployment BUT (a)
versioned snapshot (git tag per release), (b) RUNE_WR_CODE discovery
already implemented in event_wait, (c) health endpoint exists. Full
in-repo packaging DEFERRED — the shared-root contract (0644/0666) is the
load-bearing fix and it is now guarded by tests.
@@ -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.
+43
View File
@@ -0,0 +1,43 @@
# 20U — V1 requirement classification after retrofit (2026-09-16)
| Requirement | Status | Evidence |
|---|---|---|
| construction kernel | LIVE_PROVEN | T14-T19 production cycles; 19A fail-closed |
| contextual intent | TEST_PROVEN + LIVE (17R, 19R) | 22+6 held-outs; two live conversational proofs |
| Research Core integration | TEST_PROVEN | build_basis qualified ×2 real bases; depth-from-route |
| structural Scaffold | TEST_PROVEN + LIVE (18O, 19R) | self-scaffold consistency-enforced; 107-entity Rune model |
| bounded scope/impact | TEST_PROVEN | closures + previews + deltas |
| new-build mode | TEST_PROVEN | classify + bootstrap; 17Q-1/14 |
| retrofit mode | LIVE_PROVEN | T20: 107 entities via surgical workflow |
| future-upgrade mode | LIVE_PROVEN | 18P 11-step scaffold-driven upgrade |
| Capability Ledgers | TEST_PROVEN + LIVE | 19R packet bound 1 file; risk_notes added from benchmark |
| Project Registry | TEST_PROVEN | boundaries proven |
| Behaviour Validation Ledger | TEST_PROVEN | generation history; selective invalidation; no manufactured USER_CONFIRMED |
| quicksand protection | TEST_PROVEN + LIVE (20R) | real-entity invalidation + preservation |
| /testing | TEST_PROVEN + LIVE (19R) | backend + command; dynamic queue |
| Observation→diagnosis | TEST_PROVEN | closure gate (retest AND confirm) |
| drift | TEST_PROVEN | 18H entity checks; 2 real fixes by matrix |
| schema migration consent | TEST_PROVEN | ConsentRequired structurally enforced |
| workspace lifecycle | TEST_PROVEN | order-enforced; generations not mutations |
| forge abstraction | TEST_PROVEN | Gitea proven every push; fail-loud fix |
| self-update isolation | PARTIAL | worktree-per-contract proven; live-runtime A/B generation swap NOT yet exercised |
| Rune-wide retrofit | PARTIAL | 37% file coverage — honest, not complete |
| Spine modernization | PARTIAL | boundary defined, 8 subsystems linked; no schema migration |
| structural graph | TEST_PROVEN | rune graph 107 nodes/171 edges, read-only |
| substantial transformation proof | TEST_PROVEN | 20E retrofit + 19R /testing command |
| retirement/deletion proof | PARTIAL | plugin deletion (T17) only; backlog classified, not executed |
| external-project adoption | ABSENT | no external project scaffolded yet |
| provider-independent operation | TEST_PROVEN | structural guard; ambient context |
| operational usefulness | TEST_PROVEN + honest limits | 20Q: faster on narrow questions; breadth still favors cold sweeps; coarse edges recorded |
## VERDICT: NEAR_V1_ACCEPTANCE_READY
Missing proof classes (exactly the mandate's predicted list):
- substantial legacy transformation (backlog classified, not executed)
- governed retirement/deletion beyond one plugin
- full self-update/live-runtime proof (A/B generation swap)
- external-project adoption
The retrofit succeeded; the foundation is proven at Rune scale (37%
coverage is the honest number). What remains is proof-class breadth,
not missing architecture.
@@ -0,0 +1,281 @@
digraph structural {
rankdir=LR;
node [shape=box, style="rounded,filled", fillcolor="#eef"];
"component:auto_bump_pr" [label="auto_bump_pr (component)"];
"component:create_release" [label="create_release (component)"];
"component:deploy_promotion" [label="deploy_promotion (component)"];
"component:deploy_qualify" [label="deploy_qualify (component)"];
"component:deploy_stage" [label="deploy_stage (component)"];
"component:gitea_auth" [label="gitea_auth (component)"];
"component:semver_bump" [label="semver_bump (component)"];
"component:version_convergence" [label="version_convergence (component)"];
"component:adaptive_router" [label="adaptive_router (component)"];
"component:attention_capsule" [label="attention_capsule (component)"];
"component:behaviour_ledger" [label="behaviour_ledger (component)"];
"component:build_mode_adapter" [label="build_mode_adapter (component)"];
"component:builder_bridge" [label="builder_bridge (component)"];
"component:canary_native" [label="canary_native (component)"];
"component:capability_ledger" [label="capability_ledger (component)"];
"component:claim_audit" [label="claim_audit (component)"];
"component:construction_interface" [label="construction_interface (component)"];
"component:construction_mode" [label="construction_mode (component)"];
"component:context_broker" [label="context_broker (component)"];
"component:curator" [label="curator (component)"];
"component:drift_ledger" [label="drift_ledger (component)"];
"component:drift_observability" [label="drift_observability (component)"];
"component:drift_resume" [label="drift_resume (component)"];
"component:event_wait" [label="event_wait (component)"];
"component:failure_recovery" [label="failure_recovery (component)"];
"component:graph_memory" [label="graph_memory (component)"];
"component:identity_anchors" [label="identity_anchors (component)"];
"component:iteration" [label="iteration (component)"];
"component:local_tool_runtime" [label="local_tool_runtime (component)"];
"component:memory_env" [label="memory_env (component)"];
"component:memory_selector" [label="memory_selector (component)"];
"component:memory_store" [label="memory_store (component)"];
"component:microkernel" [label="microkernel (component)"];
"component:model_connect" [label="model_connect (component)"];
"component:model_registry" [label="model_registry (component)"];
"component:model_routing" [label="model_routing (component)"];
"component:night_mode" [label="night_mode (component)"];
"component:openai_compat_transport" [label="openai_compat_transport (component)"];
"component:atomic" [label="atomic (component)"];
"component:conversation_state" [label="conversation_state (component)"];
"component:event_store" [label="event_store (component)"];
"component:records" [label="records (component)"];
"component:programme_continuation" [label="programme_continuation (component)"];
"component:project_registry" [label="project_registry (component)"];
"component:provider_registry" [label="provider_registry (component)"];
"component:query_memory" [label="query_memory (component)"];
"component:recall_router" [label="recall_router (component)"];
"component:recall_uplift" [label="recall_uplift (component)"];
"component:route_risk_flags" [label="route_risk_flags (component)"];
"component:rune_operator" [label="rune_operator (component)"];
"component:rune_tui" [label="rune_tui (component)"];
"component:scaffold" [label="scaffold (component)"];
"component:standardized_providers" [label="standardized_providers (component)"];
"component:subconscious_pass" [label="subconscious_pass (component)"];
"component:subsystem_health" [label="subsystem_health (component)"];
"component:task_gate" [label="task_gate (component)"];
"component:task_persist" [label="task_persist (component)"];
"component:testing_mode" [label="testing_mode (component)"];
"component:tool_executor" [label="tool_executor (component)"];
"component:tool_protocol" [label="tool_protocol (component)"];
"component:tui_backlog" [label="tui_backlog (component)"];
"component:warm_neuron_store" [label="warm_neuron_store (component)"];
"component:warm_router" [label="warm_router (component)"];
"component:work_manager" [label="work_manager (component)"];
"component:conversation_state_probe" [label="conversation_state_probe (component)"];
"component:growth_simulator" [label="growth_simulator (component)"];
"component:persona" [label="persona (component)"];
"component:voice_synthesis" [label="voice_synthesis (component)"];
"deployment_surface:rune-code-current" [label="rune-code-current (deployment_surface)"];
"domain:client-presentation" [label="client-presentation (domain)"];
"domain:construction-governance" [label="construction-governance (domain)"];
"domain:execution-runtime" [label="execution-runtime (domain)"];
"domain:inference-providers" [label="inference-providers (domain)"];
"domain:memory-identity" [label="memory-identity (domain)"];
"domain:orchestration-execution" [label="orchestration-execution (domain)"];
"domain:perception-context" [label="perception-context (domain)"];
"domain:release-delivery" [label="release-delivery (domain)"];
"domain:state-resilience" [label="state-resilience (domain)"];
"domain:validation" [label="validation (domain)"];
"domain:validation-health" [label="validation-health (domain)"];
"environment:production" [label="production (environment)"];
"interface:operator-api" [label="operator-api (interface)"];
"state_store:scaffold-store" [label="scaffold-store (state_store)"];
"state_store:spine-store" [label="spine-store (state_store)"];
"state_store:wr-bridge" [label="wr-bridge (state_store)"];
"subsystem:canaries" [label="canaries (subsystem)"];
"subsystem:ci-verification" [label="ci-verification (subsystem)"];
"subsystem:context" [label="context (subsystem)"];
"subsystem:deploy" [label="deploy (subsystem)"];
"subsystem:event-resilience" [label="event-resilience (subsystem)"];
"subsystem:growth" [label="growth (subsystem)"];
"subsystem:health" [label="health (subsystem)"];
"subsystem:identity" [label="identity (subsystem)"];
"subsystem:memory" [label="memory (subsystem)"];
"subsystem:operator" [label="operator (subsystem)"];
"subsystem:orchestration" [label="orchestration (subsystem)"];
"subsystem:recall" [label="recall (subsystem)"];
"subsystem:registry" [label="registry (subsystem)"];
"subsystem:routing" [label="routing (subsystem)"];
"subsystem:task-state" [label="task-state (subsystem)"];
"subsystem:tool-runtime" [label="tool-runtime (subsystem)"];
"subsystem:transports" [label="transports (subsystem)"];
"subsystem:tui" [label="tui (subsystem)"];
"subsystem:work-dispatch" [label="work-dispatch (subsystem)"];
"test_suite:rune-tests" [label="rune-tests (test_suite)"];
"test_suite:tui-tests" [label="tui-tests (test_suite)"];
"project:rune" [label="rune (project)"];
"component:build_mode_adapter" -> "component:scaffold" [label="legacy_of"];
"component:event_wait" -> "state_store:wr-bridge" [label="owns"];
"component:scaffold" -> "state_store:scaffold-store" [label="owns"];
"component:event_wait" -> "component:programme_continuation" [label="connects_to"];
"component:rune_tui" -> "interface:builder-boundary" [label="consumes"];
"component:rune_tui" -> "test_suite:tui-tests" [label="verified_by"];
"component:programme_continuation" -> "component:construction_interface" [label="depends_on"];
"component:programme_continuation" -> "component:event_wait" [label="depends_on"];
"component:rune_operator" -> "component:adaptive_router" [label="depends_on"];
"component:rune_operator" -> "component:construction_mode" [label="depends_on"];
"component:scaffold" -> "component:construction_mode" [label="depends_on"];
"component:rune_tui" -> "component:rune_operator" [label="depends_on"];
"component:adaptive_router" -> "interface:builder-boundary" [label="provides"];
"component:adaptive_router" -> "test_suite:rune-tests" [label="verified_by"];
"component:atomic" -> "test_suite:rune-tests" [label="verified_by"];
"component:attention_capsule" -> "test_suite:rune-tests" [label="verified_by"];
"component:auto_bump_pr" -> "test_suite:rune-tests" [label="verified_by"];
"component:behaviour_ledger" -> "test_suite:rune-tests" [label="verified_by"];
"component:builder_bridge" -> "test_suite:rune-tests" [label="verified_by"];
"component:canary_native" -> "test_suite:rune-tests" [label="verified_by"];
"component:capability_ledger" -> "test_suite:rune-tests" [label="verified_by"];
"component:claim_audit" -> "test_suite:rune-tests" [label="verified_by"];
"component:context_broker" -> "test_suite:rune-tests" [label="verified_by"];
"component:conversation_state" -> "test_suite:rune-tests" [label="verified_by"];
"component:create_release" -> "test_suite:rune-tests" [label="verified_by"];
"component:curator" -> "test_suite:rune-tests" [label="verified_by"];
"component:deploy_promotion" -> "test_suite:rune-tests" [label="verified_by"];
"component:deploy_qualify" -> "test_suite:rune-tests" [label="verified_by"];
"component:deploy_stage" -> "test_suite:rune-tests" [label="verified_by"];
"component:drift_ledger" -> "test_suite:rune-tests" [label="verified_by"];
"component:drift_observability" -> "test_suite:rune-tests" [label="verified_by"];
"component:drift_resume" -> "test_suite:rune-tests" [label="verified_by"];
"component:event_store" -> "test_suite:rune-tests" [label="verified_by"];
"component:failure_recovery" -> "test_suite:rune-tests" [label="verified_by"];
"component:gitea_auth" -> "test_suite:rune-tests" [label="verified_by"];
"component:graph_memory" -> "test_suite:rune-tests" [label="verified_by"];
"component:identity_anchors" -> "test_suite:rune-tests" [label="verified_by"];
"component:iteration" -> "test_suite:rune-tests" [label="verified_by"];
"component:local_tool_runtime" -> "test_suite:rune-tests" [label="verified_by"];
"component:memory_env" -> "test_suite:rune-tests" [label="verified_by"];
"component:memory_selector" -> "test_suite:rune-tests" [label="verified_by"];
"component:memory_store" -> "test_suite:rune-tests" [label="verified_by"];
"component:microkernel" -> "test_suite:rune-tests" [label="verified_by"];
"component:model_connect" -> "test_suite:rune-tests" [label="verified_by"];
"component:model_registry" -> "test_suite:rune-tests" [label="verified_by"];
"component:model_routing" -> "test_suite:rune-tests" [label="verified_by"];
"component:night_mode" -> "test_suite:rune-tests" [label="verified_by"];
"component:openai_compat_transport" -> "test_suite:rune-tests" [label="verified_by"];
"component:project_registry" -> "test_suite:rune-tests" [label="verified_by"];
"component:provider_registry" -> "test_suite:rune-tests" [label="verified_by"];
"component:query_memory" -> "test_suite:rune-tests" [label="verified_by"];
"component:recall_router" -> "test_suite:rune-tests" [label="verified_by"];
"component:recall_uplift" -> "test_suite:rune-tests" [label="verified_by"];
"component:records" -> "test_suite:rune-tests" [label="verified_by"];
"component:route_risk_flags" -> "test_suite:rune-tests" [label="verified_by"];
"component:rune_operator" -> "test_suite:rune-tests" [label="verified_by"];
"component:semver_bump" -> "test_suite:rune-tests" [label="verified_by"];
"component:standardized_providers" -> "test_suite:rune-tests" [label="verified_by"];
"component:subconscious_pass" -> "test_suite:rune-tests" [label="verified_by"];
"component:subsystem_health" -> "test_suite:rune-tests" [label="verified_by"];
"component:task_gate" -> "test_suite:rune-tests" [label="verified_by"];
"component:task_persist" -> "test_suite:rune-tests" [label="verified_by"];
"component:testing_mode" -> "test_suite:rune-tests" [label="verified_by"];
"component:tool_executor" -> "test_suite:rune-tests" [label="verified_by"];
"component:tool_protocol" -> "test_suite:rune-tests" [label="verified_by"];
"component:version_convergence" -> "test_suite:rune-tests" [label="verified_by"];
"component:warm_neuron_store" -> "test_suite:rune-tests" [label="verified_by"];
"component:warm_router" -> "test_suite:rune-tests" [label="verified_by"];
"component:work_manager" -> "test_suite:rune-tests" [label="verified_by"];
"component:adaptive_router" -> "subsystem:routing" [label="belongs_to"];
"component:atomic" -> "subsystem:orchestration" [label="belongs_to"];
"component:attention_capsule" -> "subsystem:context" [label="belongs_to"];
"component:auto_bump_pr" -> "subsystem:deploy" [label="belongs_to"];
"component:behaviour_ledger" -> "subsystem:registry" [label="belongs_to"];
"component:builder_bridge" -> "subsystem:work-dispatch" [label="belongs_to"];
"component:canary_native" -> "subsystem:canaries" [label="belongs_to"];
"component:capability_ledger" -> "subsystem:registry" [label="belongs_to"];
"component:claim_audit" -> "subsystem:growth" [label="belongs_to"];
"component:context_broker" -> "subsystem:context" [label="belongs_to"];
"component:conversation_state" -> "subsystem:orchestration" [label="belongs_to"];
"component:conversation_state_probe" -> "subsystem:context" [label="belongs_to"];
"component:create_release" -> "subsystem:deploy" [label="belongs_to"];
"component:curator" -> "subsystem:memory" [label="belongs_to"];
"component:deploy_promotion" -> "subsystem:deploy" [label="belongs_to"];
"component:deploy_qualify" -> "subsystem:deploy" [label="belongs_to"];
"component:deploy_stage" -> "subsystem:deploy" [label="belongs_to"];
"component:drift_ledger" -> "subsystem:identity" [label="belongs_to"];
"component:drift_observability" -> "subsystem:identity" [label="belongs_to"];
"component:drift_resume" -> "subsystem:event-resilience" [label="belongs_to"];
"component:event_store" -> "subsystem:orchestration" [label="belongs_to"];
"component:failure_recovery" -> "subsystem:event-resilience" [label="belongs_to"];
"component:gitea_auth" -> "subsystem:ci-verification" [label="belongs_to"];
"component:graph_memory" -> "subsystem:memory" [label="belongs_to"];
"component:growth_simulator" -> "subsystem:growth" [label="belongs_to"];
"component:identity_anchors" -> "subsystem:identity" [label="belongs_to"];
"component:iteration" -> "subsystem:registry" [label="belongs_to"];
"component:local_tool_runtime" -> "subsystem:tool-runtime" [label="belongs_to"];
"component:memory_env" -> "subsystem:memory" [label="belongs_to"];
"component:memory_selector" -> "subsystem:memory" [label="belongs_to"];
"component:memory_store" -> "subsystem:memory" [label="belongs_to"];
"component:microkernel" -> "subsystem:operator" [label="belongs_to"];
"component:model_connect" -> "subsystem:transports" [label="belongs_to"];
"component:model_registry" -> "subsystem:transports" [label="belongs_to"];
"component:model_routing" -> "subsystem:routing" [label="belongs_to"];
"component:night_mode" -> "subsystem:growth" [label="belongs_to"];
"component:openai_compat_transport" -> "subsystem:transports" [label="belongs_to"];
"component:persona" -> "subsystem:identity" [label="belongs_to"];
"component:project_registry" -> "subsystem:registry" [label="belongs_to"];
"component:provider_registry" -> "subsystem:transports" [label="belongs_to"];
"component:query_memory" -> "subsystem:growth" [label="belongs_to"];
"component:recall_router" -> "subsystem:recall" [label="belongs_to"];
"component:recall_uplift" -> "subsystem:recall" [label="belongs_to"];
"component:records" -> "subsystem:orchestration" [label="belongs_to"];
"component:route_risk_flags" -> "subsystem:routing" [label="belongs_to"];
"component:rune_operator" -> "subsystem:operator" [label="belongs_to"];
"component:rune_tui" -> "subsystem:tui" [label="belongs_to"];
"component:semver_bump" -> "subsystem:deploy" [label="belongs_to"];
"component:standardized_providers" -> "subsystem:transports" [label="belongs_to"];
"component:subconscious_pass" -> "subsystem:growth" [label="belongs_to"];
"component:subsystem_health" -> "subsystem:health" [label="belongs_to"];
"component:task_gate" -> "subsystem:operator" [label="belongs_to"];
"component:task_persist" -> "subsystem:operator" [label="belongs_to"];
"component:testing_mode" -> "subsystem:registry" [label="belongs_to"];
"component:tool_executor" -> "subsystem:tool-runtime" [label="belongs_to"];
"component:tool_protocol" -> "subsystem:tool-runtime" [label="belongs_to"];
"component:tui_backlog" -> "subsystem:tui" [label="belongs_to"];
"component:version_convergence" -> "subsystem:deploy" [label="belongs_to"];
"component:voice_synthesis" -> "subsystem:identity" [label="belongs_to"];
"component:warm_neuron_store" -> "subsystem:memory" [label="belongs_to"];
"component:warm_router" -> "subsystem:recall" [label="belongs_to"];
"component:work_manager" -> "subsystem:work-dispatch" [label="belongs_to"];
"deployment_surface:rune-code-current" -> "environment:production" [label="connects_to"];
"deployment_surface:rune-code-current" -> "project:rune" [label="belongs_to"];
"domain:client-presentation" -> "project:rune" [label="belongs_to"];
"domain:construction-governance" -> "project:rune" [label="belongs_to"];
"domain:execution-runtime" -> "project:rune" [label="belongs_to"];
"domain:inference-providers" -> "project:rune" [label="belongs_to"];
"domain:memory-identity" -> "project:rune" [label="belongs_to"];
"domain:orchestration-execution" -> "project:rune" [label="belongs_to"];
"domain:perception-context" -> "project:rune" [label="belongs_to"];
"domain:release-delivery" -> "project:rune" [label="belongs_to"];
"domain:state-resilience" -> "project:rune" [label="belongs_to"];
"domain:validation" -> "project:rune" [label="belongs_to"];
"domain:validation-health" -> "project:rune" [label="belongs_to"];
"environment:production" -> "project:rune" [label="belongs_to"];
"interface:operator-api" -> "subsystem:tui" [label="belongs_to"];
"state_store:scaffold-store" -> "project:rune" [label="belongs_to"];
"state_store:spine-store" -> "project:rune" [label="belongs_to"];
"state_store:wr-bridge" -> "project:rune" [label="belongs_to"];
"subsystem:canaries" -> "domain:validation-health" [label="belongs_to"];
"subsystem:ci-verification" -> "domain:release-delivery" [label="belongs_to"];
"subsystem:context" -> "domain:perception-context" [label="belongs_to"];
"subsystem:deploy" -> "domain:release-delivery" [label="belongs_to"];
"subsystem:event-resilience" -> "domain:state-resilience" [label="belongs_to"];
"subsystem:growth" -> "domain:memory-identity" [label="belongs_to"];
"subsystem:health" -> "domain:validation-health" [label="belongs_to"];
"subsystem:identity" -> "domain:memory-identity" [label="belongs_to"];
"subsystem:memory" -> "domain:memory-identity" [label="belongs_to"];
"subsystem:operator" -> "domain:orchestration-execution" [label="belongs_to"];
"subsystem:orchestration" -> "domain:orchestration-execution" [label="belongs_to"];
"subsystem:recall" -> "domain:perception-context" [label="belongs_to"];
"subsystem:registry" -> "domain:state-resilience" [label="belongs_to"];
"subsystem:routing" -> "domain:inference-providers" [label="belongs_to"];
"subsystem:task-state" -> "domain:state-resilience" [label="belongs_to"];
"subsystem:tool-runtime" -> "domain:orchestration-execution" [label="belongs_to"];
"subsystem:transports" -> "domain:inference-providers" [label="belongs_to"];
"subsystem:tui" -> "domain:client-presentation" [label="belongs_to"];
"subsystem:work-dispatch" -> "domain:inference-providers" [label="belongs_to"];
"test_suite:rune-tests" -> "project:rune" [label="belongs_to"];
"test_suite:tui-tests" -> "project:rune" [label="belongs_to"];
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,276 @@
graph LR
n3["auto_bump_pr (component)"]
n15["create_release (component)"]
n17["deploy_promotion (component)"]
n18["deploy_qualify (component)"]
n19["deploy_stage (component)"]
n26["gitea_auth (component)"]
n53["semver_bump (component)"]
n63["version_convergence (component)"]
n0["adaptive_router (component)"]
n2["attention_capsule (component)"]
n4["behaviour_ledger (component)"]
n5["build_mode_adapter (component)"]
n6["builder_bridge (component)"]
n7["canary_native (component)"]
n8["capability_ledger (component)"]
n9["claim_audit (component)"]
n10["construction_interface (component)"]
n11["construction_mode (component)"]
n12["context_broker (component)"]
n16["curator (component)"]
n20["drift_ledger (component)"]
n21["drift_observability (component)"]
n22["drift_resume (component)"]
n24["event_wait (component)"]
n25["failure_recovery (component)"]
n27["graph_memory (component)"]
n29["identity_anchors (component)"]
n30["iteration (component)"]
n31["local_tool_runtime (component)"]
n32["memory_env (component)"]
n33["memory_selector (component)"]
n34["memory_store (component)"]
n35["microkernel (component)"]
n36["model_connect (component)"]
n37["model_registry (component)"]
n38["model_routing (component)"]
n39["night_mode (component)"]
n40["openai_compat_transport (component)"]
n1["atomic (component)"]
n13["conversation_state (component)"]
n23["event_store (component)"]
n48["records (component)"]
n42["programme_continuation (component)"]
n43["project_registry (component)"]
n44["provider_registry (component)"]
n45["query_memory (component)"]
n46["recall_router (component)"]
n47["recall_uplift (component)"]
n49["route_risk_flags (component)"]
n50["rune_operator (component)"]
n51["rune_tui (component)"]
n52["scaffold (component)"]
n54["standardized_providers (component)"]
n55["subconscious_pass (component)"]
n56["subsystem_health (component)"]
n57["task_gate (component)"]
n58["task_persist (component)"]
n59["testing_mode (component)"]
n60["tool_executor (component)"]
n61["tool_protocol (component)"]
n62["tui_backlog (component)"]
n65["warm_neuron_store (component)"]
n66["warm_router (component)"]
n67["work_manager (component)"]
n14["conversation_state_probe (component)"]
n28["growth_simulator (component)"]
n41["persona (component)"]
n64["voice_synthesis (component)"]
n68["rune-code-current (deployment_surface)"]
n69["client-presentation (domain)"]
n70["construction-governance (domain)"]
n71["execution-runtime (domain)"]
n72["inference-providers (domain)"]
n73["memory-identity (domain)"]
n74["orchestration-execution (domain)"]
n75["perception-context (domain)"]
n76["release-delivery (domain)"]
n77["state-resilience (domain)"]
n78["validation (domain)"]
n79["validation-health (domain)"]
n80["production (environment)"]
n81["operator-api (interface)"]
n83["scaffold-store (state_store)"]
n84["spine-store (state_store)"]
n85["wr-bridge (state_store)"]
n86["canaries (subsystem)"]
n87["ci-verification (subsystem)"]
n88["context (subsystem)"]
n89["deploy (subsystem)"]
n90["event-resilience (subsystem)"]
n91["growth (subsystem)"]
n92["health (subsystem)"]
n93["identity (subsystem)"]
n94["memory (subsystem)"]
n95["operator (subsystem)"]
n96["orchestration (subsystem)"]
n97["recall (subsystem)"]
n98["registry (subsystem)"]
n99["routing (subsystem)"]
n100["task-state (subsystem)"]
n101["tool-runtime (subsystem)"]
n102["transports (subsystem)"]
n103["tui (subsystem)"]
n104["work-dispatch (subsystem)"]
n105["rune-tests (test_suite)"]
n106["tui-tests (test_suite)"]
n82["rune (project)"]
n5 -->|legacy_of| n52
n24 -->|owns| n85
n52 -->|owns| n83
n24 -->|connects_to| n42
n51 -->|verified_by| n106
n42 -->|depends_on| n10
n42 -->|depends_on| n24
n50 -->|depends_on| n0
n50 -->|depends_on| n11
n52 -->|depends_on| n11
n51 -->|depends_on| n50
n0 -->|verified_by| n105
n1 -->|verified_by| n105
n2 -->|verified_by| n105
n3 -->|verified_by| n105
n4 -->|verified_by| n105
n6 -->|verified_by| n105
n7 -->|verified_by| n105
n8 -->|verified_by| n105
n9 -->|verified_by| n105
n12 -->|verified_by| n105
n13 -->|verified_by| n105
n15 -->|verified_by| n105
n16 -->|verified_by| n105
n17 -->|verified_by| n105
n18 -->|verified_by| n105
n19 -->|verified_by| n105
n20 -->|verified_by| n105
n21 -->|verified_by| n105
n22 -->|verified_by| n105
n23 -->|verified_by| n105
n25 -->|verified_by| n105
n26 -->|verified_by| n105
n27 -->|verified_by| n105
n29 -->|verified_by| n105
n30 -->|verified_by| n105
n31 -->|verified_by| n105
n32 -->|verified_by| n105
n33 -->|verified_by| n105
n34 -->|verified_by| n105
n35 -->|verified_by| n105
n36 -->|verified_by| n105
n37 -->|verified_by| n105
n38 -->|verified_by| n105
n39 -->|verified_by| n105
n40 -->|verified_by| n105
n43 -->|verified_by| n105
n44 -->|verified_by| n105
n45 -->|verified_by| n105
n46 -->|verified_by| n105
n47 -->|verified_by| n105
n48 -->|verified_by| n105
n49 -->|verified_by| n105
n50 -->|verified_by| n105
n53 -->|verified_by| n105
n54 -->|verified_by| n105
n55 -->|verified_by| n105
n56 -->|verified_by| n105
n57 -->|verified_by| n105
n58 -->|verified_by| n105
n59 -->|verified_by| n105
n60 -->|verified_by| n105
n61 -->|verified_by| n105
n63 -->|verified_by| n105
n65 -->|verified_by| n105
n66 -->|verified_by| n105
n67 -->|verified_by| n105
n0 -->|belongs_to| n99
n1 -->|belongs_to| n96
n2 -->|belongs_to| n88
n3 -->|belongs_to| n89
n4 -->|belongs_to| n98
n6 -->|belongs_to| n104
n7 -->|belongs_to| n86
n8 -->|belongs_to| n98
n9 -->|belongs_to| n91
n12 -->|belongs_to| n88
n13 -->|belongs_to| n96
n14 -->|belongs_to| n88
n15 -->|belongs_to| n89
n16 -->|belongs_to| n94
n17 -->|belongs_to| n89
n18 -->|belongs_to| n89
n19 -->|belongs_to| n89
n20 -->|belongs_to| n93
n21 -->|belongs_to| n93
n22 -->|belongs_to| n90
n23 -->|belongs_to| n96
n25 -->|belongs_to| n90
n26 -->|belongs_to| n87
n27 -->|belongs_to| n94
n28 -->|belongs_to| n91
n29 -->|belongs_to| n93
n30 -->|belongs_to| n98
n31 -->|belongs_to| n101
n32 -->|belongs_to| n94
n33 -->|belongs_to| n94
n34 -->|belongs_to| n94
n35 -->|belongs_to| n95
n36 -->|belongs_to| n102
n37 -->|belongs_to| n102
n38 -->|belongs_to| n99
n39 -->|belongs_to| n91
n40 -->|belongs_to| n102
n41 -->|belongs_to| n93
n43 -->|belongs_to| n98
n44 -->|belongs_to| n102
n45 -->|belongs_to| n91
n46 -->|belongs_to| n97
n47 -->|belongs_to| n97
n48 -->|belongs_to| n96
n49 -->|belongs_to| n99
n50 -->|belongs_to| n95
n51 -->|belongs_to| n103
n53 -->|belongs_to| n89
n54 -->|belongs_to| n102
n55 -->|belongs_to| n91
n56 -->|belongs_to| n92
n57 -->|belongs_to| n95
n58 -->|belongs_to| n95
n59 -->|belongs_to| n98
n60 -->|belongs_to| n101
n61 -->|belongs_to| n101
n62 -->|belongs_to| n103
n63 -->|belongs_to| n89
n64 -->|belongs_to| n93
n65 -->|belongs_to| n94
n66 -->|belongs_to| n97
n67 -->|belongs_to| n104
n68 -->|connects_to| n80
n68 -->|belongs_to| n82
n69 -->|belongs_to| n82
n70 -->|belongs_to| n82
n71 -->|belongs_to| n82
n72 -->|belongs_to| n82
n73 -->|belongs_to| n82
n74 -->|belongs_to| n82
n75 -->|belongs_to| n82
n76 -->|belongs_to| n82
n77 -->|belongs_to| n82
n78 -->|belongs_to| n82
n79 -->|belongs_to| n82
n80 -->|belongs_to| n82
n81 -->|belongs_to| n103
n83 -->|belongs_to| n82
n84 -->|belongs_to| n82
n85 -->|belongs_to| n82
n86 -->|belongs_to| n79
n87 -->|belongs_to| n76
n88 -->|belongs_to| n75
n89 -->|belongs_to| n76
n90 -->|belongs_to| n77
n91 -->|belongs_to| n73
n92 -->|belongs_to| n79
n93 -->|belongs_to| n73
n94 -->|belongs_to| n73
n95 -->|belongs_to| n74
n96 -->|belongs_to| n74
n97 -->|belongs_to| n75
n98 -->|belongs_to| n77
n99 -->|belongs_to| n72
n100 -->|belongs_to| n77
n101 -->|belongs_to| n74
n102 -->|belongs_to| n72
n103 -->|belongs_to| n69
n104 -->|belongs_to| n72
n105 -->|belongs_to| n82
n106 -->|belongs_to| n82
@@ -0,0 +1,21 @@
# 21B-1 — Rune sleep/poll audit (2026-09-16)
| Site | Classification | Rationale |
|---|---|---|
| ci_event_wait.py:82 `time.sleep(gap_secs)` | RETRY_BACKOFF | bounded authority_after_wake ack-window re-read (4s×3) |
| auto_bump_pr.py:169/235 | POLLING (legacy path) | bump-PR CI wait — auto_bump PRs retired (direct bumps since 2026-09-08) |
| merge_robot.py:120/217 | RETRY_BACKOFF | bounded retry on merge API attempts |
| notify_event_bridge.py:51 | RETRY_BACKOFF | single 2s retry before best-effort declaration |
| research_event_relay.py:152 | EVENT_FALLBACK_WITH_REASON | local file-read poll (not Gitea) |
| t14/t15_builder_session.py | WATCHDOG-adjacent | notifier stderr drain interval |
| event_wait.py:93 | EVENT_FALLBACK | notifier POST retry (3× best-effort) |
| auto-update-gitea.yml:45 | SCHEDULED_TIMER | gitea binary update ordering |
**T20's own sleeps (`sleep 540/480/600`) were in the AGENT SESSION, not
Rune code** — the model invented sleep→poll continuation instead of
using ci_event_wait.py. That is a CONTROLLER-level defect: the MiMo
Code execution machinery accepted `sleep N; check` when a supported
event source existed.
MiMo Code controller located: /home/nick/.local/share/npm/lib/node_modules/@mimo-ai/cli
(runs as same package for both nick and gitea-desktop-runner).
@@ -0,0 +1,31 @@
# 21B — MiMo Code controller sleep/poll audit + guard (2026-09-16)
## Controller identity (verified, not assumed)
- Package: @mimo-ai/cli (npm), launcher at
/home/nick/.local/share/npm/lib/node_modules/@mimo-ai/cli/bin/mimo
- Runtime: compiled native binary (ELF x86-64, Bun-compiled, not stripped)
at .../@mimo-ai/mimocode-linux-x64/bin/mimo — NOT source-patchable.
- Extension surface: ~/.mimocode/hooks/*.ts via @mimo-ai/plugin Hooks API
(tool.execute.before with cancel). Hot-reloads per turn.
Precedent: no-blocking-wait.ts (2026-09-07), production-proven.
## Controller sleep/poll audit
- The controller binary has no extension-surface polling loops. The T20
regression (`sleep 540; <CI status probe>`) was MODEL-GENERATED Bash
passed through the controller's shell execution — the correct
interception point is tool.execute.before. Guard = event-wait-guard.ts.
## LIVE PROOF (LIVE_PROVEN_THIS_SESSION)
- v1 guard written mid-session; hot-reloaded IMMEDIATELY (same turn):
it intercepted the very next Bash tool call — the guard's own evidence
file write — because the heredoc contained the literal text "sleep 540".
- False positive acknowledged and fixed in v2: pattern detection now
requires sleep + status-poll in the SAME command segment, with heredoc
bodies stripped before scanning.
- The interception itself is the live proof: the harness accepted the
hook, matched the pattern, cancelled the tool call, and delivered the
redirect message — without any controller restart.
## Rune-side classification (21B-1)
See 21B1_rune_sleep_audit.md — no UNJUSTIFIED_SLEEP in active paths;
auto_bump_pr.py polling loops classified legacy (21G candidate).
@@ -0,0 +1,47 @@
{
"packet": {
"schema_version": "1",
"intent": "make tax rate configurable",
"target": "component:pricing",
"target_kind": "component",
"owning_domain": "domain:inventory",
"local": "{'schema_version': 1, 'capability_id': 'capability:pricing', 'name': 'Pricing', 'purpose': 'taxed price computation', 'component': 'component:pricing', 'source_locations': ['src/inventory/pricing.py'], 'contract': 'item_price(unit, qty) \u2192 taxed total', 'state_touched': 'none (pure)', 'dependencies': [], 'consumers': ['component:summary'], 'invariants': ['tax applied multiplicatively'], 'behaviour_specs': ['behaviour:VAL-ST-1', 'behaviour:VAL-ST-2'], 'maturity': 'implemented', 'constraints': '', 'generation': '', 'limitations': []}",
"local_files": "['src/inventory/pricing.py']",
"beneath_count": "0",
"connected": "{'dependencies': [], 'consumers': ['component:summary'], 'interface_mediated_consumers': ['component:summary'], 'interfaces_provided': ['interface:pricing-api'], 'state_owned': [], 'verification': ['test_suite:tests'], '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": "None",
"generated_at": "2026-09-16T12:47:06Z"
},
"preview": {
"schema_version": 1,
"target": "component:pricing",
"affected_domains": [
"domain:inventory"
],
"affected_subsystems": [],
"consumers_connectors": [
"component:summary"
],
"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": [
"test_suite:tests"
],
"rollback_implications": [
"revert contract scope commits; deployment rollbacks via last-good symlink (canonical deploy machinery)"
],
"basis_summary": {
"depth": "OUTWARD",
"why": "precedent-relevant unknowns present"
}
}
}
+217
View File
@@ -0,0 +1,217 @@
#!/usr/bin/env python3
"""T21 §21K — external-project adoption proof: stock-tool.
Runs the FULL machinery on a disposable external repo:
registry → basis → scaffold → ledgers → packet → predictive impact →
construction (MINOR) → descriptive impact → invalidation → commit.
Idempotent per step; safe to re-run.
"""
import json
import subprocess
import sys
from pathlib import Path
sys.path.insert(0, "/mnt/workdrive/issue161-harness/src")
from rune import behaviour_ledger as bl
from rune import build_basis as bb
from rune import capability_ledger as cl
from rune import impact_preview as ip
from rune import iteration as it
from rune import project_registry as pr
from rune import structural_model as sm
REPO = Path("/mnt/workdrive/t21-adoption/repo")
STORE = Path("/mnt/workdrive/t21-adoption/scaffold")
STEP = sys.argv[1] if len(sys.argv) > 1 else "all"
def basis():
plan = bb.generate_research_plan(
intent="adopt stock-tool into Scaffolding: discovery + ledgers",
target="stock-tool",
unknowns=["what subsystems/capabilities exist (discovery)"],
decisions=["which module owns state"])
b = bb.new_basis(question="adopt stock-tool",
depth_decision=plan["depth"], issue_ref="t21-21k")
b["outward"]["findings"] = [
"no external prior art needed: adoption machinery already exists "
"(T19 registry/ledgers); discovery question is repo-internal"]
b["outward"]["refs"] = ["src/rune/project_registry.py"]
errs = bb.set_dispositions(b, [
{"item": "hardcoded TAX_RATE", "disposition": "ADAPT",
"why": "configurable tax = backwards-compatible MINOR "
"(param with default)", "ref": "src/inventory/pricing.py"},
{"item": "rewrite stock-tool from scratch", "disposition": "REJECT",
"why": "working code; adopting not replacing", "ref": "-"}])
assert errs == [], errs
return b
def scaffold():
m = sm.StructuralModel(STORE, "stock-tool")
changed = False
if m.get("domain:inventory") is None:
m.add("domain", "inventory", parent="project:stock-tool")
m.add("domain", "reporting", parent="project:stock-tool")
m.add("subsystem", "stock-tracking", parent="domain:inventory")
m.add("component", "stock", parent="subsystem:stock-tracking",
ext={"source": "src/inventory/stock.py"})
m.add("component", "pricing", parent="subsystem:stock-tracking",
ext={"source": "src/inventory/pricing.py"})
m.add("component", "summary", parent="domain:reporting",
ext={"source": "src/reporting/summary.py"})
m.add("interface", "pricing-api", parent="domain:inventory")
m.relate("component:pricing", "provides", "interface:pricing-api")
m.relate("component:summary", "consumes", "interface:pricing-api",
evidence_ref="static import in summary.py")
m.add("state_store", "stock-data", parent="project:stock-tool")
m.relate("component:stock", "owns", "state_store:stock-data")
m.add("test_suite", "tests", parent="project:stock-tool")
for c in ("stock", "pricing", "summary"):
m.relate(f"component:{c}", "verified_by", "test_suite:tests")
changed = True
if changed:
m.save()
return m
def ledgers():
l = cl.CapabilityLedger(STORE)
if l.get("pricing") is None:
l.define(capability_id="pricing", name="Pricing",
purpose="taxed price computation",
owning_project="stock-tool",
component="component:pricing",
source_locations=["src/inventory/pricing.py"],
contract="item_price(unit, qty) → taxed total",
state_touched="none (pure)",
consumers=["component:summary"],
invariants=["tax applied multiplicatively"],
behaviour_specs=["behaviour:VAL-ST-1", "behaviour:VAL-ST-2"])
cl.attach_spec(l, "pricing",
requirements=["compute taxed inventory value"],
expected_behaviours=["tax applied", "value sums"],
non_goals=["currency conversion"],
acceptance_criteria=["existing tests pass"],
baseline_version="0.1.0",
baseline_reason="adopted existing code; contract young")
l.save()
beh = bl.BehaviourLedger(STORE)
if beh.get("VAL-ST-1") is None:
for bid, intent, comp in [
("VAL-ST-1", "item price includes tax", "component:pricing"),
("VAL-ST-2", "inventory value sums taxed prices",
"component:pricing"),
("VAL-ST-3", "report includes total value line",
"component:summary")]:
beh.define(behaviour_id=bid, owning_project="stock-tool",
intended_behaviour=intent, owner_component=comp,
dependencies=[comp])
beh.record_auto_verification(bid, passed=True, generation="0.1.0")
beh.save()
return l, beh
def adopt():
reg = pr.ProjectRegistry("/mnt/workdrive/issue161-harness/store/scaffold")
if reg.get("stock-tool") is None:
reg.register(project_id="stock-tool", scaffold_location=str(STORE),
repo_identity="git:" + str(REPO),
default_branch="master", structural_schema_version=1)
reg.save()
print("1. REGISTRY: stock-tool registered (references only)")
b = basis()
print("2. BASIS: qualified, depth =", b["depth"]["depth"])
m = scaffold()
print("3. SCAFFOLD:", len(m.doc["entities"]), "entities,",
len(m.doc["relationships"]), "edges")
l, beh = ledgers()
print("4. LEDGERS: capability spec", l.get("pricing")["spec"]["version"],
"+ behaviours:", beh.summary()["states"])
pkt = it.context_packet(m, l, "component:pricing",
intent="make tax rate configurable")
preview = ip.build_preview(m, it.predictive_impact(m, "component:pricing"),
basis=b)
out = Path("/mnt/workdrive/issue161-harness/store/evidence/t21")
out.mkdir(exist_ok=True)
(out / "21K_adoption_packet.json").write_text(json.dumps(
{"packet": {k: str(v) for k, v in pkt.items()},
"preview": preview}, indent=1, default=str))
print("5. PACKET: files =", pkt["local_files"], "| preview valid:",
ip.validate_preview(preview) == [])
return m, l, beh, pkt
def construct(m, l, beh):
"""The bounded MINOR change: configurable tax with default."""
src = REPO / "src/inventory/pricing.py"
text = src.read_text()
if "def __init__" in text or "tax_rate: float" in text:
print("6. CONSTRUCTION: already applied")
return
old = '''TAX_RATE = 0.08 # LEGACY: hardcoded, should be configurable
def item_price(unit: float, qty: int) -> float:
return round(unit * qty * (1 + TAX_RATE), 2)'''
new = '''DEFAULT_TAX_RATE = 0.08 # default; configurable per inventory (T21 21K)
def item_price(unit: float, qty: int, tax_rate: float = DEFAULT_TAX_RATE) -> float:
return round(unit * qty * (1 + tax_rate), 2)'''
assert old in text
src.write_text(text.replace(old, new))
# existing default-behaviour tests must still pass (contract-compatible)
proc = subprocess.run(
[sys.executable, "-m", "pytest", "tests/test_pricing.py",
"tests/test_report.py", "-q"],
capture_output=True, text=True, cwd=REPO,
env={"PATH": "/usr/bin:/bin", "PYTHONPATH": "src"})
print("6. CONSTRUCTION: tax configurable; existing tests:",
"GREEN" if proc.returncode == 0 else "RED")
assert proc.returncode == 0, proc.stdout[-400:]
# spec revision: backwards-compatible behaviour addition → MINOR
r = cl.revise_spec(l, "pricing", behaviour_added=True,
reason="21K: tax rate now configurable per call; "
"default preserves the old contract",
changed_sections=["item_price signature"],
affected_consumers=["component:summary (none: "
"default unchanged)"],
validation_consequences=[
"new behaviour spec VAL-ST-4"])
print(f"7. SPEC: {r['old']} → {r['new']} ({r['kind']})")
beh.define(behaviour_id="VAL-ST-4", owning_project="stock-tool",
intended_behaviour="tax rate configurable per call; default 0.08",
owner_component="component:pricing",
dependencies=["component:pricing"])
beh.record_auto_verification("VAL-ST-4", passed=True, generation=r["new"])
beh.save()
print("8. NEW BEHAVIOUR: VAL-ST-4 AUTO_VERIFIED @", r["new"])
# descriptive impact: actual diff = pricing only (consumer untouched)
desc = it.descriptive_impact(m, "component:pricing",
observed_changed=["component:pricing"],
generation=r["new"])
it.record_impact_delta(STORE, desc)
print("9. DESCRIPTIVE IMPACT: observed", desc["observed"],
"| unexpected", desc["unexpected"])
# git commit in the external repo
subprocess.run(["git", "add", "-A"], cwd=REPO, check=True)
subprocess.run(["git", "commit", "-qm",
f"feat(pricing): configurable tax rate "
f"(capability pricing {r['old']}→{r['new']})"],
cwd=REPO, check=True)
print("10. COMMIT: external repo committed")
def main():
if STEP in ("all", "adopt"):
adopt()
if STEP in ("all", "construct"):
m = sm.StructuralModel(STORE, "stock-tool")
l = cl.CapabilityLedger(STORE)
beh = bl.BehaviourLedger(STORE)
construct(m, l, beh)
print("21K DONE")
if __name__ == "__main__":
main()
+382
View File
@@ -0,0 +1,382 @@
{
"behaviours": {
"behaviour:VAL-RUNE-001": {
"automated_ref": "",
"behaviour_id": "behaviour:VAL-RUNE-001",
"created_at": "2026-09-16T01:26:52Z",
"dependencies": [
"component:rune_tui",
"component:construction_mode"
],
"environment": "",
"evidence": [
{
"at": "2026-09-16T01:26:52Z",
"detail": "T18/T19 live evidence",
"generation": "gen-0.66.80",
"kind": "automated",
"passed": true
}
],
"fail_criteria": "no contract or silent bypass",
"history": [
{
"at": "2026-09-16T01:26:52Z",
"detail": "T18/T19 live evidence",
"from": "UNVALIDATED",
"generation": "gen-0.66.80",
"kind": "automated_verification",
"to": "AUTO_VERIFIED"
},
{
"at": "2026-09-16T01:31:56Z",
"detail": "was AUTO_VERIFIED; 20R case-1: construction_mode structurally changed in retrofit",
"from": "AUTO_VERIFIED",
"generation": "t20-retrofit",
"kind": "selective_invalidation",
"to": "RETEST_REQUIRED"
}
],
"intended_behaviour": "construction request becomes governed Contract",
"invalidations": [
{
"at": "2026-09-16T01:31:56Z",
"because": "20R case-1: construction_mode structurally changed in retrofit",
"changed_entities": [
"component:construction_mode"
],
"generation": "t20-retrofit",
"was": "AUTO_VERIFIED"
}
],
"limitations": [],
"origin_intent": "",
"owner_component": "component:rune_tui",
"owning_project": "rune",
"pass_criteria": "contract exists in canonical store with building phase",
"procedure": "send '/build start <real task>'; expect contract minted + building",
"state": "RETEST_REQUIRED",
"updated_at": "2026-09-16T01:31:56Z"
},
"behaviour:VAL-RUNE-002": {
"automated_ref": "",
"behaviour_id": "behaviour:VAL-RUNE-002",
"created_at": "2026-09-16T01:26:52Z",
"dependencies": [
"component:programme_continuation"
],
"environment": "",
"evidence": [
{
"at": "2026-09-16T01:26:52Z",
"detail": "T18/T19 live evidence",
"generation": "gen-0.66.80",
"kind": "automated",
"passed": true
}
],
"fail_criteria": "voluntary stop with work remaining",
"history": [
{
"at": "2026-09-16T01:26:52Z",
"detail": "T18/T19 live evidence",
"from": "UNVALIDATED",
"generation": "gen-0.66.80",
"kind": "automated_verification",
"to": "AUTO_VERIFIED"
}
],
"intended_behaviour": "programme continuation resumes without user intervention",
"invalidations": [],
"limitations": [],
"origin_intent": "",
"owner_component": "component:programme_continuation",
"owning_project": "rune",
"pass_criteria": "PROGRAMME_COMPLETE without user continue",
"procedure": "run 4-stage programme; next stage auto-selected after each completion",
"state": "AUTO_VERIFIED",
"updated_at": "2026-09-16T01:26:52Z"
},
"behaviour:VAL-RUNE-003": {
"automated_ref": "",
"behaviour_id": "behaviour:VAL-RUNE-003",
"created_at": "2026-09-16T01:26:52Z",
"dependencies": [
"component:event_wait"
],
"environment": "",
"evidence": [
{
"at": "2026-09-16T01:26:52Z",
"detail": "T18/T19 live evidence",
"generation": "gen-0.66.77",
"kind": "automated",
"passed": true
}
],
"fail_criteria": "deadline_pending on every cycle",
"history": [
{
"at": "2026-09-16T01:26:52Z",
"detail": "T18/T19 live evidence",
"from": "UNVALIDATED",
"generation": "gen-0.66.77",
"kind": "automated_verification",
"to": "AUTO_VERIFIED"
}
],
"intended_behaviour": "event wait resumes from event rather than timeout",
"invalidations": [],
"limitations": [],
"origin_intent": "",
"owner_component": "component:event_wait",
"owning_project": "rune",
"pass_criteria": "mode=event or event_replayed",
"procedure": "deploy cycle with live CI; waiter resumes on wake",
"state": "AUTO_VERIFIED",
"updated_at": "2026-09-16T01:26:52Z"
},
"behaviour:VAL-RUNE-004": {
"automated_ref": "",
"behaviour_id": "behaviour:VAL-RUNE-004",
"created_at": "2026-09-16T01:26:52Z",
"dependencies": [
"component:rune_operator"
],
"environment": "",
"evidence": [
{
"at": "2026-09-16T01:26:52Z",
"detail": "T18/T19 live evidence",
"generation": "gen-0.66.80",
"kind": "automated",
"passed": true
}
],
"fail_criteria": "silent provider switch",
"history": [
{
"at": "2026-09-16T01:26:52Z",
"detail": "T18/T19 live evidence",
"from": "UNVALIDATED",
"generation": "gen-0.66.80",
"kind": "automated_verification",
"to": "AUTO_VERIFIED"
}
],
"intended_behaviour": "provider failure preserves truthful state, no fallback",
"invalidations": [],
"limitations": [],
"origin_intent": "",
"owner_component": "component:rune_operator",
"owning_project": "rune",
"pass_criteria": "single attempt, real code preserved",
"procedure": "401/429 from provider; expect PROVIDER_UNAVAILABLE, one attempt",
"state": "AUTO_VERIFIED",
"updated_at": "2026-09-16T01:26:52Z"
},
"behaviour:VAL-RUNE-005": {
"automated_ref": "",
"behaviour_id": "behaviour:VAL-RUNE-005",
"created_at": "2026-09-16T01:26:52Z",
"dependencies": [
"component:deploy_pipeline"
],
"environment": "",
"evidence": [
{
"at": "2026-09-16T01:26:52Z",
"detail": "T18/T19 live evidence",
"generation": "gen-0.66.77",
"kind": "automated",
"passed": true
}
],
"fail_criteria": "promotion of red SHA",
"history": [
{
"at": "2026-09-16T01:26:52Z",
"detail": "T18/T19 live evidence",
"from": "UNVALIDATED",
"generation": "gen-0.66.77",
"kind": "automated_verification",
"to": "AUTO_VERIFIED"
}
],
"intended_behaviour": "release never promotes red SHA",
"invalidations": [],
"limitations": [],
"origin_intent": "",
"owner_component": "component:deploy_pipeline",
"owning_project": "rune",
"pass_criteria": "deployment refused truthfully",
"procedure": "red CI SHA; deploy must refuse",
"state": "AUTO_VERIFIED",
"updated_at": "2026-09-16T01:26:52Z"
},
"behaviour:VAL-RUNE-006": {
"automated_ref": "",
"behaviour_id": "behaviour:VAL-RUNE-006",
"created_at": "2026-09-16T01:26:52Z",
"dependencies": [
"component:rune_operator"
],
"environment": "",
"evidence": [
{
"at": "2026-09-16T01:26:52Z",
"detail": "T18/T19 live evidence",
"generation": "gen-0.67.0",
"kind": "automated",
"passed": true
}
],
"fail_criteria": "operator.json cache diverges",
"history": [
{
"at": "2026-09-16T01:26:52Z",
"detail": "T18/T19 live evidence",
"from": "UNVALIDATED",
"generation": "gen-0.67.0",
"kind": "automated_verification",
"to": "AUTO_VERIFIED"
}
],
"intended_behaviour": "task-state authority remains canonical",
"invalidations": [],
"limitations": [],
"origin_intent": "",
"owner_component": "component:rune_operator",
"owning_project": "rune",
"pass_criteria": "get_status matches repo counts",
"procedure": "counters derive from TaskStateRepository",
"state": "AUTO_VERIFIED",
"updated_at": "2026-09-16T01:26:52Z"
},
"behaviour:VAL-RUNE-007": {
"automated_ref": "",
"behaviour_id": "behaviour:VAL-RUNE-007",
"created_at": "2026-09-16T01:26:52Z",
"dependencies": [
"component:rune_tui"
],
"environment": "",
"evidence": [],
"fail_criteria": "stale/wrong provider display",
"history": [],
"intended_behaviour": "TUI reports correct active model/provider",
"invalidations": [],
"limitations": [],
"origin_intent": "",
"owner_component": "component:rune_tui",
"owning_project": "rune",
"pass_criteria": "footer matches selection",
"procedure": "manual: switch provider, check footer",
"state": "UNVALIDATED",
"updated_at": "2026-09-16T01:26:52Z"
},
"behaviour:VAL-RUNE-008": {
"automated_ref": "",
"behaviour_id": "behaviour:VAL-RUNE-008",
"created_at": "2026-09-16T01:26:52Z",
"dependencies": [
"component:rune_tui",
"module:testing_mode"
],
"environment": "",
"evidence": [
{
"at": "2026-09-16T01:26:52Z",
"detail": "T18/T19 live evidence",
"generation": "gen-0.67.0",
"kind": "automated",
"passed": true
}
],
"fail_criteria": "crash or wrong counts",
"history": [
{
"at": "2026-09-16T01:26:52Z",
"detail": "T18/T19 live evidence",
"from": "UNVALIDATED",
"generation": "gen-0.67.0",
"kind": "automated_verification",
"to": "AUTO_VERIFIED"
}
],
"intended_behaviour": "/testing reports outstanding validations",
"invalidations": [],
"limitations": [],
"origin_intent": "",
"owner_component": "component:rune_tui",
"owning_project": "rune",
"pass_criteria": "counts match ledger outstanding()",
"procedure": "run /testing with registered projects; expect counts",
"state": "AUTO_VERIFIED",
"updated_at": "2026-09-16T01:26:52Z"
},
"behaviour:VAL-RUNE-009": {
"automated_ref": "",
"behaviour_id": "behaviour:VAL-RUNE-009",
"created_at": "2026-09-16T01:26:52Z",
"dependencies": [
"component:rune_operator"
],
"environment": "",
"evidence": [
{
"at": "2026-09-16T01:26:52Z",
"detail": "T18/T19 live evidence",
"generation": "gen-0.66.80",
"kind": "automated",
"passed": true
}
],
"fail_criteria": "requires transcript",
"history": [
{
"at": "2026-09-16T01:26:52Z",
"detail": "T18/T19 live evidence",
"from": "UNVALIDATED",
"generation": "gen-0.66.80",
"kind": "automated_verification",
"to": "AUTO_VERIFIED"
}
],
"intended_behaviour": "state survives expected restart/resume path",
"invalidations": [],
"limitations": [],
"origin_intent": "",
"owner_component": "component:rune_operator",
"owning_project": "rune",
"pass_criteria": "resume from contract id alone",
"procedure": "restart mid-programme; resume from durable record",
"state": "AUTO_VERIFIED",
"updated_at": "2026-09-16T01:26:52Z"
},
"behaviour:VAL-RUNE-010": {
"automated_ref": "",
"behaviour_id": "behaviour:VAL-RUNE-010",
"created_at": "2026-09-16T01:33:59Z",
"dependencies": [
"component:deploy_pipeline"
],
"environment": "",
"evidence": [],
"fail_criteria": "any skew requiring baseline chase",
"history": [],
"intended_behaviour": "same pinned inputs produce deterministic validation locally and in CI",
"invalidations": [],
"limitations": [],
"origin_intent": "",
"owner_component": "component:deploy_pipeline",
"owning_project": "rune",
"pass_criteria": "identical error/warning arithmetic",
"procedure": "build venv from committed lock; run ratchet; compare with CI job",
"state": "UNVALIDATED",
"updated_at": "2026-09-16T01:33:59Z"
}
},
"schema_version": 1,
"updated_at": "2026-09-16T01:33:59Z"
}
+260
View File
@@ -0,0 +1,260 @@
{
"capabilities": {
"capability:deploy-promote": {
"behaviour_specs": [
"behaviour:deploy-refuses-red"
],
"capability_id": "capability:deploy-promote",
"component": "component:deploy_pipeline",
"constraints": "",
"consumers": [],
"contract": "qualify \u2192 stage \u2192 verify \u2192 promote/noop/stale",
"created_at": "2026-09-16T01:26:33Z",
"dependencies": [],
"generation": "",
"invariants": [
"never promote red SHA; stale superseded truthfully"
],
"limitations": [],
"maturity": "implemented",
"name": "Monotonic deploy promotion",
"owning_project": "rune",
"purpose": "stage\u2192verify\u2192promote with flock check-and-swap, fail-closed",
"source_locations": [
"scripts/deploy_stage.py",
"scripts/deploy_promotion.py"
],
"spec": {
"acceptance_criteria": [
"deployment identity re-read from authority"
],
"baseline_reason": "monotonic + fail-closed proven across T16-T20; contract stable",
"expected_behaviours": [
"red SHA refused",
"stale superseded truthfully",
"current/last-good ordered"
],
"governance_version_started_at": "2026-09-16T03:59:49Z",
"non_goals": [
"no live-tree mutation"
],
"requirements": [
"monotonic promotion, fail-closed"
],
"schema_version": 1,
"version": "0.4.0",
"version_history": [
{
"affected_consumers": [],
"at": "2026-09-16T03:59:49Z",
"changed_sections": [],
"kind": "BASELINE",
"reason": "monotonic + fail-closed proven across T16-T20; contract stable",
"validation_consequences": [],
"version": "0.4.0"
}
]
},
"state_touched": "deploy root + current/last-good symlinks",
"updated_at": "2026-09-16T03:59:49Z"
},
"capability:event-wait-resume": {
"behaviour_specs": [
"behaviour:event-wake-midwait"
],
"capability_id": "capability:event-wait-resume",
"component": "component:event_wait",
"constraints": "",
"consumers": [
"component:programme_continuation"
],
"contract": "register \u2192 select() on FIFO \u2192 event \u2192 authority re-read",
"created_at": "2026-09-16T01:26:33Z",
"dependencies": [
"component:wait-daemon"
],
"generation": "",
"invariants": [
"event is wake, never truth"
],
"limitations": [],
"maturity": "implemented",
"name": "Event-driven wait/resume",
"owning_project": "rune",
"purpose": "EVENT drives continuation; watchdog detects failure",
"source_locations": [
"src/rune/event_wait.py"
],
"spec": {
"acceptance_criteria": [
"watchdog only fires when events are lost"
],
"baseline_reason": "cross-user + replay proven in T17C/T19; contract stable",
"expected_behaviours": [
"wake mid-wait",
"durable-record recovery",
"replay before registration"
],
"governance_version_started_at": "2026-09-16T03:59:49Z",
"non_goals": [
"no polling loops"
],
"requirements": [
"long operations resume on event, not timeout"
],
"schema_version": 1,
"version": "0.3.0",
"version_history": [
{
"affected_consumers": [],
"at": "2026-09-16T03:59:49Z",
"changed_sections": [],
"kind": "BASELINE",
"reason": "cross-user + replay proven in T17C/T19; contract stable",
"validation_consequences": [],
"version": "0.3.0"
}
]
},
"state_touched": "W/R bridge waits (shared root)",
"updated_at": "2026-09-16T03:59:49Z"
},
"capability:operator-send-task": {
"behaviour_specs": [
"behaviour:send-task-governed"
],
"capability_id": "capability:operator-send-task",
"component": "component:rune_operator",
"constraints": "provider context inherited, never selected",
"consumers": [
"component:rune_tui"
],
"contract": "description+model+channel \u2192 task record + execution",
"created_at": "2026-09-16T01:26:33Z",
"dependencies": [
"component:adaptive_router",
"component:construction_mode"
],
"generation": "t20",
"invariants": [
"route decided before execution"
],
"limitations": [],
"maturity": "implemented",
"name": "Operator send_task",
"owning_project": "rune",
"purpose": "routes a user task through classification, construction activation, and execution",
"risk_notes": [
{
"at": "t20-benchmark",
"note": "write-ordering window: ~10 sites mutate RuneTask.status + _save() BEFORE _task_state.transition(); crash between = legacy cache ahead of canonical truth; _task_counters falls back to cache on empty repo (rune_operator.py:906-1605, 755-779)"
}
],
"source_locations": [
"src/rune/rune_operator.py"
],
"spec": {
"acceptance_criteria": [
"route exists in route.jsonl for every task"
],
"baseline_reason": "canonical authority proven; contract stable since T17F",
"expected_behaviours": [
"task record created",
"route decided before execution",
"construction activation recorded"
],
"governance_version_started_at": "2026-09-16T03:59:49Z",
"non_goals": [
"no provider selection"
],
"requirements": [
"description routes through classification and construction activation"
],
"schema_version": 1,
"version": "0.2.0",
"version_history": [
{
"affected_consumers": [],
"at": "2026-09-16T03:59:49Z",
"changed_sections": [],
"kind": "BASELINE",
"reason": "canonical authority proven; contract stable since T17F",
"validation_consequences": [],
"version": "0.2.0"
}
]
},
"state_touched": "TaskStateRepository (canonical), operator.json (legacy cache)",
"updated_at": "2026-09-16T03:59:49Z"
},
"capability:tui-testing-command": {
"behaviour_specs": [
"behaviour:testing-aggregate"
],
"capability_id": "capability:tui-testing-command",
"component": "component:rune_tui",
"constraints": "",
"consumers": [],
"contract": "/testing \u2192 aggregate; /testing <project> \u2192 filtered",
"created_at": "2026-09-16T01:26:33Z",
"dependencies": [
"module:testing_mode"
],
"generation": "",
"invariants": [
"testing_mode backend is authority; TUI renders"
],
"limitations": [],
"maturity": "implemented",
"name": "TUI /testing command",
"owning_project": "rune",
"purpose": "aggregates outstanding validations across registered projects",
"source_locations": [
"src/rune/rune_tui.py"
],
"spec": {
"acceptance_criteria": [
"backend is authority"
],
"baseline_reason": "new capability T19; young contract",
"expected_behaviours": [
"counts match ledger outstanding()"
],
"governance_version_started_at": "2026-09-16T03:59:49Z",
"non_goals": [
"no TUI polish"
],
"requirements": [
"aggregate + per-project outstanding validations"
],
"schema_version": 1,
"version": "0.1.0",
"version_history": [
{
"affected_consumers": [],
"at": "2026-09-16T03:59:49Z",
"changed_sections": [],
"kind": "BASELINE",
"reason": "new capability T19; young contract",
"validation_consequences": [],
"version": "0.1.0"
}
]
},
"state_touched": "",
"updated_at": "2026-09-16T03:59:49Z"
}
},
"history": [
{
"at": "2026-09-16T01:55:10Z",
"capability_id": "capability:operator-send-task",
"event": "material_change",
"generation": "t20",
"note": "risk_notes added from 20Q cold-benchmark finding",
"prior_maturity": "implemented"
}
],
"schema_version": 1,
"updated_at": "2026-09-16T03:59:49Z"
}
Loaded 100 of 138 files, more files were not shown because too many files have changed in this diff. Show more