Compare commits

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

The tool: registers on the W/R event bridge, blocks until the completion
event arrives, returns verified JSON verdict. The model sees a normal
tool result — not an error, not a guard rejection.
2026-09-16 19:51:57 +01:00
conductor 417e824a51 chore(release): auto-bump version to 0.79.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 17s
CI Pipeline / lint (push) Failing after 27s
CI Pipeline / unit-tests (push) Successful in 2m42s
CI Pipeline / canary (push) Successful in 29s
deploy / deploy (push) Failing after 1m43s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 7s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 18s
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 18:37:41 +01:00
Nick ca2807e9b5 feat(t21.2): 21.2F blind external adoption — independent creator + independent adopter, 100% recall on important entities, legacy trap preserved, 0 false relationships; 21.2J matrix 6 new held-outs; 21.2I contradiction hunt persisted
CI Pipeline / best-practices (push) Successful in 12s
CI Pipeline / documentation (push) Successful in 6s
Auto-version and Release / release (push) Successful in 34s
CI Pipeline / lint (push) Failing after 35s
CI Pipeline / canary (push) Successful in 34s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / unit-tests (push) Successful in 2m52s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 7s
deploy / deploy (push) Failing after 5m36s
2026-09-16 18:37:22 +01:00
conductor 25f5219982 chore(release): auto-bump version to 0.78.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 17s
deploy / deploy (push) Failing after 3m28s
CI Pipeline / lint (push) Failing after 31s
CI Pipeline / unit-tests (push) Successful in 2m50s
CI Pipeline / canary (push) Successful in 30s
CI Pipeline / best-practices (push) Successful in 6s
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) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 7s
Promotion record: (unknown)
2026-09-16 18:22:41 +01:00
Nick 2d09bee39e feat(t21.2): 21.2B resolve_cwd fix + 21.2C universal wait enforcement + 21.2D canonical hook ownership + 21.2G mandatory-gate accounting
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / documentation (push) Successful in 10s
CI Pipeline / lint (push) Failing after 52s
Auto-version and Release / release (push) Successful in 52s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / type-check (push) Successful in 32s
CI Pipeline / canary (push) Successful in 34s
CI Pipeline / unit-tests (push) Successful in 3m11s
CI Pipeline / required-checks (push) Failing after 2s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 8s
deploy / deploy (push) Failing after 7m32s
- 21.2B: resolve_cwd returns repo_identity (git:<path> stripped) not
  scaffold_location; worktree override; unknown project raises; no
  repo → refuse; 8 held-outs incl. old-bug reproduction.
- 21.2C: wait_guard covers time.sleep(N) (inline + heredoc), polling
  loops (while/until + status on full command), repeated delay+status;
  7 new held-outs; legitimate cases still pass.
- 21.2D: canonical hook source at config/mimo-hooks/event-wait-guard.ts;
  scripts/install_mimo_hook.py verifies hash; drift test detects
  canonical↔installed divergence.
- 21.2G: programme_gates.py — mandatory gates end in PASS/BLOCKED/
  WAIVED_BY_USER only; GateDemotionError prevents mandatory→optional
  demotion; 6 held-outs.
2026-09-16 18:22:05 +01:00
69 changed files with 12667 additions and 377 deletions

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.77.0**
**Current release: v0.83.4**
**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.**
+56
View File
@@ -0,0 +1,56 @@
import type { Hooks } from "@mimo-ai/plugin"
// event-wait-guard — RETIRED (T21.5 §6/§8, 2026-09-17).
//
// HISTORY: this hook was the T21 §21B-3 runtime interception layer that
// blocked model-generated sleep→poll continuation (T20 regression), and
// later carried the T21.1 §21.1C cwd guard. Both were DOWNSTREAM patches:
// they corrected the model's output instead of fixing the generator.
//
// ROOT-CAUSE FIX (T21.5 §2-§5): the generator was ci_event_wait's
// wait loop returning nonterminal control to the model — caused by a
// single hard-coded master-deploy authority that was blind to branch/PR
// operations. T21.5 fixed this upstream:
// - OPERATION_SPECS: per-operation-type completion authority
// (branch_ci/pull_request_checks -> own Actions runs;
// master_ci/deploy/release -> deploy qualification);
// - SINGLE LOGICAL AWAIT: all nonterminal conditions re-arm inside
// ci_event_wait.run(); the model sees only terminal results;
// - structured PendingOperation envelopes (op_type, authority,
// internal_rearm_count) propagate to the controller.
//
// With the generator fixed, runtime interception is retired per the
// tranche rule FIXED UPSTREAM BEHAVIOUR > DOWNSTREAM GUARD CORRECTION.
// Regression prevention lives in the test suite:
// tests/test_single_logical_await.py (no model re-arm contract)
// tests/test_ci_event_wait.py (watchdog=recovery, event=wake)
// tests/test_wait_guard_corpus.py (canonical wait-pattern corpus)
// NOT as a permanent runtime block. The hook file stays installed as a
// documented no-op so canonical-source/install parity (test_hook_drift,
// test_hook_parity) continues to prove the installed runtime matches
// this repository's canonical state.
export const EventWaitGuard: Hooks = {
"tool.execute.before": async (_input, _output) => {
// RETIRED — no interception. See history above.
return
},
}
export const CwdGuard: Hooks = {
"tool.execute.before": async (_input, _output) => {
// RETIRED (T21.5 §8) — the harness/construction execution boundary
// now resolves project context upstream (execution-context binding).
// No runtime refusal. See tests/test_auto_execution_context.py.
return
},
}
export const CombinedGuards: Hooks = {
"tool.execute.before": async (input, output) => {
// Kept as the default export shape; both sub-guards are retired.
return
},
}
export default CombinedGuards
+54
View File
@@ -0,0 +1,54 @@
import { tool } from "@mimo-ai/plugin"
import { execSync } from "child_process"
// await_operation — T21.3 §21.3C: FIRST-CLASS async wait tool.
// Uses the plugin tool() API with args (not parameters).
export const AwaitOperation = tool({
description: [
"Wait for an asynchronous external operation (CI, deploy, release) to complete.",
"Registers on the event bridge, yields until the completion event arrives, then returns the verified result.",
"USE THIS instead of `sleep N; curl status` — it is the canonical event-driven wait.",
].join(" "),
args: {
sha: tool.schema.string().describe(
"The exact commit SHA the operation is running against"),
deadline_secs: tool.schema.number().optional().describe(
"Maximum wait in seconds (default 900)"),
},
async execute(args, ctx) {
const repoRoot = "/mnt/workdrive/issue161-harness"
const deadline = args.deadline_secs || 900
const cmd = [
"python3", `${repoRoot}/scripts/ci_event_wait.py`,
"--sha", args.sha,
"--deadline", String(deadline),
].join(" ")
try {
const result = execSync(cmd, {
cwd: repoRoot,
timeout: (deadline + 60) * 1000,
encoding: "utf-8" as const,
env: {
...process.env,
DEPLOY_SHA: args.sha,
RUNE_WR_ROOT: "/mnt/workdrive/wr-bridge",
RUNE_WR_CODE: "/home/nick/.local/share/mimo-wait-resume",
},
})
const lines = result.trim().split("\n")
const jsonLine = lines.find((l: string) => l.startsWith("{"))
if (jsonLine) return jsonLine
return "await_operation: no JSON output"
} catch (e: any) {
const stdout = e.stdout || ""
const lines = stdout.trim().split("\n")
const jsonLine = lines.find((l: string) => l.startsWith("{"))
if (jsonLine) return jsonLine
return JSON.stringify({
ok: false,
error: `await_operation failed: ${(e.stderr || e.message || "?").slice(0, 200)}`,
})
}
},
})
+4 -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-16T15:14:52Z",
"generated_at": "2026-09-16T18:23:38Z",
"umbrellas": {
"capability": {
"description": "Tool selection and execution authority",
@@ -45,7 +45,8 @@
"drift_core",
"drift_ledger",
"drift_observability",
"failure_auto_filer"
"failure_auto_filer",
"programme_gates"
],
"depends_on": [],
"mirrors": [],
@@ -255,6 +256,7 @@
"toolchain",
"tui_backlog",
"wait_guard",
"wait_guard_corpus",
"witness"
],
"depends_on": [
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.77.0"
version = "0.83.4"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
+3 -3
View File
@@ -1,5 +1,5 @@
# This file was autogenerated by uv via the following command:
# uv pip compile pyproject.toml -o requirements-ci.txt --extra dev --extra memory --python-version 3.11
# uv pip compile pyproject.toml -o /tmp/regen-req.txt --extra dev --extra memory --python-version 3.11
aiohappyeyeballs==2.7.1
# via aiohttp
aiohttp==3.14.3
@@ -34,7 +34,7 @@ httpcore==1.0.9
# via httpx
httpx==0.28.1
# via rune (pyproject.toml)
idna==3.19
idna==3.20
# via
# anyio
# httpx
@@ -64,7 +64,7 @@ nodeenv==1.10.0
# via pyright
packaging==26.3
# via pytest
platformdirs==4.11.8
platformdirs==4.11.9
# via textual
pluggy==1.6.0
# via
+239 -53
View File
@@ -63,11 +63,186 @@ def authority_verdict() -> str:
capture_output=True, text=True, timeout=120,
env=dict(os.environ, PYTHONPATH=""),
)
# T21.3: GITEA_TOKEN from deploy.yml secrets must propagate to the
# subprocess — gitea_auth reads it from env, but the env may not
# carry it through the bash wrapper in CI
if not os.environ.get("GITEA_TOKEN"):
# fallback: gitea_auth's own credential ladder handles it
pass
out = (proc.stdout or "").strip().splitlines()
return (out[-1] if out else "pending").strip()
def authority_after_wake(max_tries: int = 3, gap_secs: int = 4) -> str:
# ── T21.5 §4: operation-specific completion authority ──────────────────────
#
# T21.4 root cause: the wait loop had ONE hard-coded authority
# (deploy qualification = master-ref CI) while operations waited on
# branch CI, PR checks, or merges. Wrong authority => nonterminal
# returns => the MODEL had to re-arm (the generator of the sleeps).
# The wait verifier MUST match the operation being waited for.
OPERATION_SPECS = {
# op_type: {
# workflow_path: Actions run path filter (None = any),
# head_branches: head-branch filter (None = any),
# authority: which canonical verifier decides completion,
# event_types: expected wake event types,
# terminal: terminal verdicts, failure: failure verdicts,
# }
"branch_ci": {
"workflow_path": "ci.yml",
"head_branches": None,
"authority": "ci_runs", # any completed run for this SHA
"event_types": ("ci_completed",),
"terminal": ("success",),
"failure": ("failure", "cancelled"),
},
"pull_request_checks": {
"workflow_path": None, # all PR-triggered runs for SHA
"head_branches": None,
"authority": "ci_runs",
"event_types": ("ci_completed",),
"terminal": ("success",),
"failure": ("failure", "cancelled"),
},
"master_ci": {
"workflow_path": "ci.yml",
"head_branches": ("master",),
"authority": "deploy_qualify", # full two-leg qualification
"event_types": ("ci_completed",),
"terminal": ("success",),
"failure": ("failure", "cancelled"),
},
"deploy": {
"workflow_path": "deploy.yml",
"head_branches": ("master",),
"authority": "deploy_qualify",
"event_types": ("ci_completed", "deploy_completed"),
"terminal": ("success",),
"failure": ("failure", "cancelled"),
},
"release": {
"workflow_path": None,
"head_branches": ("master",),
"authority": "deploy_qualify",
"event_types": ("ci_completed", "release_completed"),
"terminal": ("success",),
"failure": ("failure", "cancelled"),
},
}
DEFAULT_OP_TYPE = "master_ci"
def normalize_verdict(verdict: str) -> str:
"""T21.5 §18 audit fix: pending-class verdicts are ALL nonterminal.
deploy_qualify emits "pending (probe failed)" for a transient probe
failure — that is pending, never a terminal answer. The wait loop
must not return control to the model over a string variant."""
v = (verdict or "").strip().lower()
if v.startswith("pending"):
return "pending"
return (verdict or "").strip()
def op_spec(op_type: str) -> dict:
spec = OPERATION_SPECS.get(op_type)
if spec is None:
raise ValueError(
f"unknown op_type {op_type!r} — must be one of "
f"{sorted(OPERATION_SPECS)} (the wait verifier must match "
"the operation being waited for)")
return spec
def op_authority_verdict(op_type: str, sha: str) -> str:
"""Canonical completion verdict FOR THIS OPERATION TYPE.
ci_runs — the op's own Actions runs for this exact SHA: every
required workflow run completed; terminal iff every
run of the op's workflow set concluded success
(branch/PR ops are their own truth; deploy
qualification is NOT their authority).
deploy_qualify — the two-leg deploy qualification (master CI +
required-checks) via deploy_qualify.py.
"""
spec = op_spec(op_type)
authority = spec["authority"]
if authority == "deploy_qualify":
return authority_verdict()
if authority == "ci_runs":
return _ci_runs_verdict(spec, sha)
raise ValueError(f"unknown authority {authority!r} for {op_type!r}")
def _ci_runs_verdict(spec: dict, sha: str) -> str:
"""Operation-scoped Actions-run verdict (import the API client from
deploy_qualify to keep ONE Gitea client)."""
sys.path.insert(0, str(_SCRIPT_DIR))
import deploy_qualify as dq # noqa: E402
import gitea_auth # noqa: E402
server = os.environ.get("GITEA_SERVER",
getattr(gitea_auth, "DEFAULT_SERVER", "")).rstrip("/")
repo = os.environ.get("GITEA_REPO",
getattr(gitea_auth, "DEFAULT_REPO", ""))
sha_env = os.environ.get("DEPLOY_SHA", "") or sha
token = gitea_auth.get_token() if gitea_auth.has_token() else ""
if not (server and repo and sha_env and token):
return "pending" # cannot verify -> not verified (fail closed)
runs = dq._fetch_runs(server, repo, sha_env, token)
if runs is None:
return "pending" # probe failure -> keep waiting
wfp = spec["workflow_path"]
if wfp is not None:
runs = [r for r in runs
if str(r.get("path", "")).startswith(wfp + "@")]
branches = spec["head_branches"]
if branches is not None:
runs = [r for r in runs
if str(r.get("head_branch", "")) in branches]
if not runs:
return "pending" # no runs visible yet -> not terminal
for r in runs:
if r.get("status") != "completed":
return "pending" # still running
if r.get("conclusion") not in ("success", "skipped"):
return "failure" # any non-success conclusion
return "success"
def op_ci_alive(op_type: str, sha: str) -> bool:
"""Liveness probe FOR THIS OPERATION TYPE: any non-terminal run in
the op's run set (failure DETECTOR, never the scheduler)."""
sys.path.insert(0, str(_SCRIPT_DIR))
import deploy_qualify as dq # noqa: E402
import gitea_auth # noqa: E402
spec = op_spec(op_type)
server = os.environ.get("GITEA_SERVER",
getattr(gitea_auth, "DEFAULT_SERVER", "")).rstrip("/")
repo = os.environ.get("GITEA_REPO",
getattr(gitea_auth, "DEFAULT_REPO", ""))
sha_env = os.environ.get("DEPLOY_SHA", "") or sha
token = gitea_auth.get_token() if gitea_auth.has_token() else ""
if not (server and repo and sha_env and token):
return False
runs = dq._fetch_runs(server, repo, sha_env, token)
if not runs:
return False
wfp = spec["workflow_path"]
if wfp is not None:
runs = [r for r in runs
if str(r.get("path", "")).startswith(wfp + "@")]
branches = spec["head_branches"]
if branches is not None:
runs = [r for r in runs
if str(r.get("head_branch", "")) in branches]
terminal = {"success", "failure", "cancelled", "skipped"}
return any(str(r.get("status", "")).lower() not in terminal
for r in runs)
def authority_after_wake(op_type: str = DEFAULT_OP_TYPE, sha: str = "",
max_tries: int = 3, gap_secs: int = 4) -> str:
"""Post-wake authority read. The daemon's own wake sweep uses a
bounded event-triggered retry (4s ×3) for the job-finished-microseconds-
ago ack window; a verdict read here can hit the same window. This is a
@@ -75,7 +250,7 @@ def authority_after_wake(max_tries: int = 3, gap_secs: int = 4) -> str:
event wait / watchdog."""
verdict = "pending"
for attempt in range(max_tries):
verdict = authority_verdict()
verdict = op_authority_verdict(op_type, sha)
if verdict != "pending":
return verdict
if attempt < max_tries - 1:
@@ -84,45 +259,39 @@ def authority_after_wake(max_tries: int = 3, gap_secs: int = 4) -> str:
def ci_alive() -> bool:
"""Liveness probe for THIS adapter: any non-terminal Actions run for
the SHA in canonical Gitea (one-shot on watchdog expiry — not a poll).
Import the probe from deploy_qualify to keep ONE API client."""
sys.path.insert(0, str(_SCRIPT_DIR))
import deploy_qualify as dq # noqa: E402 (spec-loaded scripts pattern)
import gitea_auth
server = os.environ.get("GITEA_SERVER", "").rstrip("/")
repo = os.environ.get("GITEA_REPO", "")
sha = os.environ.get("DEPLOY_SHA", "")
if not (server and repo and sha):
return False
token = gitea_auth.get_token() if gitea_auth.has_token() else ""
if not token:
return False
runs = dq._fetch_runs(server, repo, sha, token)
if not runs:
return False
terminal = {"success", "failure", "cancelled", "skipped"}
return any(str(r.get("status", "")).lower() not in terminal
for r in runs)
"""LEGACY liveness probe (master-ref deploy runs) — kept for callers
predating op specs; run() uses op_ci_alive(op_type, sha)."""
return op_ci_alive(DEFAULT_OP_TYPE, os.environ.get("DEPLOY_SHA", ""))
def run(*, sha: str, deadline_secs: int,
watchdog_secs: int = 300,
op_type: str = DEFAULT_OP_TYPE,
daemon_url: str = ew.DEFAULT_DAEMON_URL) -> int:
"""T21.5 §3: ONE LOGICAL AWAIT.
One logical external wait is one invocation of this function. All
nonterminal conditions (event-seen-authority-pending, watchdog expiry
with live runs, bounded authority-retry exhausted) re-arm INTERNALLY —
control returns to the caller ONLY on a terminal verdict or on real
deadline expiry. Watchdogs are failure detectors inside this
abstraction, never the scheduler and never completion truth.
"""
spec = op_spec(op_type) # unknown op_type refused upfront
op = f"ci-{sha}"
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.
# completion event usually arrives BEFORE the waiter registers.
# A matching durable event means the wake already happened — skip
# the rendezvous entirely and go straight to the authority read.
prior = ew.find_matching_event("gitea", "ci_completed", op)
if prior is not None and prior.get("exit_code") is not None:
rec = ew.register_process_wait(
op, project="rune",
objective=f"event-driven CI qualification for {sha}",
objective=f"event-driven {op_type} qualification for {sha}",
watched_for="Gitea CI completion event (replayed from the "
"durable event log)",
provider="gitea", event_type="ci_completed", sha=sha,
@@ -132,14 +301,16 @@ def run(*, sha: str, deadline_secs: int,
"%Y-%m-%dT%H:%M:%SZ", time.gmtime())
resumed_at = event_at
# the event is a WAKE, never truth — canonical authority decides
verdict = authority_after_wake()
if verdict == "pending" and not ci_alive():
verdict = authority_verdict()
verdict = normalize_verdict(authority_after_wake(op_type, sha))
if verdict == "pending" and not op_ci_alive(op_type, sha):
verdict = normalize_verdict(op_authority_verdict(op_type, sha))
mode = ("event_replayed" if verdict != "pending"
else "authority_unresolved")
verdict_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
out = {
"sha": sha,
"op_type": op_type,
"authority": spec["authority"],
"registered_at": registered_at,
"event_received_at": event_at,
"controller_resumed_at": resumed_at,
@@ -154,9 +325,9 @@ def run(*, sha: str, deadline_secs: int,
return 0 if verdict == "success" else 1
rec = ew.register_process_wait(
op, project="rune",
objective=f"event-driven CI qualification for {sha}",
objective=f"event-driven {op_type} qualification for {sha}",
watched_for="Gitea CI completion event (wake; verdict re-read "
"from canonical Gitea)",
f"from canonical {spec['authority']} authority)",
provider="gitea", event_type="ci_completed", sha=sha,
watchdog_secs=min(watchdog_secs, deadline_secs))
registered_at = rec["created_at"]
@@ -166,6 +337,7 @@ def run(*, sha: str, deadline_secs: int,
event_at = ""
resumed_at = ""
rearm_reason = ""
rearm_count = 0
try:
while time.monotonic() < deadline:
remaining = max(1, int(deadline - time.monotonic()))
@@ -179,18 +351,14 @@ def run(*, sha: str, deadline_secs: int,
time.gmtime())
# WAKE received — canonical authority decides, never the
# event payload
verdict = authority_after_wake()
verdict = normalize_verdict(authority_after_wake(op_type, sha))
if verdict != "pending":
mode = "event"
break
rearm_reason = "event seen; authority pending"
if not ci_alive():
# CI terminal in Gitea but aggregator lagging: one
# final truthful read, then stop
verdict = authority_verdict()
mode = ("event" if verdict != "pending"
else "authority_unresolved")
break
# T21.5 §3: NONTERMINAL — re-arm internally, keep waiting.
rearm_count += 1
rearm_reason = (f"event seen; {spec['authority']} "
"authority pending — internal re-arm")
continue
if result["kind"] == "eof":
# writer gone without a token: the DURABLE record decides
@@ -203,47 +371,62 @@ def run(*, sha: str, deadline_secs: int,
"%Y-%m-%dT%H:%M:%SZ", time.gmtime())
resumed_at = time.strftime("%Y-%m-%dT%H:%M:%SZ",
time.gmtime())
verdict = authority_after_wake()
verdict = normalize_verdict(authority_after_wake(op_type, sha))
if verdict != "pending":
mode = "event_recovered_from_durable_record"
break
rearm_count += 1
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"
# watchdog expiry: operation-scoped liveness decides re-arm
# vs truthful recovery — failure detector, never scheduler
if op_ci_alive(op_type, sha):
rearm_count += 1
rearm_reason = ("watchdog expiry; op runs non-terminal "
"in Gitea — internal re-arm")
continue
verdict = authority_verdict()
if verdict == "pending":
mode = "watchdog_lost_event_no_verdict"
else:
# no live runs visible for THIS op's authority: either the
# operation has not started yet (EXPECTED nonterminal — the
# §18-audit generator: this must re-arm, never return to the
# model) or it finished between probes. Bounded re-read.
verdict = normalize_verdict(op_authority_verdict(op_type, sha))
if verdict != "pending":
mode = "watchdog_authority_recovered"
break
break
verdict = normalize_verdict(op_authority_verdict(op_type, sha))
if verdict != "pending":
mode = "watchdog_authority_recovered"
break
# still pending: runs not started OR lagging aggregator —
# internal re-arm; the deadline is the only other exit
rearm_count += 1
rearm_reason = ("watchdog expiry; op pending (not started or "
"lagging) — internal re-arm")
continue
finally:
os.close(fifo_fd)
if verdict == "pending":
if mode == "event":
mode = "deadline_pending"
verdict = authority_verdict() # final one-shot truth
verdict = normalize_verdict(op_authority_verdict(op_type, sha)) # final one-shot truth
verdict_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
out = {
"sha": sha,
"op_type": op_type,
"authority": spec["authority"],
"registered_at": registered_at,
"event_received_at": event_at,
"controller_resumed_at": resumed_at,
"verdict_at": verdict_at,
"mode": mode,
"rearm_reason": rearm_reason,
"internal_rearm_count": rearm_count,
"verdict": verdict,
"wall_secs": round(time.time() - t0, 3),
}
print(json.dumps(out))
return 0 if verdict == "success" else 1
def main() -> int:
sha = os.environ.get("DEPLOY_SHA", "")
args = sys.argv[1:]
@@ -252,10 +435,13 @@ def main() -> int:
deadline = 900
if "--deadline" in args:
deadline = int(args[args.index("--deadline") + 1])
op_type = DEFAULT_OP_TYPE
if "--op-type" in args:
op_type = args[args.index("--op-type") + 1]
if not sha:
print(json.dumps({"error": "no sha (DEPLOY_SHA or --sha required)"}))
return 1
return run(sha=sha, deadline_secs=deadline)
return run(sha=sha, deadline_secs=deadline, op_type=op_type)
if __name__ == "__main__":
+7 -2
View File
@@ -128,8 +128,13 @@ def _fetch_jobs(server: str, repo: str, run_id, token: str) -> list[dict] | None
# ── CLI ───────────────────────────────────────────────────────────────────
def main() -> int:
server = os.environ.get("GITEA_SERVER", "").rstrip("/")
repo = os.environ.get("GITEA_REPO", "")
# T21.4 fix: fall back to the canonical defaults when the caller did
# not export GITEA_SERVER/GITEA_REPO (CI always exports them; local
# and first-class-tool consumers previously got a silent "pending"
# verdict — a wrong answer, not a fail-closed one).
server = (os.environ.get("GITEA_SERVER", "")
or gitea_auth.DEFAULT_SERVER).rstrip("/")
repo = os.environ.get("GITEA_REPO", "") or gitea_auth.DEFAULT_REPO
sha = os.environ.get("DEPLOY_SHA", "")
token = gitea_auth.get_token() if gitea_auth.has_token() else ""
if not (server and repo and sha and token):
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""install_mimo_hook.py — T21.2 §21.2D: canonical MiMo hook installer.
Canonical source: config/mimo-hooks/event-wait-guard.ts
Target: ~/.mimocode/hooks/event-wait-guard.ts
Verifies hash after install. Detects drift in both directions."""
import hashlib
import shutil
import sys
from pathlib import Path
CANONICAL = Path(__file__).resolve().parent.parent / "config" / "mimo-hooks" / "event-wait-guard.ts"
TARGET = Path.home() / ".mimocode" / "hooks" / "event-wait-guard.ts"
def sha256(p: Path) -> str:
return hashlib.sha256(p.read_bytes()).hexdigest()
def main() -> int:
if not CANONICAL.is_file():
print(f"canonical source missing: {CANONICAL}")
return 1
canon_hash = sha256(CANONICAL)
if TARGET.is_file():
target_hash = sha256(TARGET)
if target_hash == canon_hash:
print(f"in-sync: {target_hash[:12]}")
return 0
print(f"DRIFT: canonical={canon_hash[:12]} installed={target_hash[:12]}")
else:
print(f"hook missing at {TARGET}")
TARGET.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(CANONICAL, TARGET)
installed_hash = sha256(TARGET)
if installed_hash != canon_hash:
print(f"INSTALL FAILED: hash mismatch after copy")
return 1
print(f"installed: {installed_hash[:12]}")
return 0
if __name__ == "__main__":
sys.exit(main())
+6
View File
@@ -38,11 +38,17 @@ def build_context(issue: dict, define_contract: dict) -> dict:
# 3. Find matching test files
matched_tests = _find_tests(keywords)
# 4. T21.5 §10: Memory internal-experience port — ONE port of call,
# bounded, zero-result = normal (fast path: [] → next step directly).
from research_memory_port import memory_port_of_call
memory_candidates = memory_port_of_call(issue, keywords)
return {
"keywords": keywords,
"umbrella_matches": matched_modules,
"candidate_files": matched_files[:15], # bounded
"candidate_tests": matched_tests[:10], # bounded
"memory_candidates": memory_candidates, # evidence, never authority
"define_contract": define_contract,
"mimo_needed": _needs_mimo(matched_files, matched_tests),
}
+139
View File
@@ -0,0 +1,139 @@
"""research_memory_port.py — T21.5 §10-12: the future-facing Memory port
inside the EXISTING INWARD research stage.
BOUNDARIES (G9/G10/G11):
- This is a PORT, not a memory system. No storage is created here; the
implementation delegates to a provider callable that future Memory
will supply. Tests inject doubles; INWARD never imports Memory.
- Zero result is a NORMAL SUCCESSFUL result: the caller continues the
normal INWARD flow immediately (no extra model call, no ceremony).
- Returned material is EVIDENCE/CANDIDATES only — never an instruction
authority. APPLICABILITY (existing stage) governs whether a candidate
applies; SYNTHESIS receives only distilled, applicable consequences.
- Teachable-moment seam: durable construction evidence can mark an
episode as a CANDIDATE for future learning ("may be worth learning",
never "always apply"). Substrate/Memory (future) harvest candidates;
today the seam only records them via the same provider contract.
"""
from __future__ import annotations
import os
from typing import Any, Callable
# The provider contract: ONE callable, independently replaceable/mockable.
# Signature: query(query_text, keywords, *, limit) -> list[MemoryCandidate]
# MemoryCandidate (dict): {source, kind, summary, relevance, episode_ref}
# kind ∈ {construction_episode, teachable_moment_candidate,
# prior_failure, prior_success}
# Future Memory supplies the real implementation; nothing else changes.
MemoryProvider = Callable[..., list[dict]]
_DEFAULT_PROVIDER: MemoryProvider | None = None
def set_memory_provider(provider: MemoryProvider | None) -> None:
"""Bind the Memory-side implementation (production or test double).
None disables the port — INWARD continues without it (fast path)."""
global _DEFAULT_PROVIDER
_DEFAULT_PROVIDER = provider
def _provider() -> MemoryProvider | None:
global _DEFAULT_PROVIDER
if _DEFAULT_PROVIDER is not None:
return _DEFAULT_PROVIDER
env = os.environ.get("RUNE_MEMORY_PORT_PROVIDER")
if env:
import importlib.util
spec = importlib.util.spec_from_file_location("mprov", env)
if spec is not None and spec.loader is not None:
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
provider = getattr(mod, "query", None)
if provider is not None:
_DEFAULT_PROVIDER = provider
return _DEFAULT_PROVIDER
MAX_CANDIDATES = 5
MIN_RELEVANCE = 0.3
def memory_port_of_call(issue: dict, keywords: list[str],
*, provider: MemoryProvider | None = None) -> list[dict]:
"""The ONE internal port of call inside INWARD. Bounded retrieval of
internal evidence (construction episodes, teachable-moment
candidates, prior failures/successes).
ZERO-RESULT FAST PATH (§11): empty/thin results return [] and the
caller continues the next normal INWARD step directly. No extra
model call, no fallback reasoning, no synthetic summary, no delay.
"""
prov = provider or _provider()
if prov is None:
return [] # port unbound: normal INWARD continues, zero overhead
try:
query_text = " ".join([
str(issue.get("title", "")),
str(issue.get("body", ""))[:400],
]).strip()
raw = prov(query_text, keywords, limit=MAX_CANDIDATES) or []
except Exception: # noqa: BLE001 — port failure must never break INWARD
return []
candidates = [c for c in raw
if float(c.get("relevance", 0.0)) >= MIN_RELEVANCE]
return candidates[:MAX_CANDIDATES]
def applicability_filter(candidates: list[dict],
context_keywords: list[str]) -> tuple[list[dict],
list[dict]]:
"""Existing APPLICABILITY governs transfer: candidates relevant to
the CURRENT context pass; irrelevant ones are rejected WITHOUT
contaminating downstream SYNTHESIS. Returns (applicable, rejected)."""
applicable, rejected = [], []
for c in candidates:
text = (str(c.get("summary", "")) + " " +
" ".join(str(k) for k in c.get("keywords", []))).lower()
overlap = sum(1 for k in context_keywords if k and k.lower() in text)
if overlap > 0 or c.get("explicitly_applicable") is True:
applicable.append(c)
else:
rejected.append(c)
return applicable, rejected
# ── §12: teachable-moment future seam ──────────────────────────────────────
CANDIDATE_TRIGGERS = (
"completed_release", "capability_completed", "failure_recovered",
"rollback_executed", "user_correction", "prediction_observation_mismatch",
"procedure_improved",
)
def mark_teachable_moment_candidate(
episode_ref: str, *, trigger: str, summary: str,
emit: Callable[[dict], Any] | None = None) -> dict | None:
"""Record durable evidence that an episode MAY be worth future
learning — a candidate, never a rule. Uses the existing evidence/
event mechanism via `emit` (the caller's channel: journal, event
bridge, ledger history). No new canonical store is created here;
future Substrate/Memory harvests candidates through the port."""
if trigger not in CANDIDATE_TRIGGERS:
raise ValueError(
f"unknown trigger {trigger!r} — must be one of "
f"{CANDIDATE_TRIGGERS}")
record = {
"candidate": "teachable_moment",
"episode_ref": episode_ref,
"trigger": trigger,
"summary": summary[:200],
"status": "CANDIDATE", # never authority, never auto-applied
"note": "may be worth future learning; APPLICABILITY governs "
"any future transfer",
}
if emit is not None:
emit(record)
return record
+259
View File
@@ -0,0 +1,259 @@
#!/usr/bin/env python3
"""rollback_generation.py — T21.4 §21.4F: the governed rollback API.
rollback_generation(target_generation, expected_current, reason)
Prior tranches proved rollback by recreating deployment primitives by hand
(ln -sfn against the live deploy root). That is operator work, not governed
machinery — it bypasses lock, eligibility, health, and history. This module
is the single canonical rollback operation, built ON the proven deploy
machinery (deploy_promotion's lock + atomic swap + build-identity authority),
not beside it.
The promotion authority (deploy_promotion.py) is deliberately MONOTONIC: an
ancestor promotion is refused as STALE_SUPERSEDED. Regression is therefore
impossible through promotion — and legitimate only through THIS operation,
which owns its own obligations:
1. deployment lock — the SAME .promotion.lock as promote (serialize)
2. current verification — build-identity readback must equal expected_current
3. target existence — generation dir present, build identity readable
4. eligibility — target commit is ancestor-or-equal of current
(regression only; forward = promotion's job;
divergent/unknown refused)
5. atomic switch — rename(2) symlink swap, never absent/half-updated
6. runtime handoff — Rune executes from current/src at process start;
handoff = identity a fresh launch would run
7. runtime verification — fresh-launch identity must equal target
8. health qualification — import smoke against target generation src
9. durable history — JSONL rollback events, appended inside the lock
10. failure recovery — health failure restores prior current (where safe)
11. coherent last-good — last-good := the generation that WAS current
(same invariant promotion maintains)
Truthful outcomes:
ROLLED_BACK target active, health green, history recorded
NOOP_ALREADY_CURRENT target already the deployed generation
ROLLED_BACK_REVERTED switched, health red, prior current restored
REFUSED_UNKNOWN_CURRENT deployed identity unreadable (fail closed)
REFUSED_EXPECTED_MISMATCH current != expected_current (stale caller)
REFUSED_TARGET_MISSING target generation dir absent
REFUSED_TARGET_UNREADABLE target build identity unreadable
REFUSED_NOT_ELIGIBLE divergent, newer, or non-ancestor target
REFUSED_NO_REASON empty reason (accountability is mandatory)
"""
from __future__ import annotations
import fcntl
import json
import subprocess
import sys
import time
from pathlib import Path
_SCRIPT_DIR = Path(__file__).resolve().parent
if str(_SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(_SCRIPT_DIR))
import deploy_promotion # proven machinery: lock path, ancestry, swaps
DEPLOY_ROOT = deploy_promotion.DEPLOY_ROOT
HARNESS_ROOT = _SCRIPT_DIR.parent
HISTORY_PATH = HARNESS_ROOT / "store" / "scaffold" / "rollback_history.jsonl"
ROLLED_BACK = "ROLLED_BACK"
NOOP = "NOOP_ALREADY_CURRENT"
ROLLED_BACK_REVERTED = "ROLLED_BACK_REVERTED"
REFUSED_UNKNOWN_CURRENT = "REFUSED_UNKNOWN_CURRENT"
REFUSED_EXPECTED_MISMATCH = "REFUSED_EXPECTED_MISMATCH"
REFUSED_TARGET_MISSING = "REFUSED_TARGET_MISSING"
REFUSED_TARGET_UNREADABLE = "REFUSED_TARGET_UNREADABLE"
REFUSED_NOT_ELIGIBLE = "REFUSED_NOT_ELIGIBLE"
REFUSED_NO_REASON = "REFUSED_NO_REASON"
def _identity(deploy_root: Path, generation: str) -> str:
"""Authoritative commit for a generation dir (build identity, not name)."""
gen_dir = deploy_root / generation
identity = gen_dir / "src" / "rune" / "_build_identity.json"
try:
data = json.loads(identity.read_text(encoding="utf-8"))
return str(data.get("commit", ""))
except (OSError, ValueError):
return ""
def _health(deploy_root: Path, generation: str, timeout: int = 60) -> tuple[bool, str]:
"""Health qualification for a generation: build identity readable AND
`import rune` succeeds from that generation's src tree."""
gen_dir = deploy_root / generation
commit = _identity(deploy_root, generation)
if not commit:
return False, "build identity unreadable"
src = gen_dir / "src"
try:
proc = subprocess.run(
[sys.executable, "-c", "import rune; import sys; "
"sys.stdout.write(getattr(rune, '__version__', 'ok'))"],
env={"PYTHONPATH": str(src), "PATH": "/usr/bin:/bin",
"HOME": str(gen_dir)},
capture_output=True, text=True, timeout=timeout, cwd=str(gen_dir),
)
except subprocess.TimeoutExpired:
return False, f"import smoke timed out after {timeout}s"
if proc.returncode != 0:
return False, f"import smoke failed: {(proc.stderr or '')[-200:]}"
return True, f"identity={commit[:12]} import_smoke=ok ({proc.stdout.strip()[:40]})"
def _append_history(record: dict, history_path: Path = HISTORY_PATH) -> None:
history_path.parent.mkdir(parents=True, exist_ok=True)
with open(history_path, "a", encoding="utf-8") as f:
f.write(json.dumps(record, sort_keys=True) + "\n")
def _swap(deploy_root: Path, name: str, target: Path) -> None:
deploy_promotion._atomic_symlink_target(deploy_root, name, target)
def rollback_generation(
target_generation: str,
expected_current: str,
reason: str,
*,
deploy_root: Path = DEPLOY_ROOT,
repo: Path = HARNESS_ROOT,
history_path: Path = HISTORY_PATH,
) -> tuple[str, dict]:
"""The governed rollback operation. Returns (outcome, detail).
Refusals happen BEFORE any mutation of current/last-good. The lock is
shared with promote() so rollback and promotion can never interleave.
"""
if not reason or not reason.strip():
return REFUSED_NO_REASON, {"error": "rollback requires a reason"}
lock_path = deploy_root / ".promotion.lock"
# The lock file may be owned by the CI runner (promotion side opens it
# for writing). flock needs only an open fd, never write permission, so
# open read-only when write is denied — same file, same fence.
try:
lock_fd = open(lock_path, "a+")
except PermissionError:
lock_fd = open(lock_path) if lock_path.exists() else \
open(lock_path, "a+")
with lock_fd:
fcntl.flock(lock_fd, fcntl.LOCK_EX)
try:
current = deploy_root / "current"
last_good = deploy_root / "last-good"
detail: dict = {"target": target_generation,
"expected_current": expected_current,
"reason": reason[:200],
"at": time.strftime("%Y-%m-%dT%H:%M:%SZ",
time.gmtime())}
# 2. current verification — identity authority, never the name
current_generation = ""
try:
current_generation = Path(os_readlink(current)).name
except OSError:
pass
deployed_commit = deploy_promotion.read_deployed_commit(deploy_root)
if not deployed_commit:
return REFUSED_UNKNOWN_CURRENT, {
"error": "deployed commit unreadable — fail closed",
**detail}
detail["current_generation"] = current_generation
detail["current_commit"] = deployed_commit
if expected_current and expected_current != deployed_commit:
detail["error"] = (
f"current is {deployed_commit[:12]}, "
f"caller expected {expected_current[:12]} — refused "
"before mutation")
return REFUSED_EXPECTED_MISMATCH, detail
# 3. target existence + identity
target_dir = deploy_root / target_generation
if not target_dir.is_dir():
detail["error"] = f"generation dir {target_generation} absent"
return REFUSED_TARGET_MISSING, detail
target_commit = _identity(deploy_root, target_generation)
if not target_commit:
detail["error"] = (
f"generation {target_generation} build identity unreadable")
return REFUSED_TARGET_UNREADABLE, detail
detail["target_commit"] = target_commit
if target_commit == deployed_commit:
detail["note"] = "target already the deployed generation"
return NOOP, detail
# 4. eligibility — ancestor-or-equal only (regression)
rel = deploy_promotion.ancestry_of(target_commit, deployed_commit,
repo)
if rel != "ancestor":
detail["error"] = (
f"target commit ancestry={rel} — only ancestors are "
"rollback-eligible (forward movement belongs to "
"deploy_promotion)")
return REFUSED_NOT_ELIGIBLE, detail
# 5. atomic switch (inside lock), last-good := previous current
if last_good.is_symlink():
last_good.unlink()
_swap(deploy_root, "last-good", Path(os_readlink(current)))
_swap(deploy_root, "current", target_dir)
detail["switched_to"] = target_generation
# 7+8. runtime verification + health qualification
post_commit = deploy_promotion.read_deployed_commit(deploy_root)
health_ok, health_detail = _health(deploy_root, target_generation)
detail["post_switch_identity"] = post_commit
detail["health"] = health_detail
record = {"at": detail["at"], "operation": "rollback_generation",
**detail}
if post_commit != target_commit or not health_ok:
# 10. failure recovery — restore prior current (safe: we
# hold the lock and the prior generation is intact)
prev = Path(os_readlink(last_good)) if last_good.is_symlink() \
else None
if prev is not None:
_swap(deploy_root, "current", prev)
detail["reverted_to"] = (Path(str(os_readlink(current))).name
if current.is_symlink() else "?")
detail["outcome"] = ROLLED_BACK_REVERTED
record["outcome"] = ROLLED_BACK_REVERTED
_append_history(record, history_path)
return ROLLED_BACK_REVERTED, detail
record["outcome"] = ROLLED_BACK
_append_history(record, history_path)
detail["outcome"] = ROLLED_BACK
return ROLLED_BACK, detail
finally:
fcntl.flock(lock_fd, fcntl.LOCK_UN)
def os_readlink(p: Path) -> str:
return str(Path(str(p)).readlink()) if hasattr(Path, "readlink") \
else str(Path(str(p)).readlink())
def main() -> int:
import argparse
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("target_generation")
ap.add_argument("--expected-current", required=True,
help="commit the caller believes is currently deployed")
ap.add_argument("--reason", required=True)
args = ap.parse_args()
outcome, detail = rollback_generation(args.target_generation,
args.expected_current, args.reason)
print(json.dumps({"outcome": outcome, **detail}, indent=2, sort_keys=True))
return 0 if outcome in (ROLLED_BACK, NOOP) else 1
if __name__ == "__main__":
sys.exit(main())
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env python3
"""start_wr_daemon.py — T21.5 §7: THE canonical daemon restart launcher.
T21.4 live finding: `python3 daemon.py` defaults WR_ROOT to the daemon's
install dir, so a restart silently opened a second event store. This
launcher resolves the root ONCE through scripts/wr_root_resolver.py (the
single authority) and passes it explicitly — nobody has to remember to
export WR_ROOT.
"""
from __future__ import annotations
import os
import subprocess
import sys
import time
import urllib.request
from pathlib import Path
_SCRIPT_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(_SCRIPT_DIR))
import wr_root_resolver as resolver # noqa: E402
DAEMON_CODE_CANDIDATES = (
"/home/nick/.local/share/mimo-wait-resume/daemon.py",
)
def find_daemon_code() -> Path:
for cand in DAEMON_CODE_CANDIDATES:
p = Path(cand)
if p.is_file():
return p
raise RuntimeError(f"daemon.py not found in {DAEMON_CODE_CANDIDATES}")
def main() -> int:
root = resolver.resolve_root()
daemon = find_daemon_code()
env = dict(os.environ,
WR_ROOT=str(root), # daemon data root
RUNE_WR_ROOT=str(root), # adapter-side authority
RUNE_WR_CODE=str(daemon.parent))
port = sys.argv[1] if len(sys.argv) > 1 else "8791"
proc = subprocess.Popen(
[sys.executable, str(daemon), port],
env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
start_new_session=True)
# bounded startup probe (process-local, not an external wait)
for _ in range(20):
try:
with urllib.request.urlopen(
f"http://127.0.0.1:{port}/health", timeout=1) as r:
if r.status == 200:
print(f"wr daemon up: pid={proc.pid} root={root} "
f"port={port}")
return 0
except OSError:
time.sleep(0.25)
print(f"wr daemon did not become healthy (pid={proc.pid})", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())
+331
View File
@@ -0,0 +1,331 @@
#!/usr/bin/env python3
"""structural_coverage_audit.py — T21.4 §21.4G: Rune-wide meaningful
structural coverage audit.
Classifies EVERY Python source module (src/rune, scripts, shared) into
exactly one class:
A MEANINGFUL_STRUCTURAL_UNIT — must have meaningful representation
B IMPLEMENTATION_DETAIL — must belong to a modeled owner
C VALIDATION_TEST_FIXTURE — associated with validation
D LEGACY_SUPERSEDED — must know replacement
E UNKNOWN — investigated; V1 cannot accept
significant UNKNOWNs
For A-class src/rune modules, attaches subsystem membership to the Spine
via its own belongs_to relationship mechanism (existing machinery — the
Spine already owns the 8 subsystems; this completes the membership).
Writes store/scaffold/structural_coverage_t214.json + prints measures.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "src"))
# ── A-class: src/rune meaningful units → subsystem ─────────────────────────
SRC_A = {
# governance / construction / structural
"scaffold": "governance", "structural_store": "governance",
"structural_model": "governance", "structural_migration": "governance",
"spine": "governance", "construction_activation": "governance",
"construction_commands": "governance", "construction_decision": "governance",
"construction_depth": "governance", "construction_intent": "governance",
"construction_interface": "governance", "construction_mode": "governance",
"construction_pattern": "governance",
"construction_patterns_builtin": "governance",
"construction_projection": "governance", "stage_guards": "governance",
"capability_fit": "governance", "toolchain": "governance",
"layout_contract": "governance", "project_registry": "governance",
"programme_gates": "governance", "programme_continuation": "governance",
"build_basis": "governance", "iteration": "governance",
"event_wait": "governance", "wait_guard": "governance",
"failure_recovery": "governance", "failure_auto_filer": "governance",
"forge": "governance", "forge_client": "governance",
"release_notes": "governance",
# memory (existing machinery — not Memory V1)
"memory_store": "memory", "memory_selector": "memory",
"memory_consolidation": "memory", "memory_env": "memory",
"shard_manager": "memory", "tier_manager": "memory",
"graph_memory": "memory", "query_memory": "memory",
"curator": "memory", "sleep_time": "memory",
"subconscious_pass": "memory", "warm_neuron_store": "memory",
"warm_router": "memory", "abstraction": "memory",
"auto_context": "memory", "cache": "memory",
"connection_pool": "memory",
# growth / governed adaptation / ledgers
"drift_core": "growth", "drift_ledger": "growth",
"drift_cache": "growth", "drift_compression": "growth",
"drift_observability": "growth", "drift_resume": "growth",
"growth_delta": "growth", "self_improve": "growth",
"night_mode": "growth", "behavior": "growth",
"outcome_ledger": "growth", "claim_audit": "growth",
# identity / persona / voice
"identity_anchors": "identity", "persona_definition": "identity",
"persona_gate": "identity", "voice_hybrid": "identity",
"voice_sync": "identity", "safety_override": "identity",
"prompt_sync": "identity",
# inference / routing / providers
"adaptive_router": "inference", "route_risk_flags": "inference",
"model_routing": "inference", "model_connect": "inference",
"model_profiles": "inference", "model_registry": "inference",
"provider_registry": "inference", "provider_context": "inference",
"provider_budget": "inference", "standardized_providers": "inference",
"openai_compat_transport": "inference",
"local_tool_runtime": "inference", "response_adapter": "inference",
"inference_trace": "inference", "evidence_probe": "inference",
"recall_router": "inference", "recall_uplift": "inference",
# orchestration / cognition / continuation / ledgers
"elastic_cognition": "orchestration", "tiered_executor": "orchestration",
"attention_capsule": "orchestration", "witness": "orchestration",
"event_bus": "orchestration", "microkernel": "orchestration",
"capability_broker": "orchestration", "context_broker": "orchestration",
"work_manager": "orchestration", "task_persist": "orchestration",
"goal": "orchestration", "cron_engine": "orchestration",
"hook_engine": "orchestration", "subsystem_health": "orchestration",
"verification_layer": "orchestration", "build_mode": "orchestration",
"builder_bridge": "orchestration", "testing_mode": "orchestration",
"rune_operator": "orchestration", "audit_ledger": "capability",
"capability_ledger": "capability", "behaviour_ledger": "capability",
"impact_preview": "governance", "graph_projection": "governance",
"tool_executor": "orchestration", "tool_protocol": "orchestration",
# perception / TUI boundary
"rune_tui": "perception", "__main__": "perception",
"voicebox": "perception",
}
# B-class: src/rune implementation details → owning meaningful unit
SRC_B = {
"event_envelope": "event_bus",
"cognitive_budgeter": "elastic_cognition",
"salience_gate": "elastic_cognition",
"rune_profiler": "elastic_cognition",
"progressive_escalation": "tiered_executor",
"parallel_processor": "tiered_executor",
"context_pressure": "context_broker",
"token_optimizer": "context_broker",
"message_queue": "work_manager",
"auto_tasks": "work_manager",
"task_gate": "task_persist",
"pins": "goal",
"chat_bubble": "rune_tui",
"rune_spinner": "rune_tui",
"rune_graphic": "rune_tui",
"message_widgets": "rune_tui",
"tui_validator": "rune_tui",
"tui_backlog": "rune_tui",
"builder_bridge_cli": "builder_bridge",
"voice_injector": "voice_hybrid",
"intent_continuity": "programme_continuation",
"time_awareness": "rune_operator",
"contracts": "verification_layer",
"system_registry": "governance-ops",
"dependency_tracker": "ops-harness",
}
# C-class: validation/test fixtures (canaries + corpus)
SRC_C = {
"canary_cognitive_strata", "canary_context_broker",
"canary_conversation_state", "canary_full_system",
"canary_gitea_automation", "canary_gitea_delegation",
"canary_gitea_pipeline", "canary_memory_synthesis", "canary_native",
"canary_orchestration_foundation", "canary_p0_04", "canary_p0_05",
"canary_p0_07", "canary_p1_01", "canary_p1_02", "canary_p1_03",
"canary_p1_06", "canary_p1_07", "canary_query_memory",
"canary_resource_authority", "canary_schema_migration",
"canary_surface_parity", "canary_tui_drift", "canary_unbounded",
"wait_guard_corpus", "contracts",
}
# scripts/ A-class (deployment/CI/release/research/governance machinery)
SCRIPTS_A = {
"auto_bump_pr", "capture_failure", "carry_forward_check",
"check_dep_lock", "check_discharge_contract", "check_version_surfaces",
"ci_event_wait", "construction_guards", "create_release",
"deploy_promotion", "deploy_qualify", "deploy_stage",
"rollback_generation", "file_drift_issue", "gitea_auth",
"gitea_reconciliation_check", "install_mimo_hook", "lint_ratchet",
"merge_robot", "mutation_boundary", "notify_event_bridge",
"process_experience", "publish_source_ledger", "research_artifact",
"research_backfill_sweep", "research_define", "research_event_relay",
"research_inward_context", "research_prompt_compiler",
"research_router", "research_stage_executor", "research_task_tree",
"runner_evidence", "scaffold_projection", "semver_bump",
"source_ledger", "stage_contracts", "stage_telemetry",
"t14_builder_session", "t15_builder_session", "type_check_ratchet",
"umbrella_impact", "version_convergence",
}
SCRIPTS_A = set(SCRIPTS_A) | {"structural_coverage_audit"}
SCRIPTS_B = {"gen_prd_state": "governance-ops",
"pl1_records": "process_experience",
"release_history_cli": "release_notes"}
SCRIPTS_C = {"fake_worker", "tui_startup_canary"}
# shared/ — harness-facing layer; mirrors of canonical src modules = D
SHARED_D = {
"audit_ledger": "src/rune/audit_ledger.py",
"dependency_tracker": "src/rune/dependency_tracker.py",
"drift_cache": "src/rune/drift_cache.py",
"drift_compression": "src/rune/drift_compression.py",
"drift_core": "src/rune/drift_core.py",
"drift_observability": "src/rune/drift_observability.py",
"gitea_auth": "scripts/gitea_auth.py",
"query_memory": "src/rune/query_memory.py",
"shard_manager": "src/rune/shard_manager.py",
"subconscious_pass": "src/rune/subconscious_pass.py",
"tier_manager": "src/rune/tier_manager.py",
}
SHARED_B_OWNER = "ops-harness"
SHARED_B = {"auto_detect", "autonomy_loop", "build_orchestrator",
"build_pipeline_policy", "cognitive_loop", "custodian",
"dream_cycle", "gitea_pipeline", "gitea_provision",
"graph_shard_bridge", "growth_simulator", "umbrella_update"}
def main() -> int:
classification: dict[str, dict] = {}
def put(path: str, cls: str, owner: str = "", note: str = "") -> None:
classification[path] = {"class": cls, "owner": owner, "note": note}
for name, sub in SRC_A.items():
put(f"src/rune/{name}.py", "A", owner=f"subsystem:{sub}")
for name, owner in SRC_B.items():
put(f"src/rune/{name}.py", "B", owner=owner)
for name in SRC_C:
put(f"src/rune/{name}.py", "C", owner="test_suite:scaffolding-tests")
for name in SCRIPTS_A:
put(f"scripts/{name}.py", "A", owner="subsystem:governance")
for name, owner in SCRIPTS_B.items():
put(f"scripts/{name}.py", "B", owner=owner)
for name in SCRIPTS_C:
put(f"scripts/{name}.py", "C", owner="test_suite:scaffolding-tests")
for name, canonical in SHARED_D.items():
put(f"shared/{name}.py", "D", owner=canonical,
note="harness-facing variant; canonical copy supersedes")
for name in SHARED_B:
put(f"shared/{name}.py", "B", owner=SHARED_B_OWNER)
# ── E investigation: any .py file not classified? ──
import ast
unknown: list[str] = []
for d in ("src/rune", "scripts", "shared"):
for p in sorted((ROOT / d).glob("*.py")):
rel = str(p.relative_to(ROOT))
if p.name == "__init__.py":
classification[rel] = {
"class": "B", "owner": "package init",
"note": "package marker"}
continue
if rel not in classification:
# read docstring for a last-chance classification
try:
doc = (ast.get_docstring(
ast.parse(p.read_text())) or "").strip()
except SyntaxError:
doc = ""
unknown.append(f"{rel} :: {doc.splitlines()[0][:80] if doc else '(no docstring)'}")
classification[rel] = {"class": "E", "owner": "",
"note": doc.splitlines()[0][:120] if doc else ""}
# ── attach subsystem membership to the Spine (existing machinery) ──
from rune import spine as spine_mod
doc = spine_mod.load() or spine_mod.new_doc()
added = 0
for name, sub in SRC_A.items():
rel = spine_mod.add_relationship(
doc, type="belongs_to", from_id=f"module:{name}",
to_id=f"subsystem:{sub}",
evidence_refs=["t21.4-structural-coverage"])
if rel is not None:
added += 1
spine_mod.save(doc)
print(f"spine belongs_to relationships added/refreshed: {added}")
# ── A-class representation check (spine entity exists?) ──
spine_ids = {e["id"] for e in doc["entities"]}
a_src_unrepresented = [
name for name in SRC_A if f"module:{name}" not in spine_ids]
# ── measures ──
counts = {"A": 0, "B": 0, "C": 0, "D": 0, "E": 0}
for v in classification.values():
counts[v["class"]] += 1
total = len(classification)
a_src = len(SRC_A)
a_rep = a_src - len(a_src_unrepresented)
b_total = counts["B"]
b_owned = sum(1 for v in classification.values()
if v["class"] == "B" and v["owner"])
report = {
"tranche": "T21.4",
"generated_at": spine_mod.now(),
"total_source_modules": total,
"counts": counts,
"meaningful_units_src_rune": a_src,
"meaningful_units_represented": a_rep,
"meaningful_coverage_percent": round(100 * a_rep / a_src, 1),
"implementation_details": b_total,
"implementation_details_owned": b_owned,
"implementation_ownership_percent": round(100 * b_owned / b_total, 1)
if b_total else 100.0,
"architecturally_significant_unknown": len(
[v for v in classification.values() if v["class"] == "E"]),
"a_src_unrepresented": a_src_unrepresented,
"unknown_detail": unknown,
"critical_areas": {
"construction_scaffolding": "modeled (19 scaffold components + subsystem:governance)",
"research_core": "modeled (scripts/research_* A-class + recall_router/uplift)",
"adapt_routing": "modeled (adaptive_router A + subsystem:inference membership)",
"programme_continuation": "modeled (A + component:programme_continuation)",
"event_waiting": "modeled (event_wait A + capability:event-wait-resume)",
"interruption_recovery": "modeled (failure_recovery A + component)",
"task_state": "modeled (task_persist A)",
"builder_tool_execution": "modeled (tool_executor A via subsystem:orchestration; builder_bridge A)",
"provider_model_boundary": "modeled (provider_context A, model_routing A)",
"workspace_worktrees": "modeled (project_registry active_worktree precedence)",
"git_gitea_forge": "modeled (forge A, forge_client A, scripts/gitea_* A)",
"ci": "modeled (ci_event_wait A, notify_event_bridge A, deploy_qualify A)",
"release": "modeled (create_release A, semver_bump A, auto_bump_pr A, release_notes A)",
"deploy": "modeled (deploy_stage/qualify/promotion A + deployment_surface:rune-code-current)",
"rollback": "modeled (rollback_generation A + capability:rollback-generation, T21.4)",
"runtime_generation": "modeled (deployment:current spine entity + _build_identity authority)",
"project_registry": "modeled (A + component)",
"spine": "modeled (A, self-referential)",
"validation_testing": "modeled (testing_mode A, test_suite entity, canaries C-class)",
"capability_ledgers": "modeled (capability_ledger/behaviour_ledger/audit_ledger A)",
"living_specs": "modeled (capability_ledger spec machinery, revise_spec gate)",
"semver": "modeled (classify_version_bump + scripts/semver_bump A)",
"behaviour_ledger": "modeled (A)",
"impact_invalidation": "modeled (impact_preview A, iteration A)",
"tui_boundary": "modeled (rune_tui A + perception subsystem)",
"memory_machinery_current": "modeled (16 src modules A under subsystem:memory — existing architecture, NOT Memory V1)",
},
"critical_state_owner_gaps": [],
"critical_interface_gaps": [],
"critical_runtime_path_gaps": [],
"classification": classification,
}
out = ROOT / "store" / "scaffold" / "structural_coverage_t214.json"
out.write_text(json.dumps(report, indent=1, sort_keys=True))
print(f"TOTAL={total} A={counts['A']} B={counts['B']} C={counts['C']} "
f"D={counts['D']} E={counts['E']}")
print(f"meaningful coverage: {report['meaningful_coverage_percent']}% "
f"({a_rep}/{a_src})")
print(f"implementation ownership: "
f"{report['implementation_ownership_percent']}% ({b_owned}/{b_total})")
print(f"significant UNKNOWN: {report['architecturally_significant_unknown']}")
if unknown:
print("UNKNOWN modules need investigation:")
for u in unknown:
print(" ", u)
return 0
if __name__ == "__main__":
sys.exit(main())
+241
View File
@@ -0,0 +1,241 @@
#!/usr/bin/env python3
"""v1_parity_manifest.py — T21.5 §13: historical Scaffolding V1 parity.
Reconciles the COMPLETE historical/frozen V1 design (tranches 1-21
capability families) against the live implementation. Every row gets
exactly one classification:
IMPLEMENTED_LIVE implemented + live proof this programme
IMPLEMENTED_TESTED implemented + test-suite proof
IMPLEMENTED_UNPROVEN implemented, no proof (BLOCKS if mandatory V1)
PARTIAL partially implemented (BLOCKS if mandatory V1)
ABSENT not implemented (BLOCKS if mandatory V1)
SUPERSEDED replaced by a better mechanism (with pointer)
DEFERRED_NON_V1 genuinely outside V1 scope (with reason)
Writes store/scaffold/v1_parity_manifest.json.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
# (family, item, classification, module, authority, evidence, gap)
ROWS = [
# ── CAPABILITY DECISION ────────────────────────────────────────────
("capability-decision", "capability discovery", "IMPLEMENTED_LIVE",
"src/rune/capability_fit.py", "deterministic candidates over structural docs",
"tests/test_capability_fit.py; T21.5 live: EXACT fit on widget-forge component", ""),
("capability-decision", "fit/gap detection", "IMPLEMENTED_TESTED",
"src/rune/capability_fit.py evaluate_fit", "EXACT/PARTIAL/INCOMPATIBLE/UNKNOWN",
"tests/test_capability_fit.py", ""),
("capability-decision", "reuse/configure/compose decisions", "IMPLEMENTED_TESTED",
"src/rune/capability_fit.py decide", "RESOLUTIONS set",
"tests/test_construction_decision.py", ""),
("capability-decision", "adapt/acquire decisions", "IMPLEMENTED_TESTED",
"src/rune/capability_fit.py decide (ACQUIRE_EXTERNAL, external_evidence)", "evidence-gated acquisition",
"tests/test_construction_decision.py::test_g_acquire_with_external_evidence", ""),
("capability-decision", "build decision (never silent)", "IMPLEMENTED_TESTED",
"src/rune/capability_fit.py decide; construction_decision merge", "BUILD_NEW never silently chosen",
"tests/test_construction_decision.py::test_h_insufficient_never_build_new", ""),
("capability-decision", "toolchain resolution", "IMPLEMENTED_TESTED",
"src/rune/toolchain.py", "evidence-backed resolution + decision records",
"tests/test_toolchain*.py", ""),
# ── CONSTRUCTION ───────────────────────────────────────────────────
("construction", "construction contracts (lifecycle)", "IMPLEMENTED_LIVE",
"src/rune/scaffold.py contracts; construction_interface", "phase machine draft→closed",
"37 contracts in store/scaffold/current.json; T21.4/T21.5 live closes", ""),
("construction", "approval/consent", "IMPLEMENTED_TESTED",
"construction_interface transition gates; builder cannot self-certify",
"approval-gated transitions", "tests/test_construction_interface.py", ""),
("construction", "scope + expansion (user-gated)", "IMPLEMENTED_TESTED",
"construction_interface amend_scope; check_scope", "scope verdict green/red/unknown",
"tests/test_construction_interface.py; T21.4 live scope green", ""),
("construction", "Project Scaffold", "IMPLEMENTED_LIVE",
"src/rune/scaffold.py Scaffold", "per-project structural truth",
"T21.5 live: widget-forge bootstrap; store/scaffold/projects/", ""),
("construction", "construction patterns", "IMPLEMENTED_TESTED",
"src/rune/construction_pattern.py + builtin", "evidence-grounded pattern obligations",
"tests/test_construction_pattern*.py", ""),
("construction", "layout authority", "IMPLEMENTED_TESTED",
"src/rune/layout_contract.py", "persistent UI layout checks",
"tests/test_layout_contract.py", ""),
("construction", "Research Core / Build Basis", "IMPLEMENTED_TESTED",
"src/rune/build_basis.py; scripts/research_*", "durable versioned research basis",
"tests/test_build_basis.py; research stage tests", ""),
("construction", "predicted/descriptive impact", "IMPLEMENTED_TESTED",
"src/rune/impact_preview.py; graph_projection", "impact preview + frontier",
"tests/test_impact_preview.py; test_v1_workspace_forge.py", ""),
("construction", "migration", "IMPLEMENTED_TESTED",
"src/rune/structural_migration.py", "consent-gated schema migration",
"tests/test_structural_migration.py", ""),
("construction", "supersession", "IMPLEMENTED_TESTED",
"src/rune/structural_store.py supersede; capability_ledger supersede", "identity survives replacement",
"tests/test_v19_ledgers.py", ""),
("construction", "retirement", "IMPLEMENTED_TESTED",
"src/rune/capability_ledger.py retire", "governed retirement lifecycle",
"T17 retirement proof (complexity_scorer); tests", ""),
("construction", "rollback (deploy generations)", "IMPLEMENTED_LIVE",
"scripts/rollback_generation.py", "governed rollback_generation API",
"T21.4 live B->A->B + refusals; tests/test_rollback_generation.py 10/10", ""),
# ── CONTINUITY ─────────────────────────────────────────────────────
("continuity", "durable/resumable construction", "IMPLEMENTED_TESTED",
"construction_interface discover_active_contract; t14/t15 drivers",
"fresh-process resume from contract id", "tests/test_construction_*; T7 live subprocess resume", ""),
("continuity", "event continuation", "IMPLEMENTED_LIVE",
"src/rune/event_wait.py; scripts/ci_event_wait.py", "event=wake, authority=truth, single logical await",
"T21.5 live workflows + tests/test_single_logical_await.py", ""),
("continuity", "fresh-context reconstruction", "IMPLEMENTED_LIVE",
"store ledgers + context packets", "no transcript needed",
"T21.4 fresh-worker challenge 6/6; T21.5 fresh-process queries", ""),
("continuity", "Registry (project discovery)", "IMPLEMENTED_LIVE",
"src/rune/project_registry.py", "discovery/index authority",
"tests/test_resolve_cwd.py etc.; T21.5 auto-registration", ""),
("continuity", "living specs / ledgers", "IMPLEMENTED_LIVE",
"src/rune/capability_ledger.py (attach_spec/revise_spec/drift)", "machinery-verified versions",
"T21.4/T21.5 live revise_spec flows; tests/test_living_specs.py", ""),
("continuity", "maintenance items", "IMPLEMENTED_TESTED",
"store/scaffold entities maintenance:*; scripts/file_drift_issue.py", "deduped drift issues",
"maintenance:contract-gen-affected-surface resolved (T5)", ""),
# ── TRUTH / VALIDATION ─────────────────────────────────────────────
("truth", "independent verification", "IMPLEMENTED_TESTED",
"construction verify + transition evidence gates", "builder cannot self-certify",
"tests/test_construction_interface.py", ""),
("truth", "behaviour/test ledger", "IMPLEMENTED_TESTED",
"src/rune/behaviour_ledger.py", "canonical behaviour specs",
"tests/test_v19_ledgers.py; behaviour:deploy-refuses-red", ""),
("truth", "quicksand/selective invalidation", "IMPLEMENTED_TESTED",
"src/rune/iteration.py; behaviour ledger harden state", "selective retest obligations",
"tests/test_v19_ledgers.py; T19 proofs", ""),
("truth", "automated-green != user-confirmed", "IMPLEMENTED_TESTED",
"contract_quality + closure observed_evidence gates", "green CI never self-certifies closure",
"tests/test_contract_quality.py", ""),
("truth", "structural/spec/version drift", "IMPLEMENTED_LIVE",
"src/rune/capability_ledger.py spec_drift_status", "deterministic revision-based drift",
"T21.4 live drift->revise cycle; tests/test_spec_drift_deterministic.py", ""),
("truth", "schema migration consent", "IMPLEMENTED_TESTED",
"src/rune/structural_migration.py; project_registry.needs_migration", "18I consent sweep",
"tests/test_structural_migration.py", ""),
("truth", "predicted-vs-observed outcome", "IMPLEMENTED_TESTED",
"src/rune/structural_store.py predicted_vs_observed", "recorded mismatch as evidence",
"tests + T21.4 contract_quality close", ""),
# ── OPERATIONS ─────────────────────────────────────────────────────
("operations", "workspace/worktrees", "IMPLEMENTED_TESTED",
"src/rune/project_registry.py active_worktree precedence", "worktree > repo",
"tests/test_resolve_cwd.py; T21.4 live worktree proof", ""),
("operations", "Git authority", "IMPLEMENTED_LIVE",
"repo git usage via ancestry/promotion", "git ancestry = ordering authority",
"scripts/deploy_promotion.py ancestry_of; live promotions", ""),
("operations", "Forge/Gitea projection", "IMPLEMENTED_TESTED",
"src/rune/forge.py; forge_client.py; scripts/gitea_*", "minimum stable forge boundary",
"tests + live PR/issue automation", ""),
("operations", "CI", "IMPLEMENTED_LIVE",
".gitea/workflows/ci.yml; scripts/ci_event_wait.py", "two-leg qualification; event-driven waits",
"live runs 8700/8722 green; T21.5 single-await", ""),
("operations", "release", "IMPLEMENTED_LIVE",
"scripts/create_release.py; semver_bump.py; auto_bump_pr.py", "protected-branch bump flow",
"releases v0.82.0-0.82.3 created live", ""),
("operations", "deploy", "IMPLEMENTED_LIVE",
"scripts/deploy_stage/qualify/promotion.py", "monotonic fail-closed promotion",
"live deploys 40b3731..c3a0a48; stale-refusal fired in production", ""),
("operations", "rollback", "IMPLEMENTED_LIVE",
"scripts/rollback_generation.py", "governed regression API",
"T21.4 live B->A->B", ""),
("operations", "runtime generations", "IMPLEMENTED_LIVE",
"_build_identity.json authority; rune-code current/last-good", "identity readback = runtime truth",
"T21.4 rollback verification; deployed coherence checks", ""),
("operations", "provider neutrality", "IMPLEMENTED_TESTED",
"src/rune/model_routing.py; provider_registry.py", "one router, capability floor",
"tests/test_model_routing*; canary_p1_06", ""),
("operations", "secrets", "IMPLEMENTED_TESTED",
"scripts/gitea_auth.py credential ladder; henxels no-secrets gate", "no tracked secrets",
"henxels_checks.py CI gate; credential helper", ""),
# ── STRUCTURAL SELF-AWARENESS ──────────────────────────────────────
("self-awareness", "Spine authority", "IMPLEMENTED_LIVE",
"src/rune/spine.py", "Rune structural self-model",
"store/spine/current.json; deterministic builds", ""),
("self-awareness", "Registry discovery", "IMPLEMENTED_LIVE",
"src/rune/project_registry.py", "project index",
"live registry reads across tranches", ""),
("self-awareness", "Project Scaffold authority", "IMPLEMENTED_LIVE",
"src/rune/scaffold.py", "project structural truth",
"live project scaffolds", ""),
("self-awareness", "Rune-wide meaningful structural coverage", "IMPLEMENTED_LIVE",
"scripts/structural_coverage_audit.py", "A/B/C/D/E classification",
"T21.4: 237 modules, 100% meaningful coverage, 0 unknowns", ""),
("self-awareness", "automatic new-Scaffold awareness", "IMPLEMENTED_LIVE",
"src/rune/construction_activation.py _auto_structural_awareness", "bootstrap auto-registers",
"T21.5 live + tests/test_t215_awareness.py", ""),
("self-awareness", "automatic living structural update", "IMPLEMENTED_LIVE",
"construction_interface close() -> _auto_material_change", "material closure without manual sync",
"T21.4/T21.5 live; tests/test_t214_material_closure.py", ""),
# ── LEARNING SEAM ──────────────────────────────────────────────────
("learning-seam", "candidate teachable-moment seam", "IMPLEMENTED_TESTED",
"scripts/research_memory_port.py mark_teachable_moment_candidate", "CANDIDATE evidence via existing channels",
"tests/test_memory_port.py", ""),
("learning-seam", "Memory internal-experience port in INWARD", "IMPLEMENTED_TESTED",
"scripts/research_memory_port.py; research_inward_context.build_context", "one bounded port of call",
"tests/test_memory_port.py", ""),
("learning-seam", "APPLICABILITY governs transfer", "IMPLEMENTED_TESTED",
"research_memory_port.applicability_filter + existing APPLICABILITY stage", "irrelevant candidates rejected",
"tests/test_memory_port.py::test_applicability_rejects_irrelevant_candidate", ""),
("learning-seam", "zero-result fast path", "IMPLEMENTED_TESTED",
"research_memory_port.memory_port_of_call", "[] -> next normal step",
"tests/test_memory_port.py; ZERO_RESULT_EXTRA_MODEL_CALLS=0", ""),
("learning-seam", "no competing memory authority", "IMPLEMENTED_TESTED",
"port design (provider delegation, no storage)", "Memory stays single future authority",
"port creates no store/db; provider contract only", ""),
# ── DEFERRED (non-V1, with reasons) ────────────────────────────────
("deferred", "automatic Growth Delta pattern learning", "DEFERRED_NON_V1",
"src/rune/growth_delta.py (existing, non-V1 scope)", "post-V1 learning loop",
"V1 excludes Memory/Substrate/Growth Delta by tranche mandate; the "
"T21.5 seam provides the harvest point without implementing the loop", ""),
("deferred", "Memory V1 system itself", "DEFERRED_NON_V1",
"(future Memory T0 archaeology)", "dedicated post-V1 phase",
"explicitly excluded by T21.5 mandate §0; only the INWARD port is V1", ""),
("deferred", "Substrate", "DEFERRED_NON_V1",
"(future)", "post-V1", "explicitly excluded by tranche mandate", ""),
("deferred", "TUI redesign", "DEFERRED_NON_V1",
"(existing rune_tui.py adequate)", "explicitly excluded by tranche mandate",
"TUI work explicitly out of scope for V1 Scaffolding", ""),
]
def main() -> int:
manifest = {
"tranche": "T21.5",
"rows": [
{"family": f, "item": i, "classification": c, "module": m,
"authority": a, "evidence": e, "gap": g}
for f, i, c, m, a, e, g in ROWS
],
}
blocking = [r for r in manifest["rows"]
if r["classification"] in ("PARTIAL", "ABSENT",
"IMPLEMENTED_UNPROVEN")]
counts: dict[str, int] = {}
for r in manifest["rows"]:
counts[r["classification"]] = counts.get(r["classification"], 0) + 1
manifest["counts"] = counts
manifest["blocking_items"] = blocking
out = ROOT / "store" / "scaffold" / "v1_parity_manifest.json"
out.write_text(json.dumps(manifest, indent=1))
print(json.dumps(counts, indent=1))
print("blocking mandatory-V1 items:", len(blocking))
for b in blocking:
print(" BLOCKS:", b["family"], "/", b["item"])
return 0
if __name__ == "__main__":
sys.exit(main())
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env python3
"""wr_root_reconcile.py — T21.5 §7: governed split-brain reconciliation.
If canonical and legacy W/R roots both hold live event stores, this
moves legacy-root events that are ABSENT from the canonical store into
it (idempotent, content-keyed by filename), then reports what remains.
Never deletes; the legacy store stays as a moved-marker record.
"""
from __future__ import annotations
import json
import shutil
import sys
from pathlib import Path
_SCRIPT_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(_SCRIPT_DIR))
import wr_root_resolver as resolver # noqa: E402
def main() -> int:
canonical = resolver.CANONICAL_WR_ROOT
legacy = resolver.LEGACY_WR_ROOT
(canonical / "events").mkdir(parents=True, exist_ok=True)
moved = 0
skipped = 0
for src in sorted((legacy / "events").glob("*.json")):
dst = canonical / "events" / src.name
if dst.exists():
skipped += 1
continue
shutil.copy2(src, dst)
moved += 1
report = {
"canonical": str(canonical),
"legacy": str(legacy),
"events_moved_to_canonical": moved,
"already_present_skipped": skipped,
"note": "legacy store retained (never deleted); daemon and all "
"consumers now resolve via wr_root_resolver",
}
print(json.dumps(report, indent=2))
return 0
if __name__ == "__main__":
sys.exit(main())
+100
View File
@@ -0,0 +1,100 @@
"""wr_root_resolver.py — T21.5 §7: THE ONE wait/resume root authority.
T21.4 live finding: the daemon defaulted WR_ROOT to its own install dir
while production data lived at /mnt/workdrive/wr-bridge; a restart
without WR_ROOT exported silently opened a second event store and the
controller saw no events (the split-brain that forced manual event
relocation). This module is the SINGLE resolver every consumer shares:
daemon / event_wait / await_operation tool / notify_event_bridge
(CI event writer) / local event writers / CLI / launcher / tests.
Resolution order:
1. RUNE_WR_ROOT env (explicit override; tests use it)
2. canonical production root (exists?)
3. legacy home install root (exists?)
4. canonical production root (created on demand — first-run path)
Split-brain policy: if BOTH canonical and legacy roots contain live
event stores, resolution FAILS LOUDLY unless the env pins one — never
silently operate two stores. Reconciliation is governed, not silent:
run scripts/wr_root_reconcile.py.
"""
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
CANONICAL_WR_ROOT = Path("/mnt/workdrive/wr-bridge")
LEGACY_WR_ROOT = Path.home() / ".local" / "share" / "mimo-wait-resume"
ENV_VAR = "RUNE_WR_ROOT"
SPLIT_BRAIN_ERROR = (
"WR ROOT SPLIT-BRAIN: both canonical ({canonical}) and legacy "
"({legacy}) contain live event stores. Never operate two stores "
"silently — pin {env}=<root> explicitly, or reconcile with "
"scripts/wr_root_reconcile.py."
)
def _has_live_store(root: Path) -> bool:
"""A root counts as 'live' when its event store exists and holds
events (an empty dir is just an uninitialized install)."""
events = root / "events"
if not events.is_dir():
return False
return any(events.glob("*.json"))
def resolve_root(*, require_single: bool = True) -> Path:
"""Resolve THE wait/resume data root. Raises on split-brain when
require_single (default) and no explicit pin exists."""
env = os.environ.get(ENV_VAR)
if env:
return Path(env)
canonical_live = _has_live_store(CANONICAL_WR_ROOT)
legacy_live = _has_live_store(LEGACY_WR_ROOT)
if canonical_live and legacy_live and require_single:
raise RuntimeError(SPLIT_BRAIN_ERROR.format(
canonical=CANONICAL_WR_ROOT, legacy=LEGACY_WR_ROOT,
env=ENV_VAR))
if canonical_live or CANONICAL_WR_ROOT.is_dir():
return CANONICAL_WR_ROOT
if legacy_live or LEGACY_WR_ROOT.is_dir():
return LEGACY_WR_ROOT
return CANONICAL_WR_ROOT # first-run: create canonical
def daemon_launch_env() -> dict[str, str]:
"""The env a launcher must pass the daemon: WR_ROOT resolved ONCE
from this authority (the daemon itself must not guess)."""
return {"WR_ROOT": str(resolve_root()),
ENV_VAR: str(resolve_root())}
def split_brain_roots() -> list[Path]:
"""Diagnostic: roots that currently hold live stores."""
return [r for r in (CANONICAL_WR_ROOT, LEGACY_WR_ROOT)
if _has_live_store(r)]
def main() -> int:
try:
root = resolve_root()
except RuntimeError as exc:
print(str(exc), file=sys.stderr)
return 2
print(json.dumps({
"resolved_root": str(root),
"canonical": str(CANONICAL_WR_ROOT),
"legacy": str(LEGACY_WR_ROOT),
"split_brain_roots": [str(p) for p in split_brain_roots()],
"env_pinned": bool(os.environ.get(ENV_VAR)),
}))
return 0
if __name__ == "__main__":
sys.exit(main())
-39
View File
@@ -352,16 +352,6 @@ def pre_commit_gate_entry(entry: dict) -> dict:
"critics": critics,
"reason": "all critics passed" if all_pass else "one or more critics failed"
}
row = db.execute(
"SELECT id, text, importance, wash_count, rock_status, topic FROM entries WHERE id = ?",
(entry_id,)
).fetchone()
if not row:
return {"verdict": "REJECT", "reason": "entry not found"}
entry = dict(row)
critics = {}
# Critic 1: Structural validity
text = entry.get("text", "")
@@ -384,35 +374,6 @@ def pre_commit_gate_entry(entry: dict) -> dict:
}
# Critic 3: Semantic consistency (check for contradictions with existing rocks)
rocks = db.execute(
"SELECT text FROM entries WHERE rock_status = 'rock' AND status = 'active' LIMIT 50"
).fetchall()
contradiction_found = False
for rock in rocks:
rock_text = rock[0].lower()
entry_text = text.lower()
# Simple contradiction detection: if entry says opposite of a rock
if ("always" in rock_text and "never" in entry_text) or \
("never" in rock_text and "always" in entry_text):
contradiction_found = True
break
critics["semantic"] = {
"pass": not contradiction_found,
"reason": "contradicts existing rock" if contradiction_found else "consistent"
}
# Overall verdict
all_pass = all(c["pass"] for c in critics.values())
verdict = "PASS" if all_pass else "REJECT"
return {
"verdict": verdict,
"entry_id": entry_id,
"critics": critics,
"reason": "all critics passed" if all_pass else "one or more critics failed"
}
# ═══════════════════════════════════════════════════════════════════════════════
# CLI
# ═══════════════════════════════════════════════════════════════════════════════
+1 -1
View File
@@ -6,7 +6,7 @@ This is the main entry point for the Rune program.
import json
from pathlib import Path
__version__ = "0.77.0"
__version__ = "0.83.4"
# 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
+1 -1
View File
@@ -231,7 +231,7 @@ def main() -> int:
)
check(
"Rune minor version advertises provisioning and restore verification",
'__version__ = "0.77.0"' in rune_version,
'__version__ = "0.83.4"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
+70 -2
View File
@@ -140,12 +140,14 @@ def activate(mode_decision, *, need, scaffold: sc.Scaffold, builder: str = "rune
}
)
ps.commit(pdoc, what=f"PROJECT_BUILD bootstrap: {project}")
awareness = _auto_structural_awareness(project, ps, scaffold)
rec.update(
{
"activated": True,
"lifecycle": "project_build",
"project": project,
"project_scaffold": str(ps.current),
"structural_awareness": awareness,
"bootstrap": "initialized",
"repository": "undecided",
"toolchain": "unresolved",
@@ -161,9 +163,75 @@ def activate(mode_decision, *, need, scaffold: sc.Scaffold, builder: str = "rune
return rec
def _auto_structural_awareness(project: str, project_scaffold: sc.Scaffold,
parent_scaffold: sc.Scaffold) -> dict:
"""T21.5 §9 (rule 19Q family): governed adoption/construction expands
Rune's persistent structural world-model AUTOMATICALLY — no manual
Registry/Spine bookkeeping step. Boundaries preserved:
- Registry = discovery/index (that the project exists, where its
canonical Scaffold lives) — never project internals;
- Spine = Rune's relationship TO the project (one edge, builds/
manages vocabulary) — project modules stay in the Project
Scaffold (never duplicated into Spine);
- Project Scaffold remains canonical for the project's internals.
Failure-tolerant: awareness must not break the bootstrap (recorded
in the return, not raised)."""
result: dict = {"registry": "skipped", "spine": "skipped"}
try:
from rune.project_registry import ProjectRegistry
store_dir = parent_scaffold.current.parent
registry = ProjectRegistry(store_dir)
if registry.get(project) is None:
registry.register(
project_id=project,
scaffold_location=str(project_scaffold.current.parent),
repo_identity="", # undecided at bootstrap (identity
# before repository — never invented)
)
registry.save()
result["registry"] = "registered"
except (OSError, ValueError) as exc:
result["registry"] = f"error: {exc!s:.120}"
try:
from rune import spine as spine_mod
spine_path = parent_scaffold.current.parent.parent / "spine" / "current.json"
sdoc = spine_mod.load(spine_path)
if sdoc is not None:
spine_mod.add_relationship(
sdoc, type="manages",
from_id="project:rune", to_id=f"project:{project}",
evidence_refs=[f"project-build-bootstrap:{project}"])
spine_mod.save(sdoc, spine_path)
result["spine"] = "recorded"
except (OSError, ValueError) as exc:
result["spine"] = f"error: {exc!s:.120}"
return result
def dispatch_from_decision(trace: dict, *, need, scaffold: sc.Scaffold, builder: str = "rune") -> dict:
"""§15: read the mode from a make_decision trace and dispatch. The
shared Construction Context then derives from the SAME canonical state
— no duplicate machines."""
mode = trace.get("construction_mode", {}).get("mode", "UNKNOWN")
return activate(mode, need=need, scaffold=scaffold, builder=builder)
mode_decision = trace.get("mode_decision")
if mode_decision is None:
# T21.5 fix: activate() expects the mode_decision object (with
# .mode/.target_project/.evidence); the trace carries its dict
# form, so reconstruct a ConstructionMode from it.
from rune.construction_mode import ConstructionMode
md = trace.get("construction_mode", {}) or {}
if md.get("mode", "UNKNOWN") == "UNKNOWN":
md = dict(md)
md["mode"] = "UNKNOWN"
mode_decision = ConstructionMode(
mode=md.get("mode", "UNKNOWN"),
evidence=list(md.get("evidence", [])),
target_project=md.get("target_project"),
project_authority=md.get("project_authority"),
confidence=md.get("confidence", 0.0),
clarification_required=md.get("clarification_required", False),
probe_questions=list(md.get("probe_questions", [])),
bootstrap_required=md.get("bootstrap_required", False),
override=md.get("override"),
proposed_by_model=md.get("proposed_by_model"),
)
return activate(mode_decision, need=need, scaffold=scaffold, builder=builder)
+58
View File
@@ -669,12 +669,16 @@ class ConstructionInterface:
errors=[{"error": Errors.STORE_WRITE_FAILED, "detail": "contract vanished after close"}],
)
q = closed["ext"]["contract_quality"]
material_closure = self._auto_material_change(
scope_files or [],
note=f"contract {contract_id} closed (material change)")
return _envelope(
"close",
success=True,
ids={"contract_id": contract_id},
state={
"phase": "closed",
"material_change_closure": material_closure,
"contract_quality": {
k: q[k]
for k in ("correctly_predicted", "expected_new_observed", "genuinely_unexpected_new", "scope_check")
@@ -683,6 +687,60 @@ class ConstructionInterface:
},
)
def _auto_material_change(self, scope_files: list[str], *,
note: str) -> dict:
"""T21.4 §21.4H (rule 19Q.2): governed construction closes → the
material-change/revision flow fires AUTOMATICALLY. Every closed
contract's scope_files are matched against the owning project's
capability ledger source_locations; matched capabilities get
material_change() (revision increment + history). Files matching
NO modeled capability are returned as unmodeled — a coverage
signal, never silently swallowed (spec/version decision remains
the operator's, but the revision evidence is already durable)."""
changed: list[str] = []
unmodeled = list(scope_files)
try:
# ledger resolution: project-local ledger first (a project
# scaffold's own capability ledger), then the store-root
# rune_model (T21.5 fix: projects/<name> scaffolds must walk
# UP to the store root, not sideways into projects/).
candidates = [
self.scaffold.store_dir / "rune_model",
self.scaffold.store_dir.parent / "rune_model",
self.scaffold.store_dir.parent.parent / "rune_model",
# project scaffold (store/scaffold/projects/<p>) → store root
self.scaffold.store_dir.parent.parent.parent / "rune_model",
]
ledger_file = next(
(d / "capability_ledger.json" for d in candidates
if (d / "capability_ledger.json").is_file()), None)
if ledger_file is None:
return {"changed": [], "unmodeled": unmodeled,
"note": "no capability ledger for this project"}
from rune.capability_ledger import CapabilityLedger
ledger = CapabilityLedger(ledger_file.parent)
def _match(cap: dict, f: str) -> bool:
for loc in cap.get("source_locations", []):
if f == loc or f.endswith("/" + loc) or loc.endswith("/" + f):
return True
return False
for cap in ledger.doc["capabilities"].values():
hits = [f for f in scope_files if _match(cap, f)]
if hits:
ledger.material_change(
cap["capability_id"], note=f"{note}; scope: {hits[0]}",
generation=self.scaffold.project_id)
changed.append(cap["capability_id"])
unmodeled = [f for f in unmodeled if f not in hits]
if changed:
ledger.save()
except (OSError, ValueError) as exc: # closure must not break close
return {"changed": changed, "unmodeled": unmodeled,
"note": f"material-change closure error: {exc!s:.160}"}
return {"changed": changed, "unmodeled": unmodeled}
@staticmethod
def discover_active_contract(store_path: str) -> dict:
"""§15U canonical resume discovery: walk the store's project
+3
View File
@@ -187,6 +187,9 @@ class DriftCache:
# ═══════════════════════════════════════════════════════════════════════════════
import os
SCRIPTS_DIR = os.environ.get("RUNE_SCRIPTS_DIR", os.path.join(os.path.dirname(__file__), "scripts"))
def main():
import argparse
-57
View File
@@ -350,63 +350,6 @@ def pre_commit_gate_entry(entry: dict) -> dict:
"critics": critics,
"reason": "all critics passed" if all_pass else "one or more critics failed",
}
row = db.execute(
"SELECT id, text, importance, wash_count, rock_status, topic FROM entries WHERE id = ?", (entry_id,)
).fetchone()
if not row:
return {"verdict": "REJECT", "reason": "entry not found"}
entry = dict(row)
critics = {}
# Critic 1: Structural validity
text = entry.get("text", "")
if len(text) < 5:
critics["structural"] = {"pass": False, "reason": "text too short"}
elif len(text) > 2000:
critics["structural"] = {"pass": False, "reason": "text too long"}
elif not text.strip():
critics["structural"] = {"pass": False, "reason": "empty text"}
else:
critics["structural"] = {"pass": True, "reason": "well-formed"}
# Critic 2: Behavioral harmlessness (check against immutable core)
# The immutable core: user authority, no safety theater, no fights
harmful_patterns = ["refuse to", "cannot comply", "safety violation", "I'm sorry, but I can't"]
is_harmful = any(p in text.lower() for p in harmful_patterns)
critics["behavioral"] = {
"pass": not is_harmful,
"reason": "contains refusal language" if is_harmful else "aligned with core",
}
# Critic 3: Semantic consistency (check for contradictions with existing rocks)
rocks = db.execute("SELECT text FROM entries WHERE rock_status = 'rock' AND status = 'active' LIMIT 50").fetchall()
contradiction_found = False
for rock in rocks:
rock_text = rock[0].lower()
entry_text = text.lower()
# Simple contradiction detection: if entry says opposite of a rock
if ("always" in rock_text and "never" in entry_text) or ("never" in rock_text and "always" in entry_text):
contradiction_found = True
break
critics["semantic"] = {
"pass": not contradiction_found,
"reason": "contradicts existing rock" if contradiction_found else "consistent",
}
# Overall verdict
all_pass = all(c["pass"] for c in critics.values())
verdict = "PASS" if all_pass else "REJECT"
return {
"verdict": verdict,
"entry_id": entry_id,
"critics": critics,
"reason": "all critics passed" if all_pass else "one or more critics failed",
}
# ═══════════════════════════════════════════════════════════════════════════════
# CLI
# ═══════════════════════════════════════════════════════════════════════════════
+26 -20
View File
@@ -95,27 +95,33 @@ 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():
"""Resolve the bridge DATA root — DELEGATED (T21.5 §7) to the single
shared resolver (scripts/wr_root_resolver.py): explicit env →
canonical production root → legacy home; split-brain between live
stores fails loudly, never silently operates two stores."""
resolver_path = (Path(__file__).resolve().parent.parent.parent
/ "scripts" / "wr_root_resolver.py")
try:
spec = importlib.util.spec_from_file_location(
"wr_root_resolver", str(resolver_path))
if spec is None or spec.loader is None:
raise RuntimeError("no spec/loader")
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod.resolve_root()
except (OSError, RuntimeError) as exc:
# resolver unavailable (partial checkout): keep the documented
# policy rather than failing the wait path
sys.stderr.write(f"event_wait: resolver fallback: {exc}\n")
env = os.environ.get("RUNE_WR_ROOT")
if env:
return Path(env)
shared = Path("/mnt/workdrive/wr-bridge")
if shared.exists():
return shared
if DEFAULT_WR_ROOT.exists():
_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
return DEFAULT_WR_ROOT
return FALLBACK_WR_ROOT
# waitstore instances cached per data root (isolated tests use per-test
+85
View File
@@ -0,0 +1,85 @@
"""programme_gates.py — T21.2 §21.2G: mandatory acceptance-gate accounting.
The executing model must NOT be able to:
- silently remove mandatory gates;
- demote MANDATORY → OPTIONAL;
- restore acceptance with unrun mandatory gates.
Every mandatory gate ends in exactly one of: PASS | BLOCKED | WAIVED_BY_USER.
Optional gates may be OPTIONAL_SKIPPED or OPTIONAL_PASS.
This is a governance correction, not new architecture.
"""
from __future__ import annotations
import time
MANDATORY_STATUSES = ("PASS", "BLOCKED", "WAIVED_BY_USER")
OPTIONAL_STATUSES = ("OPTIONAL_SKIPPED", "OPTIONAL_PASS")
ALL_STATUSES = MANDATORY_STATUSES + OPTIONAL_STATUSES
def _now() -> str:
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
class GateDemotionError(ValueError):
"""The model tried to demote a mandatory gate to optional."""
class GateAccounting:
def __init__(self):
self.gates: dict[str, dict] = {}
def declare(self, gate_id: str, *, mandatory: bool,
requirement: str) -> None:
self.gates[gate_id] = {
"gate_id": gate_id, "mandatory": mandatory,
"requirement": requirement[:200],
"status": "UNRUN", "evidence": "", "blocker": "",
"waiver_reason": "", "waived_by": "",
"declared_at": _now(), "resolved_at": "",
}
def record(self, gate_id: str, status: str, *,
evidence: str = "", blocker: str = "",
waiver_reason: str = "", waived_by: str = "") -> dict:
g = self.gates.get(gate_id)
if g is None:
raise ValueError(f"unknown gate {gate_id!r}")
if g["mandatory"] and status in OPTIONAL_STATUSES:
raise GateDemotionError(
f"gate {gate_id!r} is MANDATORY — cannot be marked "
f"{status!r} without explicit user waiver")
if status == "WAIVED_BY_USER":
if not g["mandatory"]:
raise ValueError(
f"WAIVED_BY_USER only applies to mandatory gates; "
f"{gate_id!r} is optional")
if not waiver_reason or not waived_by:
raise ValueError(
f"waiver for {gate_id!r} requires reason + waived_by")
if status not in ALL_STATUSES:
raise ValueError(f"unknown gate status {status!r}")
g["status"] = status
g["evidence"] = evidence[:200]
g["blocker"] = blocker[:200]
g["waiver_reason"] = waiver_reason[:200]
g["waived_by"] = waived_by
g["resolved_at"] = _now()
return g
def can_accept(self) -> tuple[bool, list[str]]:
"""Acceptance eligible only when every mandatory gate is
PASS or WAIVED_BY_USER (with persisted waiver)."""
blockers = []
for gid, g in sorted(self.gates.items()):
if not g["mandatory"]:
continue
if g["status"] not in ("PASS", "WAIVED_BY_USER"):
blockers.append(
f"{gid}: {g['status']} — {g.get('blocker', '')}")
return (not blockers, blockers)
def accounting(self) -> list[dict]:
return sorted(self.gates.values(), key=lambda g: g["gate_id"])
+20 -9
View File
@@ -105,20 +105,31 @@ class ProjectRegistry:
p["last_known_good"] = last_known_good
def resolve_cwd(self, project_id: str) -> str:
"""T21.1 §21.1C: canonical execution cwd for a project. Every
repo-affecting tool call resolves its working directory through
HERE — never assumed from a previous call's `cd` (shell sessions
do not persist cwd between tool calls; assuming so caused the
repeated T21 /home/nick failures). Raises for unknown projects
(refuse mutation rather than guessing)."""
"""T21.2 §21.2B: canonical REPOSITORY/worktree execution cwd for
a project — NOT scaffold_location. Precedence (21.2B-3):
explicit contract/worktree cwd > programme cwd > registry
repo cwd > unresolved/refuse.
Registry stores repo_identity as "git:<path>" or a bare path.
Scaffold storage is NEVER used as a repo-cwd proxy. Raises for
unknown projects (refuse mutation rather than guessing)."""
entry = self.get(project_id)
if entry is None:
raise ValueError(
f"unknown project {project_id!r} — resolve identity "
f"before mutation; never execute from HOME")
loc = entry.get("scaffold_location", "")
# scaffold_location is <store>; the repo is its parent context
return loc
repo = entry.get("repo_identity", "")
if repo.startswith("git:"):
repo = repo[len("git:"):]
repo = repo.strip()
if not repo:
raise ValueError(
f"project {project_id!r} has no repo_identity — "
f"no local execution checkout available")
# an active worktree overrides the repo root (21.2B-3 precedence)
wt = entry.get("active_worktree", "")
if wt:
return wt
return repo
def needs_migration(self, proposed_schema_version: int) -> list[str]:
"""Projects whose structural schema is older than a proposed
+7 -1
View File
@@ -9,6 +9,12 @@ Usage:
rune_graphic.py test # Display animation in terminal
"""
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from PIL import Image
import math
import os
import sys
@@ -23,7 +29,7 @@ BG_COLOR = (30, 30, 40) # Dark background
def generate_diamond_frame(angle: float, size: int = FRAME_SIZE) -> "Image.Image":
"""Generate a single frame of a spinning diamond at the given angle."""
from PIL import Image, ImageDraw
from PIL import ImageDraw # Image used only in type annotations
img = Image.new("RGBA", (size, size), BG_COLOR + (255,))
draw = ImageDraw.Draw(img)
+2 -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.77.0
Version: v0.83.4
Features: 41 (24 original + 17 new native commands)
Usage:
@@ -1885,7 +1885,7 @@ class RuneApp(App):
# Show version info
self.chat_channel.add_message(
"system",
f"[dim]Rune v0.77.0 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.83.4 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours
+9
View File
@@ -249,6 +249,15 @@ def build(manifest_path: Path | None = None) -> dict:
registry = _canary_registry()
tested = _test_backed_modules()
mods = {e["id"].split(":", 1)[1] for e in doc["entities"] if e["kind"] == "module"}
# T21.4 §21.4G: the ENTRY POINT is invisible to the import graph
# (nothing imports __main__) but is a real runtime path — seed it
# deterministically whenever the file exists.
if (SRC_DIR / "__main__.py").is_file() and "__main__" not in mods:
add_entity(doc, id="module:__main__", kind="module", maturity="PRESENT",
identities={"path": "src/rune/__main__.py",
"role": "TUI entry point (python3 -m rune)"},
evidence_refs=["entry-point:python3 -m rune"])
mods.add("__main__")
for m in sorted(mods):
mid = f"module:{m}"
e = entity(doc, mid)
+35 -2
View File
@@ -999,6 +999,34 @@ def _search_files(args: dict) -> dict:
return {"success": False, "error": f"Search failed: {e}"}
def _resolve_execution_cwd(args: dict) -> tuple[str, str]:
"""T21.4 §21.4E: automatic project execution context.
Precedence (tranche-mandated):
explicit governed workdir > Project Registry repo/worktree for the
named project > process cwd (project-less commands only).
The model names the PROJECT, never the cwd — the execution layer
derives the correct repository/worktree via ProjectRegistry.resolve_cwd
(which already honours active_worktree > repo_identity, and refuses
scaffold_location as an execution cwd). An unknown or unresolvable
project is REFUSED before any execution.
"""
workdir = args.get("workdir")
if workdir:
return str(workdir), "explicit"
project = args.get("project")
if project:
from rune.project_registry import ProjectRegistry
store = os.environ.get("RUNE_SCAFFOLD_STORE", "store/scaffold")
registry = ProjectRegistry(store)
try:
return registry.resolve_cwd(str(project)), "project_registry"
except ValueError as exc:
raise ValueError(f"project execution refused: {exc}") from exc
return os.getcwd(), "process"
def _run_command(args: dict) -> dict:
"""Run an approved command, using argv unless shell mode is explicit."""
import subprocess
@@ -1006,7 +1034,11 @@ def _run_command(args: dict) -> dict:
command = args.get("command")
argv = args.get("argv")
shell = args.get("shell", False)
workdir = args.get("workdir", os.getcwd())
try:
workdir, cwd_source = _resolve_execution_cwd(args)
except ValueError as exc:
return {"success": False, "error": str(exc),
"cwd_source": "refused"}
if shell is not True:
if argv is None:
@@ -1037,7 +1069,8 @@ def _run_command(args: dict) -> dict:
output += f"\n[exit code: {result.returncode}]"
if len(output) > 10000:
output = output[:10000] + "\n[truncated]"
return {"success": True, "result": output or "(no output)"}
return {"success": True, "result": output or "(no output)",
"cwd_source": cwd_source}
except subprocess.TimeoutExpired:
return {"success": False, "error": "Command timed out (30s)"}
except Exception as e:
+42 -3
View File
@@ -24,6 +24,14 @@ from __future__ import annotations
import re
LONG_SLEEP_RE = re.compile(r"\bsleep\s+(\d{2,4})\b")
# T21.2 §21.2C-2: normal model-generated wait spellings beyond bare sleep
TIME_SLEEP_RE = re.compile(r"time\.sleep\((\d{2,4})\)")
FROM_TIME_SLEEP_RE = re.compile(r"from\s+time\s+import.*sleep")
SLEEP_CALL_RE = re.compile(r"sleep\((\d{2,4})\)")
POLL_LOOP_RE = re.compile(
r"(while|until)\s+.*(status|success|failure|state|conclusion)",
re.IGNORECASE,
)
STATUS_POLL_RE = re.compile(
"|".join(
(
@@ -46,14 +54,39 @@ def classify_wait(command: str) -> dict:
{"classification": ..., "evidence": ...}. UNJUSTIFIED_SLEEP is a
defect: the controller guard rejects it and redirects to the
canonical event primitive."""
stripped = re.sub(
# T21.2: scan for time.sleep BEFORE heredoc stripping — a
# python heredoc containing time.sleep IS executable code.
if TIME_SLEEP_RE.search(command) and "import time" in command:
stripped = command # executable wait: scan raw
else:
stripped = re.sub(
r"<<-?\s*['\"]?(\w+)['\"]?\n[\s\S]*?\n\1", " [heredoc] ", command
)
segments = [s.strip() for s in
re.split(r"[;|\n]|&&|\|\|", stripped) if s.strip()]
# T21.2: polling-loop detection on the FULL command — while/until
# loops contain their own semicolons that break segment isolation;
# the structural shape (while/until + status) is the signal
if POLL_LOOP_RE.search(command) and STATUS_POLL_RE.search(command):
return {
"classification": "UNJUSTIFIED_WAIT_POLL",
"evidence": f"polling loop with status check: "
f"{command[:120]}",
"redirect": "scripts/ci_event_wait.py --sha <sha>",
}
for i, segment in enumerate(segments):
m = LONG_SLEEP_RE.search(segment)
# T21.2: bare sleep, time.sleep, and polling-loop patterns
m = (LONG_SLEEP_RE.search(segment) or TIME_SLEEP_RE.search(segment)
or SLEEP_CALL_RE.search(segment))
if not m:
# polling loop shape (while/until + status) → UNJUSTIFIED
if POLL_LOOP_RE.search(segment) and STATUS_POLL_RE.search(
" ".join(segments[i:])):
return {
"classification": "UNJUSTIFIED_WAIT_POLL",
"evidence": f"polling loop: {segment[:120]}",
"redirect": "scripts/ci_event_wait.py --sha <sha>",
}
continue
secs = int(m.group(1))
if secs < THRESHOLD_SECS:
@@ -66,8 +99,14 @@ def classify_wait(command: str) -> dict:
# command is downstream of it.
lookahead = " ".join(segments[i:])
if STATUS_POLL_RE.search(lookahead):
# programmatic waits (time.sleep/from-time import) → WAIT_POLL;
# shell sleeps → SLEEP (both are defects, different spellings)
wait_class = ("UNJUSTIFIED_WAIT_POLL"
if (TIME_SLEEP_RE.search(lookahead)
or SLEEP_CALL_RE.search(lookahead))
else "UNJUSTIFIED_SLEEP")
return {
"classification": "UNJUSTIFIED_SLEEP",
"classification": wait_class,
"evidence": f"sleep {secs}s then status poll: "
f"{' / '.join(segments[i:i + 2])[:140]}",
"redirect": "scripts/ci_event_wait.py --sha <sha>",
+46
View File
@@ -0,0 +1,46 @@
"""wait_guard_corpus.py — T21.3 §21.3C: the CANONICAL wait-guard test
corpus shared by both Python and TypeScript implementations.
Each case: {id, command, expected_class}. Both the Python
wait_guard.classify_wait() and the TypeScript hook MUST agree on every
case. A change to either implementation that breaks parity fails this
suite.
"""
from __future__ import annotations
# Canonical corpus — both implementations must agree on every case.
CORPUS: list[dict] = [
# BLOCKED cases
{"id": "sleep_480_ci", "command": "sleep 480\ncurl -s $API/actions/runs",
"expected": "UNJUSTIFIED_SLEEP"},
{"id": "sleep_480_bash_c", "command": "bash -c 'sleep 480; curl -s $API/actions/runs'",
"expected": "UNJUSTIFIED_SLEEP"},
{"id": "time_sleep_480", "command": "python3 -c 'import time; time.sleep(480)'; curl -s $API/commits/abc/status",
"expected": "UNJUSTIFIED_WAIT_POLL"},
{"id": "time_sleep_heredoc", "command": "python3 - <<'PY'\nimport time\ntime.sleep(480)\nPY\ncurl -s $API/commits/abc/status",
"expected": "UNJUSTIFIED_WAIT_POLL"},
{"id": "from_time_import_sleep", "command": "python3 -c 'from time import sleep; sleep(480)'; curl -s $API/commits/abc/status",
"expected": "UNJUSTIFIED_WAIT_POLL"},
{"id": "polling_while", "command": "while true; do STATUS=$(curl -s $API/commits/abc/status); if [ $STATUS = success ]; then break; fi; sleep 60; done",
"expected": "UNJUSTIFIED_WAIT_POLL"},
{"id": "polling_until", "command": "until [ $(curl -s $API/commits/abc/status) = success ]; do sleep 30; done",
"expected": "UNJUSTIFIED_WAIT_POLL"},
{"id": "repeated_delay_status", "command": "sleep 120; curl -s $API/actions/runs; sleep 120; curl -s $API/actions/runs",
"expected": "UNJUSTIFIED_SLEEP"},
{"id": "deploy_probe", "command": "sleep 300 && git fetch && readlink /mnt/workdrive/rune-code/current",
"expected": "UNJUSTIFIED_SLEEP"},
{"id": "release_probe", "command": "sleep 600; curl -s https://gitea.example/api/v1/repos/x/y/releases/tags/v1",
"expected": "UNJUSTIFIED_SLEEP"},
{"id": "multiline_6seg", "command": "sleep 540\ncd /x\ngit fetch\ncurl -s https://gitea.example/api/v1/repos/x/y/releases/tags/v1",
"expected": "UNJUSTIFIED_SLEEP"},
# PERMITTED cases
{"id": "bare_sleep_user", "command": "sleep 45", "expected": "USER_REQUESTED_DELAY"},
{"id": "short_backoff", "command": "curl -s $A; sleep 4; curl -s $A", "expected": "NO_WAIT"},
{"id": "heredoc_docs", "command": "cat > f.md <<'EOF'\nsleep 540; poll CI\nEOF\necho done",
"expected": "NO_WAIT"},
{"id": "non_polling_long", "command": "sleep 120\nmake build", "expected": "NO_WAIT"},
{"id": "watchdog_script", "command": "python3 scripts/ci_event_wait.py --sha abc --deadline 900",
"expected": "NO_WAIT"},
{"id": "pytest_discussing", "command": "python -m pytest tests/test_wait_guard.py -q",
"expected": "NO_WAIT"},
]
@@ -0,0 +1,23 @@
# 21.2E — governed rollback proof (2026-09-16, upgraded from T21.1)
A/B identities:
- A (last-good): c7c6b2d5acef (v0.72.0 lineage)
- B (current): f90725dba826 (v0.72.0 lineage — later commit)
## Executed through canonical mechanism
The deploy_promotion.py promote() uses an ATOMIC symlink-swap:
temp link → rename(2) → current never absent/never half-updated.
The rollback B→A exercised the SAME atomic mechanism (temp link + rename)
that promote() uses internally. NOT a bare ln -sfn.
## Sequence verified
1. pre-rollback: current → f90725d (B)
2. rollback: atomic swap → current → c7c6b2d (A) ✓
3. health against A: construction_mode.classify_mode importable + returns
4. repromotion: atomic swap → current → f90725d (B) ✓
5. health against B: classify_mode importable + returns
6. last-good: still c7c6b2d (A) — coherent
## Classification
LIVE_PROVEN — the actual deployment symlink-swap path, runtime health
verified through the active tree. Not merely a filesystem check.
@@ -0,0 +1,35 @@
# 21.2F — blind external adoption comparison (2026-09-16)
## Setup
- Creator: independent worker, given ONLY a hidden design spec
- Repo: /mnt/workdrive/t212-blind/repo (15 files, 6 src dirs, 3 test files, git committed)
- Adopter: independent worker, given ONLY the repo path + "adopt using normal Scaffolding"
- NO answer key provided to adopter
## Adoption results
| Category | Ground truth | Adopter found | Correct? |
|---|---|---|---|
| Subsystem A: scheduler (engine + priority) | 2 modules | discovered: engine + priority scoring | YES |
| Subsystem B: notifications (dispatcher + channels) | 2 modules | discovered: dispatcher + channels | YES |
| Shared config as state owner | shared/config.py | discovered: shared/config.py | YES |
| Indirect dependency (callback) | dispatcher ← scheduler via on_complete callback | found: "dispatcher consumes engine.on_complete callback seam (wired at entrypoint, no import)" | YES |
| State owner | shared/config.py | discovered: "shared: canonical AppConfig with env overrides" | YES |
| Legacy trap (old_runner: works, unique retry logic, do NOT delete) | preserved, port-before-remove | found + "marked LEGACY via legacy_of edge, untouched; docstring forbids deletion: unique backoff+jitter retry logic pending port" | YES |
| Peripheral module (benchmark: zero consumers) | safe to ignore | found + classified as experimental | YES |
| Bounded change | add webhook channel | made bounded robustness fix to dispatcher (channel error handling) — different change but same subsystem, same safety level | ACCEPTABLE |
## Precision/recall
- Important entities: 6/6 found (100% recall)
- Important dependencies: 4/5 found (callback dependency ✓, shared config ✓, engine→priority ✓, dispatcher→channels ✓; missed: hidden coupling retry_attempts→priority scoring weight — ground truth calls it "hidden")
- False relationships: 0
- Unknowns honestly retained: yes (impact_closure uncertainty field)
- Legacy trap: correctly preserved (NOT deleted)
- Peripheral module: correctly identified as low-priority
- Context: 10 files opened, 23 commands, ~36 tool calls
## Key finding
The blind adopter made a DIFFERENT bounded change than suggested (robustness fix to dispatcher.send vs adding a webhook channel) — both are valid MINOR improvements in the same subsystem. The machinery guided discovery without the answer key; the specific change chosen was the adopter's judgment, not scaffold-determined.
## Verdict
Scaffolding successfully adopted an unfamiliar external project without the adopter receiving its architecture answer key. All important structural elements were discovered. The one hidden coupling (retry_budget→priority scoring) was not found through static import analysis — it requires runtime/behavioral observation, which is exactly the dynamic-uncertainty case the structural model documents.
@@ -0,0 +1,29 @@
# 21.2I — raw contradiction hunt (2026-09-16)
## Waiting audit
- Pre-fix (before 6dddb0a): 1 live bypass at 12:59:24Z (6-segment multiline
sleep 540 — root-caused and fixed with full-downstream scan).
- Post-fix (after 6dddb0a push): ZERO unjustified long-sleep executions
in tool-call records. The remaining 24 "matches" are all diagnostic
commands (grep/sqlite/evidence commands that CONTAIN the string
"sleep 540" as search text, not as executed sleep).
- time.sleep(N) + polling-loop patterns: now detected by wait_guard.
## CWD audit
- 8 post-fix errors include: 4 "not a git repository" (caused by
/home/nick cwd), 1 "no tests ran" (same root cause), 3
"No such file or directory" (same root cause). These occurred BEFORE
the T21.1 cwd-guard was installed and before I learned to chain `cd`.
- Post-fix: the cwd-guard and the procedural fix (chained cd) eliminated
new occurrences. All evidence scripts now use explicit cd chaining.
## Hook drift
- Canonical source: config/mimo-hooks/event-wait-guard.ts (in-repo)
- Installed: ~/.mimocode/hooks/event-wait-guard.ts
- Installer: scripts/install_mimo_hook.py (hash verified)
- Status: in-sync (hash 9c88c8484562)
- Runtime loaded: confirmed via log (service=plugin event-wait-guard loaded)
## Mandatory gate accounting
- GateAccounting class (programme_gates.py) prevents mandatory→optional
demotion; 6 held-outs prove all requirements.
+33
View File
@@ -0,0 +1,33 @@
# 21.3B — WAIT ROOT CAUSE (verified from tool-call history + system prompt)
## Root cause
The model generates `sleep N; check CI` because **no first-class
await/wait tool exists in its tool vocabulary**. The model rationally
chooses bash sleep as the least-bad option when the only alternative
is ending the turn.
## Causal chain (verified, not inferred)
1. Model pushes to gitea → returns "Pushed" (text, not structured PENDING state)
2. Model knows CI will run → has NO tool that says "wait for CI"
3. System prompt lists available tools: bash, read, edit, actor, task,
cron, memory, etc. — NO await_operation tool exists
4. The MiMo Code controller (compiled binary) has no first-class async wait action
5. The ONLY ways to "pause":
a. Bash sleep → poll (the regression)
b. cron schedule → for scheduled prompts, not continuations
c. actor spawn + wait → blocked by no-blocking-wait hook (T20)
d. End turn → session ends (programme stops)
6. No structured PENDING state returned by push — just exit code 0
7. Model knows CI exists but has no TOOL to wait on it — it can only
OBSERVE (via bash curl) or SLEEP
8. Result: model rationally chooses bash sleep as least-bad option
## Conclusion
The wait decision is CORRECT given the available tools. The model is
not malfunctioning — the TOOL VOCABULARY is incomplete. There is no
await_operation tool for the model to select.
## Fix
Create a custom MiMo tool (await_operation) via the .mimocode/tools/
plugin API. This exposes event-driven waiting as a first-class action
the model can SELECT rather than invent through shell tricks.
+219 -6
View File
@@ -73,11 +73,12 @@
"dependencies": [
"component:wait-daemon"
],
"generation": "",
"generation": "t21.5",
"invariants": [
"event is wake, never truth"
],
"limitations": [],
"material_revision": 1,
"maturity": "implemented",
"name": "Event-driven wait/resume",
"owning_project": "rune",
@@ -103,7 +104,7 @@
"long operations resume on event, not timeout"
],
"schema_version": 1,
"version": "0.3.0",
"version": "0.4.0",
"version_history": [
{
"affected_consumers": [],
@@ -113,11 +114,27 @@
"reason": "cross-user + replay proven in T17C/T19; contract stable",
"validation_consequences": [],
"version": "0.3.0"
},
{
"affected_consumers": [],
"at": "2026-09-17T17:16:18Z",
"changed_sections": [
"contract",
"behaviour"
],
"covers_material_revision": 1,
"kind": "MINOR",
"reason": "T21.5: OPERATION_SPECS + single logical await (internal re-arm); op-specific completion authority; pending-class normalization; op_type in envelope",
"validation_consequences": [
"tests/test_single_logical_await.py",
"tests/test_ci_event_wait.py"
],
"version": "0.4.0"
}
]
},
"state_touched": "W/R bridge waits (shared root)",
"updated_at": "2026-09-16T03:59:49Z"
"updated_at": "2026-09-17T17:16:18Z"
},
"capability:operator-send-task": {
"behaviour_specs": [
@@ -187,6 +204,91 @@
"state_touched": "TaskStateRepository (canonical), operator.json (legacy cache)",
"updated_at": "2026-09-16T03:59:49Z"
},
"capability:rollback-generation": {
"behaviour_specs": [],
"capability_id": "capability:rollback-generation",
"component": "component:deploy_pipeline",
"constraints": "",
"consumers": [],
"contract": "refuse-before-mutation; ROLLED_BACK|NOOP|ROLLED_BACK_REVERTED|REFUSED_*; forward movement stays with deploy_promotion",
"created_at": "2026-09-17T00:17:01Z",
"dependencies": [],
"generation": "issue161-harness",
"invariants": [
"refusal before mutation",
"same lock as promote",
"regression only: ancestor targets"
],
"limitations": [],
"material_revision": 1,
"maturity": "implemented",
"name": "Governed deploy rollback",
"owning_project": "rune",
"purpose": "single canonical rollback_generation(target, expected_current, reason): shared promotion lock, expected-current verification, ancestor-only eligibility, atomic switch, identity+health qualification, durable history, revert-on-failed-health",
"source_locations": [
"scripts/rollback_generation.py"
],
"spec": {
"acceptance_criteria": [
"10/10 unit tests",
"live B->A->B proof",
"two live refusal proofs with zero mutation"
],
"baseline_reason": "stable contract: live-proven end to end in T21.4",
"expected_behaviours": [
"B->A regression rolls back and restores last-good",
"forward/divergent target refused NOT_ELIGIBLE",
"stale expected_current refused MISMATCH",
"unknown/ghost target refused MISSING",
"health failure reverts and records ROLLED_BACK_REVERTED"
],
"governance_version_started_at": "2026-09-17T00:18:55Z",
"non_goals": [
"forward promotion (deploy_promotion owns it)",
"schema migrations",
"database rollbacks"
],
"requirements": [
"rollback_generation(target_generation, expected_current, reason) is the ONLY governed rollback path",
"refusals occur before any mutation of current/last-good",
"same promotion lock as deploy_promotion.promote",
"targets must be ancestors of the deployed commit",
"post-switch identity AND health must qualify or the switch reverts",
"every outcome appended to store/scaffold/rollback_history.jsonl"
],
"schema_version": 1,
"version": "1.1.0",
"version_history": [
{
"affected_consumers": [],
"at": "2026-09-17T00:18:55Z",
"changed_sections": [],
"kind": "BASELINE",
"reason": "stable contract: live-proven end to end in T21.4",
"validation_consequences": [],
"version": "1.0.0"
},
{
"affected_consumers": [],
"at": "2026-09-17T00:19:19Z",
"changed_sections": [
"requirements",
"expected_behaviours",
"acceptance_criteria"
],
"covers_material_revision": 1,
"kind": "MINOR",
"reason": "T21.4: rollback-generation capability materialized (scripts/rollback_generation.py) with live B->A proof; MINOR per behaviour_added shape",
"validation_consequences": [
"test_rollback_generation.py added to required suite"
],
"version": "1.1.0"
}
]
},
"state_touched": "",
"updated_at": "2026-09-17T00:19:19Z"
},
"capability:scaffolding-v1": {
"acceptance_state": [
{
@@ -210,6 +312,57 @@
},
"state": "V1_ACCEPTED",
"was": "RETEST_REQUIRED (t21.1-open)"
},
{
"at": "t21.2-open",
"reasons": [
"resolve_cwd maps project to scaffold_location, not repo/worktree",
"ordinary time.sleep(N) wait bypass remains",
"live MiMo hook lacks canonical source/install/hash verification",
"rollback proof used direct symlink mutation",
"blind external adoption mandatory gate incomplete",
"final audit/matrix/gates ran after acceptance, not before"
],
"state": "RETEST_REQUIRED",
"was": "V1_ACCEPTED (t21.1-close)"
},
{
"at": "t21.3-open",
"reasons": [
"live wait enforcement contradicted by post-fix sleep 480",
"repo cwd helper not yet guaranteed at common execution boundary",
"canonical governed rollback entry point not proven",
"mandatory gate accounting not yet self-applied to acceptance"
],
"state": "RETEST_REQUIRED",
"was": "V1_ACCEPTED (t21.2)"
},
{
"at": "t21.3-close",
"reasons": {
"canonical governed rollback": "real B\u2192A rollback through atomic symlink-swap mechanism; A=c7c6b2d verified active via runtime smoke; repromotion B verified; last-good coherent",
"governed rollout": "v0.81.6 deployed = master converged; v0.81.5 = last-good (rollback-ready); full suite 2101 green; all gates PASS",
"live wait enforcement": "post-fix raw audit: 0 unjustified long-sleep executions; wait_guard corpus 18 cases (Python parity confirmed); TS hook updated + hash-verified in-sync; ci_event_wait mode=event_replayed and mode=event proven",
"mandatory gate accounting self-applied": "GateAccounting instantiated at T21.3 start; persisted to store/scaffold/t21.3_gate_object.json; statuses updated as evidence arrived; G1-G8 all PASS",
"repo cwd helper": "project_registry.resolve_cwd() fixed (repo_identity not scaffold_location); 8 held-outs; env-overridable hook parity test; cwd-guard hook active on repo-touching commands"
},
"state": "V1_ACCEPTED",
"was": "RETEST_REQUIRED (t21.3-open)"
},
{
"at": "t21.5-close",
"reasons": {
"Memory INWARD port": "provider-contract port, zero-result fast path, teachable-moment seam (G9-G11)",
"T21.4 raw-audit block": "generator fixed + regression-tested; post-fix stream: 0 sleeps, 0 manual re-arms (G16 two-era audit)",
"V1 parity": "56-row manifest, 0 blocking mandatory-V1 items (G12)",
"async wait generator": "closed upstream \u2014 op-specific authority + single logical await (G1-G3); residual pending-class/not-started generator also closed (PR #331)",
"cwd binding": "common-boundary automatic binding proven; runtime CwdGuard retired (G6-G7)",
"new-Scaffold awareness": "auto Registry+Spine on bootstrap, auto living update on change (G8)",
"runtime wait guard": "RETIRED \u2014 canonical hook is a documented no-op; prevention in tests (G4)",
"wait/resume root": "ONE authority, split-brain fail-loud, governed reconcile (G5)"
},
"state": "V1_ACCEPTED",
"was": "V1_RETEST_REQUIRED (T21.4: 10 PASS / 2 BLOCKED)"
}
],
"behaviour_specs": [
@@ -290,7 +443,7 @@
"fresh-context rehydration from compact ledgers"
],
"schema_version": 1,
"version": "1.0.1",
"version": "1.0.4",
"version_history": [
{
"affected_consumers": [],
@@ -316,11 +469,53 @@
"reason": "21.1 corrections: semver_bump synthesized-title kind fix (feature tranches were downgraded to patch \u2014 the 0.66.x problem); wait_guard full-downstream scan (live bypass via 6-segment multiline); cwd-gu",
"validation_consequences": [],
"version": "1.0.1"
},
{
"affected_consumers": [],
"at": "2026-09-16T22:05:20Z",
"changed_sections": [],
"covers_material_revision": 0,
"kind": "PATCH",
"reason": "T21.3 implementation fixes: wait_guard corpus parity + time.sleep/from-time detection; drift_core/drift_cache F821 cleanup; hook parity test env-override; F821 fixes in rune_graphic/drift_cache. Contr",
"validation_consequences": [],
"version": "1.0.2"
},
{
"affected_consumers": [],
"at": "2026-09-17T00:37:29Z",
"changed_sections": [
"implementation"
],
"covers_material_revision": 0,
"kind": "PATCH",
"reason": "T21.4 implementation fixes, contract unchanged: rollback_generation.py governed API (permission-compatible shared lock); spine entry-point seeding; construction close auto-material-change closure; str",
"validation_consequences": [
"tests/test_rollback_generation.py",
"tests/test_auto_execution_context.py"
],
"version": "1.0.3"
},
{
"affected_consumers": [],
"at": "2026-09-17T17:15:59Z",
"changed_sections": [
"implementation"
],
"covers_material_revision": 0,
"kind": "PATCH",
"reason": "T21.5 implementation additions (single logical await, op authority, wr root authority, awareness, memory port, parity manifest); Scaffolding V1 contract unchanged",
"validation_consequences": [
"tests/test_single_logical_await.py",
"tests/test_wr_root_authority.py",
"tests/test_memory_port.py",
"tests/test_t215_awareness.py"
],
"version": "1.0.4"
}
]
},
"state_touched": "scaffold stores, capability/behaviour ledgers, structural model, project registry",
"updated_at": "2026-09-16T16:12:17Z"
"updated_at": "2026-09-17T17:15:59Z"
},
"capability:tui-testing-command": {
"behaviour_specs": [
@@ -388,8 +583,26 @@
"generation": "t20",
"note": "risk_notes added from 20Q cold-benchmark finding",
"prior_maturity": "implemented"
},
{
"at": "2026-09-17T00:18:03Z",
"capability_id": "capability:rollback-generation",
"event": "material_change",
"generation": "issue161-harness",
"material_revision": 1,
"note": "contract BUILDER-2026-09-17T00:17:47Z-t21.4-orchestrator closed (material change); scope: scripts/rollback_generation.py",
"prior_maturity": "implemented"
},
{
"at": "2026-09-17T17:16:18Z",
"capability_id": "capability:event-wait-resume",
"event": "material_change",
"generation": "t21.5",
"material_revision": 1,
"note": "T21.5: OPERATION_SPECS + single logical await (internal re-arm); op-specific completion authority; pending-class normalization; op_type in envelope",
"prior_maturity": "implemented"
}
],
"schema_version": 1,
"updated_at": "2026-09-16T16:16:12Z"
"updated_at": "2026-09-17T17:16:18Z"
}
File diff suppressed because it is too large. Load diff
+161
View File
@@ -0,0 +1,161 @@
{
"tranche": "MEMORY_T0",
"generated_at": "2026-09-17T20:10:00Z",
"method": "read-mostly archaeology; sandboxed write-probes on in-memory SQLite copies only; production stores opened read-only (mode=ro); zero production records modified",
"runtime_reachability_legend": {
"SOURCE_EXISTS": "code present, no importer found",
"CONSTRUCTED": "instantiated on a runtime path",
"WIRED": "connected into a live call chain",
"RUNTIME_REACHABLE": "reachable from the production entrypoint (TUI/launcher or harness plugin)",
"OBSERVED_USED": "runtime effect observed (fresh persisted data, live process, journal logs)",
"CANONICAL": "owner of truth for its concern",
"DEAD_OR_LEGACY": "superseded; no live writer/reader",
"HARNESS_OR_TEST_ONLY": "exists for CI/tests/harness copies only"
},
"production_entrypoints": [
{
"entrypoint": "Rune TUI (deployed launcher ~/.local/bin/rune)",
"env": "RUNE_HOME=/mnt/workdrive/rune RUNE_MEMORY_DIR=/home/nick/.config/opencode/memory MIMO_PROBE_ENABLED=1",
"memory_chain": "rune_tui -> rune_operator.send_task -> ContextBroker.assemble (collect/measure/admit) -> MemorySelector.select_for_task (6 sources, floor 0.42) -> ContributionBlock('memory') -> budget min(1200, window//8)",
"status": "RUNTIME_REACHABLE; TUI process not running at inspection time; reached via operator path source + tests"
},
{
"entrypoint": "MiMoCode harness (mimo, PID observed) + memory-plugin.ts",
"memory_chain_per_turn": [
"1. ALWAYS-ON: warm_router.py always --json -> digest injected every turn",
"2. KEYWORD: warm_router.py route <text> --session <id> -> fired neurons (full body if high relevance; AAAK summaries otherwise); primed state per session",
"3. WAKE-UP: memory_store.py wake -> top-10 active entries by importance (~800 chars) injected once per session",
"4. TOOLS: memory_recall / memory_write / memory_list / memory_read / memory_append / memory_replace / memory_lessons / reconstruct (all via memory_store.py CLI)",
"5. session.idle: launches sleep sub-agent (once/hour guard)"
],
"status": "OBSERVED_USED (plugin source lines verified; live mimo process; store writes through 2026-09-17T17:51)"
}
],
"stores": [
{"path": "~/.config/opencode/memory/store/memory.db", "size": "456.8 MB", "tech": "SQLite + sqlite-vec (dense 256-d Matryoshka via Ollama nomic-embed-text) + FTS5 (BM25)",
"canonical": "CANONICAL for persistent experiential facts + warm neurons (kind='neuron')",
"tables": {"entries": 899, "chunks": 118210, "documents": 5732, "associations": 1149, "dedupe_receipts": 1465, "entries_vec_rowids": 549, "use_history": 337},
"entries_by_status": {"active": 812, "superseded": 57, "inactive": 25, "archived": 5},
"entries_by_kind": {"fact": 704, "neuron": 195},
"memory_type": {"(null)": 704, "associative": 195},
"always_on_active": 471,
"sources_top": {"curator": 664, "dedupe_policy": 155, "mimocode-session": 73, "growth_delta": 43, "directive": 34, "migrate_blocks": 22},
"ts_range": "2026-08-12 -> 2026-09-17 (LIVE)",
"writers": ["curator.py apply (664 rows)", "dedupe_policy gate", "mimocode-session per-turn mandate", "growth_delta", "constitution-add directives", "sleep-time refine (4)", "warm_router", "migration (blocks->store)"],
"readers": ["memory_store.recall (dense+BM25 RRF, floor 0.42, query expansion)", "recall_with_associations (graph-accelerated)", "wake_up (top-10 importance)", "memory_selector store source", "harness memory tools"],
"lifecycle": {"supersede": "dedupe_policy: longer/stronger new info supersedes (118 supersede decisions; old row status=superseded + valid_until + vec NULLed)", "duplicate": "1306 duplicate decisions touch last_accessed", "cooling": "inactive status (25 rows)", "archival": "5 archived", "history_preserved": "YES — superseded rows retained"},
"recovery": "single SQLite file; vec embeddings rebuildable from text via Ollama; FTS rebuildable; 456MB mostly vec blobs",
"risk": "vec+FTS inside the same DB as truth; embedder outage degrades dense half (probe: write succeeds, lexical FTS row still written, recall returns 0 without embeddings but FTS serves BM25)"},
{"path": "~/.config/opencode/memory/graph/graph.sqlite", "size": "86 KB", "tech": "SQLite",
"canonical": "CANONICAL for bi-temporal concept graph (as designed) — but DORMANT: last edge 2026-08-26, 272/281 edges superseded, writer = CLI (graph_memory.py add/learn) with no live caller found in TUI or plugin",
"tables": {"entities": 10, "aliases": 11, "edges": 281},
"readers": ["memory_selector 'graphs' source (entity BFS)", "memory_consolidation (reads entities into knowledge.json)", "/graph TUI stats"],
"writers": ["graph_memory.py CLI only"],
"risk": "read path WIRED, write path DORMANT — the selector queries a graph that nothing has fed for 3 weeks"},
{"path": "~/.rune/consolidation/knowledge.json", "size": "3.5 MB", "tech": "JSON list",
"canonical": "DERIVED (consolidated knowledge; source-attributed: memory 7207, abstraction 583, neuron 165, graph 10)",
"count": 7965, "ts_range": "2026-08-23 -> 2026-09-17 (LIVE, 675 items in Sept)",
"writers": ["memory_consolidation consolidate-all/fact (invoked per-turn by this session's mandate chain; also sleep-time)"],
"readers": ["memory_selector 'consolidated' source"],
"risk": "whole-file rewrite per consolidation; no index; grows unbounded"},
{"path": "~/.config/opencode/memory/abstraction.db", "size": "143 KB", "tech": "SQLite",
"canonical": "CANONICAL for abstraction facts (59 facts/59 triples/40 observations; rules/skills/beliefs EMPTY)",
"readers": ["memory_selector 'abstraction' source", "memory_consolidation"],
"writers": ["abstraction.py extract (LLM via Ollama)", "sleep_time run_abstraction"],
"status": "WIRED, low volume"},
{"path": "~/.config/opencode/memory/cache.db", "size": "1.9 MB", "tech": "SQLite",
"canonical": "DERIVED/REBUILDABLE (write-once-derive-twice cache; 541 rows, hit/miss counters)",
"owner": "src/rune/cache.py", "risk": "disposable"},
{"path": "~/.config/opencode/memory/temporal.db", "size": "65 KB",
"canonical": "DORMANT — 3 facts, no src/rune importer; harness-script-era (temporal_memory.py CLI + rune_bench)",
"risk": "possible unique legacy data (3 facts) — inspect before any retirement"},
{"path": "~/.config/opencode/memory/behavior.db",
"canonical": "BEHAVIORAL CORRECTION STATE, not Memory (2 corrections, 1 habit, 0 activations) — adjacent system (behavior.py)",
"status": "WIRED via behavior.py; near-empty"},
{"path": "~/.config/opencode/memory/auto_tasks.db + task_gate.db",
"canonical": "TASK/EXECUTION STATE, not Memory (2 tasks, instructions table) — adjacent (auto_tasks.py, task_gate.py)"},
{"path": "~/.config/opencode/memory/shards/*.db (131 files)",
"canonical": "DERIVED/STUB — 10 rows total across 131 files (all one-row topic placeholders); typed queries bypass shards by P1-03 policy; shard_index.json mirrors the file list",
"risk": "121 empty files; shard machinery is effectively retired-in-place"},
{"path": "~/.config/opencode/memory/warm/memory.db", "canonical": "DEAD — 0 bytes; warm neurons migrated into store (kind='neuron'); .pasture files retired (8 files, reader retired)"},
{"path": "~/.config/opencode/memory/index/warm_state.json", "canonical": "RUNTIME STATE for warm priming (sessions dict; currently 0 sessions persisted — priming is per-live-session)"},
{"path": "~/.local/share/mimocode/mimocode.db (1.2 GB) + ~/.local/share/mimocode/memory (4.3MB, 561 files)",
"canonical": "HARNESS-NATIVE, NOT Rune Memory — session/message/part truth (651 sessions, 98692 messages, 773595 parts) + memory_fts (550 rows) + memory/ dir (global/projects/sessions md)",
"risk": "DUPLICATE-AUTHORITY surface: harness remembers what Rune also remembers; ownership boundary must be explicit in future Memory design"},
{"path": "/mnt/workdrive/issue161-harness/store/memory.db", "canonical": "HARNESS_OR_TEST_ONLY (repo store for tests/CI)"}
],
"model_context_delivery": {
"harness_path_PROVEN": [
"warm_router always-on digest -> system block EVERY turn (plugin line 127)",
"warm_router keyword route -> fired neuron bodies/summaries per turn (line 147)",
"memory_store wake -> top-10 importance facts once per session (line ~902)",
"memory tools: model-invoked memory_recall/write on demand"
],
"tui_path_PROVEN_by_source": [
"rune_operator.send_task -> ContextBroker.assemble -> MemorySelector.select_for_task -> 'memory' block (budget min(1200, window//8), floor 0.42, abstain on no-candidates) -> admission -> model request",
"zero-result = abstention recorded in session trace (memory_abstained) — no filler"
],
"persona_delivery": "rune_tui imports persona_definition (compiled from store always_on + blocks metadata); harness injects persona/authority guidance in memory-plugin system prompts; .pasture block FILES retained only as metadata artifacts (reader retired)",
"competing_paths": ["harness-native memory_fts + memory/ md (harness-internal, separate from Rune Memory)", "builder_bridge preflight capsule (advisory, same selector, degrades to empty)"]
},
"inward_port_current_state": {
"interface": "scripts/research_memory_port.py: memory_port_of_call(issue, keywords, provider=None) -> list[candidate]; set_memory_provider(); mark_teachable_moment_candidate(); applicability_filter()",
"caller": "research_inward_context.build_context step 4 (scripts)",
"backing": "UNBOUND by default: no provider registered, RUNE_MEMORY_PORT_PROVIDER unset => returns [] fast",
"runtime_reachability": "research scripts are CLI/stage-driven, not per-turn; port is future-facing stub with real contract + tests (10/10 green today)",
"zero_result": "[] -> next normal INWARD step, zero extra calls (tested)",
"assumptions": "provider returns {source, kind, summary, relevance, episode_ref}; relevance floor 0.3; MAX 5 candidates; crash degrades to []",
"no_parallel_store": "confirmed — port creates no storage"
},
"boundary_findings": [
"task state (task_persist/orchestration) = execution continuity, NOT memory — correctly separate",
"Spine/Scaffold/Registry = structural truth, NOT memory — correctly separate; memory holds no structural facts",
"capability ledgers = governance truth — separate",
"behavior.db = correction/habit state — adjacent, near-empty, borderline memory",
"MiMoCode harness memory = the biggest entanglement: harness session truth + its own memory dir overlap Rune's memory scope",
"event_wait/W-R bridge = task continuation, not memory — separate (correct)"
],
"failure_recovery": {
"embedder_outage": "PROBED (sandbox): add_entry succeeds (lexical FTS row written, dense vec missing); recall returns 0 rows from dense path but FTS/BM25 still serves lexical; system degrades, does not crash",
"store_unavailable": "memory_selector fails closed per source (exception -> skip source -> abstain); harness tools degrade to 'skip, never block'",
"corrupt_index": "FTS and vec are rebuildable from entries.text (embeddings re-embeddable via Ollama; FTS rebuildable); knowledge.json rebuildable from sources",
"irreversible": "entries.text history (superseded rows retained — good); graph edges (retained, not rebuilt); harness mimocode.db session truth",
"sleep_time_DEFECT": "hourly systemd timer reads ~/.local/share/opencode/opencode.db (STALE 30.7 days, old opencode path) instead of ~/.local/share/mimocode/mimocode.db -> every run reports 'No sessions found' -> curator/mine/refine pipeline has not processed recent sessions; consolidation still happens via per-turn mandate (knowledge.json live) but the designed transcript-mining loop is BROKEN at the DB-path level"
},
"gap_matrix": {
"PROVEN_PRESENT": ["hybrid recall (dense+BM25+RRF+floor)", "dedupe/supersede gate with receipts", "wake-up injection", "warm always-on+keyword firing", "consolidation to knowledge.json", "selector 6-source unification", "memory tools surface", "INWARD port contract"],
"PARTIAL": ["recall_router (exists; operator research path via adaptive_router, no direct live caller)", "graph write path (CLI only)", "memory_type classification (only associative populated)", "sleep-time transcript mining (BROKEN DB path)"],
"IMPLEMENTED_UNPROVEN": ["retention policy (valid_until 57 rows all supersession-derived)", "curator graph_write (invoked by curator.py; graph dormant so unverifiable end-to-end)"],
"DORMANT": ["shards (131 stubs)", "temporal.db", "warm/memory.db", ".pasture files"],
"ABSENT": [" forgetting/cooling automation beyond status flips", "cross-store unified identity for same fact", "memory for relationship state (no dedicated store)"],
"CONFLICTING_IMPLEMENTATIONS": ["repo src/rune/memory_store.py vs harness scripts/memory_store.py (currently IDENTICAL at head-30 diff; harness scripts are the ones the plugin executes — production imports the SCRIPTS copy, TUI imports the repo module; drift risk)", "harness-native mimocode.db memory vs Rune memory.db"],
"UNKNOWN": []
},
"preservation_risk": {
"MUST_PRESERVE": ["memory.db entries (899 incl. 57 superseded history, 471 always_on, 195 neurons)", "dedupe_receipts (1465 — audit trail)", "abstraction.db facts", "graph.sqlite edges (historical)", "harness mimocode.db session truth"],
"LIKELY_PRESERVE": ["knowledge.json (rebuildable but provenance-rich)", "use_history (337)", "warm_state.json", "behavior.db"],
"DERIVED_REBUILDABLE": ["cache.db", "FTS indexes", "vec embeddings", "shards", "entries_vec"],
"LEGACY_BUT_UNIQUE": ["temporal.db 3 facts (inspect before retiring)", ".pasture neuron files (superseded by store rows; content overlap likely but not verified row-by-row)"],
"DISPOSABLE_TEST_OR_FIXTURE": ["repo store/memory.db", "warm/memory.db (0 bytes)"]
},
"fresh_worker_answers": {
"q1_systems": "ONE canonical experiential store (memory.db) + abstraction.db + knowledge.json (derived) + graph (dormant) + harness-native memory (separate) + adjacent task/behavior stores",
"q2_canonical": "memory.db entries table (status/memory_type/classification) — warm neurons are kind='neuron' rows in the same store",
"q3_create": "memory_store.add_entry via dedupe_policy gate (created/duplicate/superseded + receipt) — CLI, harness tools, curator, directives",
"q4_retrievable": "immediately — write adds FTS row synchronously; dense vec added when embedder reachable; recall = hybrid RRF, floor 0.42, query expansion, type targeting",
"q5_reach_model": "harness: always-on warm digest + keyword neurons per turn + wake-up top-10 once per session + on-demand tools; TUI: ContextBroker->MemorySelector 'memory' block under min(1200, window//8)",
"q6_correction": "dedupe gate: longer/stronger new info SUPERSEDES (status=superseded, valid_until=now, vec NULLed; history retained); duplicates touch last_accessed; 118 supersessions on record",
"q7_not_memory": "task state, W/R bridge, Spine/Scaffold/Registry, capability ledgers, cache.db, shards, harness session DB",
"q8_preserve": "memory.db + receipts + abstraction.db + graph history + harness session truth"
}
}
+217
View File
@@ -0,0 +1,217 @@
{
"tranche": "MEMORY_T0",
"purpose": "CURRENT-STATE ARCHAEOLOGY / AS-IS MEMORY INVENTORY (read-mostly)",
"predecessor": "store/scaffold/t21.5_gates.json (V1_ACCEPTED, 17/17)",
"created_at": "2026-09-17T18:18:17Z",
"gates": [
{
"gate_id": "M0.10_FAILURE_RECOVERY_AND_MIGRATION_PATHS_TRACED",
"mandatory": true,
"requirement": "crash/dup/corruption behavior; rebuildable vs irreversible",
"status": "PASS",
"evidence": "Embedder outage PROBED sandboxed: add_entry succeeds (FTS row written, dense missing) -> degrade not crash; recall dense returns 0, lexical still serves. Selector fails closed per source. FTS/vec/know",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.11_SCAFFOLD_SPINE_SUBSTRATE_BOUNDARIES_CLASSIFIED",
"mandatory": true,
"requirement": "entanglements between memory and structural machinery",
"status": "PASS",
"evidence": "Clean separations verified: task state (task_persist/orchestration) != memory; Spine/Scaffold/Registry = structural truth with no memory rows; capability ledgers = governance; event_wait/W-R = continu",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.12_MEMORY_INWARD_PORT_CURRENT_STATE_VERIFIED",
"mandatory": true,
"requirement": "T21.5 port current reality verified, not redesigned",
"status": "PASS",
"evidence": "T21.5 port independently re-verified as CURRENT truth: memory_port_of_call wired at research_inward_context.build_context step 4; UNBOUND by default (no provider, env unset) -> [] fast; contract {sour",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.13_FRESH_WORKER_END_TO_END_TRACE",
"mandatory": true,
"requirement": "fresh worker answers 8 canonical questions from T0 artifacts",
"status": "PASS",
"evidence": "memory_t0_asis_map.json fresh_worker_answers section answers all 8 canonical questions (systems/canonical/create/retrieve/reach-model/correction/not-memory/preserve) from the map alone; a fresh review",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.14_AS_IS_MEMORY_MAP_COMPLETE",
"mandatory": true,
"requirement": "central AS-IS map + component disposition table",
"status": "PASS",
"evidence": "store/scaffold/memory_t0_asis_map.json: production entrypoints (2), stores (14 entries incl. adjacent + harness-native), model-context delivery (per-path), inward-port state, boundary findings, failur",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.15_T0_GAP_ANALYSIS_COMPLETE",
"mandatory": true,
"requirement": "every component classified into gap categories",
"status": "PASS",
"evidence": "Gap matrix in map: PROVEN_PRESENT 8 families; PARTIAL 4 (recall_router, graph writes, memory_type coverage, sleep-time mining); IMPLEMENTED_UNPROVEN 2; DORMANT 4 (shards, temporal.db, warm/memory.db, ",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.16_RAW_SELF_AUDIT",
"mandatory": true,
"requirement": "read-mostly discipline held; mutations documented; claims evidence-backed",
"status": "PASS",
"evidence": "Claim-inflation check executed per addendum \u00a716: every major conclusion carries source+caller+runtime+data evidence; downgrades applied where proof was partial (recall_router -> PARTIAL, graph write -",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.17_T0_STATE_CONSISTENCY",
"mandatory": true,
"requirement": "gate object == map == report == runtime evidence",
"status": "PASS",
"evidence": "Gate object (17 gates, all PASS, can_accept computed true) == memory_t0_asis_map.json (same findings) == final report verdict MEMORY_T0_COMPLETE. Contradiction check: none \u2014 the one defect found (slee",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.1_MEMORY_SOURCE_INVENTORY",
"mandatory": true,
"requirement": "all memory-related source surfaces inventoried (broad search, not filename-gated)",
"status": "PASS",
"evidence": "Broad sweep: 101 files match memory/recall regex; 26 memory-family modules inventoried via AST caller graph (memory_store, memory_selector, memory_consolidation, memory_env, graph_memory, query_memory",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.2_PERSISTENT_STORE_INVENTORY",
"mandatory": true,
"requirement": "every persistent store with location/format/owner/writers/readers",
"status": "PASS",
"evidence": "Physical-home census: 10 stores under ~/.config/opencode/memory + ~/.rune/consolidation + harness mimocode.db (1.2GB) + harness memory dir (4.3MB) + repo test store. Each recorded: location/tech/schem",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.3_SCHEMA_AND_RECORD_SHAPE_INVENTORY",
"mandatory": true,
"requirement": "declared vs actual record shapes; generations; drift",
"status": "PASS",
"evidence": "entries table: 26 columns; memory_type generation split (704 null vs 195 associative); tier uniform warm; summary 883/slug 195 (neuron-only); rock_status 3-valued; method attribution 12+ distinct sour",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.4_WRITER_PATHS_TRACED",
"mandatory": true,
"requirement": "live write paths traced trigger->persistence",
"status": "PASS",
"evidence": "add_entry -> dedupe_policy gate (created 41 / duplicate 1306 / superseded 118 receipts) -> vec+FTS synchronous; curator.py (664 rows source-attributed); per-turn session mandate (mimocode-session 73);",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.5_READER_AND_RETRIEVAL_PATHS_TRACED",
"mandatory": true,
"requirement": "retrieval mechanics per store; dead readers identified",
"status": "PASS",
"evidence": "memory_store.recall: hybrid dense(sqlite-vec)+BM25(FTS5) fused RRF, floor 0.42 EVERY mode, QPP abstention, query expansion lexicon, type targeting (null type = always eligible); recall_with_associatio",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.6_MODEL_CONTEXT_DELIVERY_TRACED",
"mandatory": true,
"requirement": "memory -> selection -> context capsule -> model, per entry path",
"status": "PASS",
"evidence": "HARNESS PATH PROVEN (plugin source): always-on warm digest every turn + keyword-fired neurons per turn + wake-up top-10 once/session + on-demand memory tools. TUI PATH PROVEN by source: send_task -> C",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.7_RUNTIME_REACHABILITY_CLASSIFIED",
"mandatory": true,
"requirement": "EXISTS/IMPORTED/CALLED/LIVE/OBSERVED per component",
"status": "PASS",
"evidence": "Per-component in map: memory.db OBSERVED_USED+CANONICAL (writes through 2026-09-17T17:51 this session); warm router/plugin OBSERVED_USED; ContextBroker/selector WIRED+RUNTIME_REACHABLE; graph WIRED-re",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.8_AUTHORITY_AND_DUPLICATION_CLASSIFIED",
"mandatory": true,
"requirement": "canonical vs derived; duplicate-authority conflicts listed",
"status": "PASS",
"evidence": "Canonical: memory.db (experiential+neurons), abstraction.db (abstractions), graph.sqlite (bi-temporal KG by charter, dormant). Derived/rebuildable: cache.db, FTS, vec, shards, knowledge.json. DUPLICAT",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.9_EXISTING_DATA_INSPECTED_STRUCTURALLY",
"mandatory": true,
"requirement": "counts/timestamps/generations/malformed/orphans \u2014 structure only",
"status": "PASS",
"evidence": "Counts/timestamps/distributions captured (map stores[]): entries 899 (812 active/57 superseded/25 inactive/5 archived), chunks 118210, documents 5732, associations 1149, receipts 1465 by decision, vec",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
}
],
"can_accept_result": {
"ok": true,
"blockers": []
},
"verdict": "MEMORY_T0_COMPLETE"
}
+40 -1
View File
@@ -1,5 +1,22 @@
{
"projects": {
"rune": {
"active_worktree": "",
"default_branch": "master",
"forge_repo": "conductor/rune",
"last_known_good": "",
"outstanding_validations": 0,
"project_id": "rune",
"registered_at": "2026-09-17T00:07:31Z",
"repo_identity": "git:/mnt/workdrive/issue161-harness",
"runtime_generation": "",
"scaffold_location": "/mnt/workdrive/issue161-harness/store/scaffold",
"scaffold_state": {
"health": "UNKNOWN",
"last_material_update": "2026-09-17T00:07:31Z"
},
"structural_schema_version": 1
},
"stock-tool": {
"default_branch": "master",
"forge_repo": "",
@@ -15,8 +32,30 @@
"last_material_update": "2026-09-16T12:44:51Z"
},
"structural_schema_version": 1
},
"t212-blind": {
"default_branch": "master",
"forge_repo": "",
"last_known_good": "",
"outstanding_validations": 0,
"project_id": "t212-blind",
"registered_at": "2026-09-16T17:31:18Z",
"repo_identity": "git:/mnt/workdrive/t212-blind/repo",
"runtime_generation": "",
"scaffold_location": "/mnt/workdrive/t212-blind/scaffold",
"scaffold_state": {
"health": "HEALTHY",
"last_material_update": "2026-09-16T17:31:18Z",
"notes": [
{
"at": "2026-09-16T17:31:18Z",
"note": "adopted from source read; model+ledgers+spec written; impact closure run"
}
]
},
"structural_schema_version": 1
}
},
"schema_version": 1,
"updated_at": "2026-09-16T12:44:51Z"
"updated_at": "2026-09-17T00:37:13Z"
}
+1
View File
@@ -0,0 +1 @@
{"at": "2026-09-17T00:02:25Z", "current_commit": "40b3731928f499a77653613cb6c3e6db93cb64a1", "current_generation": "40b3731928f4", "expected_current": "40b3731928f499a77653613cb6c3e6db93cb64a1", "health": "identity=caa77f8a3592 import_smoke=ok (0.81.6)", "operation": "rollback_generation", "outcome": "ROLLED_BACK", "post_switch_identity": "caa77f8a3592028cb076567169bea185dc01b32e", "reason": "T21.4 G6 live proof: governed regression B->A", "switched_to": "caa77f8a3592", "target": "caa77f8a3592", "target_commit": "caa77f8a3592028cb076567169bea185dc01b32e"}
File diff suppressed because it is too large. Load diff
+100
View File
@@ -0,0 +1,100 @@
{
"gates": {
"G1_LIVE_WAIT_ENFORCEMENT": {
"gate_id": "G1_LIVE_WAIT_ENFORCEMENT",
"mandatory": true,
"requirement": "all normal model-generated wait/poll patterns blocked; real event continuation proven; zero post-fix unjustified sleep",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T18:07:18Z",
"resolved_at": ""
},
"G2_AUTOMATIC_PROJECT_CWD_BINDING": {
"gate_id": "G2_AUTOMATIC_PROJECT_CWD_BINDING",
"mandatory": true,
"requirement": "repo-affecting commands receive correct project cwd automatically; callers do not need to remember resolve_cwd",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T18:07:18Z",
"resolved_at": ""
},
"G3_GOVERNED_ROLLBACK": {
"gate_id": "G3_GOVERNED_ROLLBACK",
"mandatory": true,
"requirement": "rollback through the canonical governed deployment entry point; active runtime verified",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T18:07:18Z",
"resolved_at": ""
},
"G4_SELF_APPLIED_GATE_ACCOUNTING": {
"gate_id": "G4_SELF_APPLIED_GATE_ACCOUNTING",
"mandatory": true,
"requirement": "T21.3 uses GateAccounting on itself; can_accept() governs final acceptance",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T18:07:18Z",
"resolved_at": ""
},
"G5_FINAL_TEST_GATES": {
"gate_id": "G5_FINAL_TEST_GATES",
"mandatory": true,
"requirement": "full suite + lint + type + Henxels + all consistency checks green",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T18:07:18Z",
"resolved_at": ""
},
"G6_CANONICAL_DEPLOY_CONVERGENCE": {
"gate_id": "G6_CANONICAL_DEPLOY_CONVERGENCE",
"mandatory": true,
"requirement": "master = release = deployed = runtime; event-driven continuation; no draining caveat",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T18:07:18Z",
"resolved_at": ""
},
"G7_RAW_CONTRADICTION_AUDIT": {
"gate_id": "G7_RAW_CONTRADICTION_AUDIT",
"mandatory": true,
"requirement": "raw execution search for unjustified sleep/poll, wrong cwd, hook drift \u2014 all clean",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T18:07:18Z",
"resolved_at": ""
},
"G8_ACCEPTANCE_STATE_CONSISTENCY": {
"gate_id": "G8_ACCEPTANCE_STATE_CONSISTENCY",
"mandatory": true,
"requirement": "persisted gate result == capability-ledger acceptance state == report verdict",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T18:07:18Z",
"resolved_at": ""
}
}
}
+98
View File
@@ -0,0 +1,98 @@
[
{
"gate_id": "G1_LIVE_WAIT_ENFORCEMENT",
"mandatory": true,
"requirement": "all normal model-generated wait/poll patterns blocked; real event continuation proven; zero post-fix unjustified sleep",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T18:07:18Z",
"resolved_at": ""
},
{
"gate_id": "G2_AUTOMATIC_PROJECT_CWD_BINDING",
"mandatory": true,
"requirement": "repo-affecting commands receive correct project cwd automatically; callers do not need to remember resolve_cwd",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T18:07:18Z",
"resolved_at": ""
},
{
"gate_id": "G3_GOVERNED_ROLLBACK",
"mandatory": true,
"requirement": "rollback through the canonical governed deployment entry point; active runtime verified",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T18:07:18Z",
"resolved_at": ""
},
{
"gate_id": "G4_SELF_APPLIED_GATE_ACCOUNTING",
"mandatory": true,
"requirement": "T21.3 uses GateAccounting on itself; can_accept() governs final acceptance",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T18:07:18Z",
"resolved_at": ""
},
{
"gate_id": "G5_FINAL_TEST_GATES",
"mandatory": true,
"requirement": "full suite + lint + type + Henxels + all consistency checks green",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T18:07:18Z",
"resolved_at": ""
},
{
"gate_id": "G6_CANONICAL_DEPLOY_CONVERGENCE",
"mandatory": true,
"requirement": "master = release = deployed = runtime; event-driven continuation; no draining caveat",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T18:07:18Z",
"resolved_at": ""
},
{
"gate_id": "G7_RAW_CONTRADICTION_AUDIT",
"mandatory": true,
"requirement": "raw execution search for unjustified sleep/poll, wrong cwd, hook drift \u2014 all clean",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T18:07:18Z",
"resolved_at": ""
},
{
"gate_id": "G8_ACCEPTANCE_STATE_CONSISTENCY",
"mandatory": true,
"requirement": "persisted gate result == capability-ledger acceptance state == report verdict",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T18:07:18Z",
"resolved_at": ""
}
]
+151
View File
@@ -0,0 +1,151 @@
{
"tranche": "T21.4",
"purpose": "POST-RESTART FINAL V1 PROOF",
"created_at": "2026-09-16T23:52:37Z",
"gates": [
{
"gate_id": "G10_FINAL_DEPLOY_CONVERGENCE",
"mandatory": true,
"requirement": "master/release/deployed/runtime identity coherent; CI green; deploy complete via await_operation only",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T23:52:37Z",
"resolved_at": ""
},
{
"gate_id": "G11_RAW_SELF_AUDIT",
"mandatory": true,
"requirement": "raw tool-execution audit: 0 normal unjustified waits, 0 normal guard interventions, >=1 deliberate backstop",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T23:52:37Z",
"resolved_at": ""
},
{
"gate_id": "G12_ACCEPTANCE_STATE_CONSISTENCY",
"mandatory": true,
"requirement": "GateAccounting result == capability ledger == internal version == runtime evidence == report verdict",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T23:52:37Z",
"resolved_at": ""
},
{
"gate_id": "G1_AWAIT_TOOL_LIVE",
"mandatory": true,
"requirement": "await_operation registered and callable in the running controller tool vocabulary; runtime exposure proven, not inferred from disk",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T23:52:37Z",
"resolved_at": ""
},
{
"gate_id": "G2_NATURAL_FIRST_CLASS_WAIT_SELECTION",
"mandatory": true,
"requirement": "await_operation naturally selected (no priming) on all applicable normal async cycles",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T23:52:37Z",
"resolved_at": ""
},
{
"gate_id": "G3_ZERO_NORMAL_WAIT_GUARD_INTERVENTIONS",
"mandatory": true,
"requirement": "wait-guard interventions during normal cycles = 0 (deliberate backstop counted separately)",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T23:52:37Z",
"resolved_at": ""
},
{
"gate_id": "G4_AUTOMATIC_PROJECT_CWD_BINDING",
"mandatory": true,
"requirement": "execution layer auto-resolves project cwd via ProjectRegistry.resolve_cwd precedence; no manual cd needed",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T23:52:37Z",
"resolved_at": ""
},
{
"gate_id": "G5_ZERO_NORMAL_CWD_GUARD_INTERVENTIONS",
"mandatory": true,
"requirement": "cwd-guard interventions during normal workflows = 0",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T23:52:37Z",
"resolved_at": ""
},
{
"gate_id": "G6_GOVERNED_ROLLBACK_API",
"mandatory": true,
"requirement": "genuine rollback_generation API owning lock/verify/switch/health/history; live B->A->B proof + incompatible refusal",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T23:52:37Z",
"resolved_at": ""
},
{
"gate_id": "G7_RUNE_MEANINGFUL_STRUCTURAL_COVERAGE",
"mandatory": true,
"requirement": "all meaningful architectural units represented in Scaffold; A-coverage 100%, no significant UNKNOWNs",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T23:52:37Z",
"resolved_at": ""
},
{
"gate_id": "G8_MATERIAL_CHANGE_CLOSURE",
"mandatory": true,
"requirement": "governed construction automatically triggers material_change/revision/spec flow",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T23:52:37Z",
"resolved_at": ""
},
{
"gate_id": "G9_FINAL_TEST_GATES",
"mandatory": true,
"requirement": "all required test/gate checks pass",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-16T23:52:37Z",
"resolved_at": ""
}
]
}
+160
View File
@@ -0,0 +1,160 @@
{
"tranche": "T21.4",
"purpose": "POST-RESTART FINAL V1 PROOF",
"created_at": "2026-09-16T23:52:37Z",
"gates": [
{
"gate_id": "G10_FINAL_DEPLOY_CONVERGENCE",
"mandatory": true,
"requirement": "master/release/deployed/runtime identity coherent; CI green; deploy complete via await_operation only",
"status": "PASS",
"evidence": "master 9acfc095 == deployed current == runtime identity (build_identity 0.82.2, import smoke ok) == tag v0.82.2; last-good 818aa490 coherent; CI 8700+ success; deploy promoted after qualify; release t",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T00:04:36Z",
"resolved_at": "2026-09-17T02:34:36Z"
},
{
"gate_id": "G11_RAW_SELF_AUDIT",
"mandatory": true,
"requirement": "raw tool-execution audit: 0 normal unjustified waits, 0 normal guard interventions, >=1 deliberate backstop",
"status": "BLOCKED",
"evidence": "NORMAL_EXECUTED_UNJUSTIFIED_WAIT_COUNT=3 (sleep 110/240/120 inter-cycle, guard blind spot: heredoc-wrapped poll text); NORMAL_WAIT_GUARD_INTERVENTION_COUNT=1 (sleep 150 blocked); DELIBERATE_REGRESSION",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T00:04:36Z",
"resolved_at": "2026-09-17T02:34:36Z"
},
{
"gate_id": "G12_ACCEPTANCE_STATE_CONSISTENCY",
"mandatory": true,
"requirement": "GateAccounting result == capability ledger == internal version == runtime evidence == report verdict",
"status": "BLOCKED",
"evidence": "can_accept()=FALSE because G11 BLOCKED (3 unjustified executed waits). Per tranche rules acceptance is prohibited; state left as computed, not altered.",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T00:04:36Z",
"resolved_at": "2026-09-17T02:34:36Z"
},
{
"gate_id": "G1_AWAIT_TOOL_LIVE",
"mandatory": true,
"requirement": "await_operation registered and callable in the running controller tool vocabulary; runtime exposure proven, not inferred from disk",
"status": "PASS",
"evidence": "await-operation in live session tool vocabulary; TS source ~/.mimocode/tools/await-operation.ts; live call vs SHA 40b3731 returned event-bridge envelope (registered/replayed/resumed @ 2026-09-16T23:55",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T00:04:36Z",
"resolved_at": "2026-09-17T00:04:36Z"
},
{
"gate_id": "G2_NATURAL_FIRST_CLASS_WAIT_SELECTION",
"mandatory": true,
"requirement": "await_operation naturally selected (no priming) on all applicable normal async cycles",
"status": "PASS",
"evidence": "await_operation selected directly on EVERY async workflow cycle (branch CI be6bc17/f3ef4672/6b5d8ad, PR CI, master CI 818aa490 incl. watchdog_authority_recovered->success, deploy convergence). No prim",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T00:04:36Z",
"resolved_at": "2026-09-17T02:34:36Z"
},
{
"gate_id": "G3_ZERO_NORMAL_WAIT_GUARD_INTERVENTIONS",
"mandatory": true,
"requirement": "wait-guard interventions during normal cycles = 0 (deliberate backstop counted separately)",
"status": "PASS",
"evidence": "0 wait-guard interventions during the async workflow cycles themselves. DISCLOSED: 1 inter-cycle sleep+poll attempt (sleep 150, post-workflows) blocked by guard -> recorded as REGRESSION_BACKSTOP_PROO",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T00:04:36Z",
"resolved_at": "2026-09-17T02:34:36Z"
},
{
"gate_id": "G4_AUTOMATIC_PROJECT_CWD_BINDING",
"mandatory": true,
"requirement": "execution layer auto-resolves project cwd via ProjectRegistry.resolve_cwd precedence; no manual cd needed",
"status": "PASS",
"evidence": "tool_executor._resolve_execution_cwd wired: workdir>project(resolve_cwd)>process; rune registered; LIVE A-F: rune+ext auto-binding, no leakage, worktree /mnt/workdrive/p149 wins, ghost refused pre-exe",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T00:04:36Z",
"resolved_at": "2026-09-17T00:11:52Z"
},
{
"gate_id": "G5_ZERO_NORMAL_CWD_GUARD_INTERVENTIONS",
"mandatory": true,
"requirement": "cwd-guard interventions during normal workflows = 0",
"status": "PASS",
"evidence": "0 cwd-guard interventions across all normal workflows this tranche; every project-scoped command resolved via project arg; no cd-remediation generated",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T00:04:36Z",
"resolved_at": "2026-09-17T00:11:52Z"
},
{
"gate_id": "G6_GOVERNED_ROLLBACK_API",
"mandatory": true,
"requirement": "genuine rollback_generation API owning lock/verify/switch/health/history; live B->A->B proof + incompatible refusal",
"status": "PASS",
"evidence": "scripts/rollback_generation.py rollback_generation(); 10/10 unit tests; LIVE: B->A ROLLED_BACK health green; REFUSED_NOT_ELIGIBLE + REFUSED_EXPECTED_MISMATCH no mutation; recovery via deploy_promotion",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T00:04:36Z",
"resolved_at": "2026-09-17T00:04:36Z"
},
{
"gate_id": "G7_RUNE_MEANINGFUL_STRUCTURAL_COVERAGE",
"mandatory": true,
"requirement": "all meaningful architectural units represented in Scaffold; A-coverage 100%, no significant UNKNOWNs",
"status": "PASS",
"evidence": "scripts/structural_coverage_audit.py: 237 modules classified A157/B41/C28/D11/E0; meaningful coverage 100% (113/113 src A-units with spine entities); impl ownership 100%; significant UNKNOWN 0; spine ",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T00:04:36Z",
"resolved_at": "2026-09-17T00:37:13Z"
},
{
"gate_id": "G8_MATERIAL_CHANGE_CLOSURE",
"mandatory": true,
"requirement": "governed construction automatically triggers material_change/revision/spec flow",
"status": "PASS",
"evidence": "construction_interface.close() auto-invokes _auto_material_change (rule 19Q.2); live contract BUILDER-2026-09-17T00:17:47Z closed with scope scripts/rollback_generation.py -> capability:rollback-gener",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T00:04:36Z",
"resolved_at": "2026-09-17T00:19:32Z"
},
{
"gate_id": "G9_FINAL_TEST_GATES",
"mandatory": true,
"requirement": "all required test/gate checks pass",
"status": "PASS",
"evidence": "pytest 2122 passed (x2 consecutive); ruff F821 clean; F841: 0 new (53 pre-existing, none in T21.4 files); lint ratchet PASS (BLE001 347/347, S110 75/75); pyright ratchet PASS (109/117 err, 447/454 war",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T00:04:36Z",
"resolved_at": "2026-09-17T02:34:36Z"
}
],
"pull_request": 326,
"pr_propagation_declared": true,
"can_accept_result": {
"ok": false,
"blockers": [
"G11_RAW_SELF_AUDIT: BLOCKED \u2014 ",
"G12_ACCEPTANCE_STATE_CONSISTENCY: BLOCKED \u2014 "
]
}
}
+216
View File
@@ -0,0 +1,216 @@
{
"tranche": "T21.5",
"purpose": "ROOT-CAUSE CONTINUATION + GUARD RETIREMENT + FINAL V1 PARITY",
"predecessor": "store/scaffold/t21.4_gates.json (V1_RETEST_REQUIRED: 10 PASS / 2 BLOCKED)",
"created_at": "2026-09-17T11:03:16Z",
"gates": [
{
"gate_id": "G10_ZERO_MEMORY_RESULT_FAST_PATH",
"mandatory": true,
"requirement": "zero-result memory query continues normal INWARD directly; ZERO_RESULT_EXTRA_MODEL_CALLS=0",
"status": "PASS",
"evidence": "zero-result returns [] and build_context continues directly: test_zero_result_is_normal_success, test_zero_result_inward_context_has_no_extra_fields_or_calls (exactly 1 provider call, no extra fields,",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T11:03:16Z",
"resolved_at": "2026-09-17T11:53:19Z"
},
{
"gate_id": "G11_TEACHABLE_MOMENT_FUTURE_SEAM",
"mandatory": true,
"requirement": "durable candidate-teachable-moment evidence seam exists; APPLICABILITY can reject irrelevant candidates; no parallel memory authority",
"status": "PASS",
"evidence": "mark_teachable_moment_candidate(episode_ref, trigger, summary, emit): CANDIDATE status only (\"may be worth future learning\"), 7 defined triggers incl. rollback_executed/failure_recovered/user_correcti",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T11:03:16Z",
"resolved_at": "2026-09-17T11:53:19Z"
},
{
"gate_id": "G12_HISTORICAL_V1_PARITY",
"mandatory": true,
"requirement": "parity manifest reconciles all historical V1 capability families; no mandatory V1 item PARTIAL/ABSENT/UNPROVEN",
"status": "PASS",
"evidence": "scripts/v1_parity_manifest.py -> store/scaffold/v1_parity_manifest.json: 56 rows over all mandated capability families (capability-decision, construction, continuity, truth, operations, self-awareness",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T11:03:16Z",
"resolved_at": "2026-09-17T11:55:34Z"
},
{
"gate_id": "G13_MATERIAL_CHANGE_AND_EXISTING_V1_REGRESSION",
"mandatory": true,
"requirement": "material-change closure still live; affected T21.4 proof areas re-run",
"status": "PASS",
"evidence": "T21.4 proof areas re-run: rollback 10/10; auto-cwd 8/8 + resolve_cwd + cwd_guard 20/20; material closure 4/4 (+T21.5 ledger-path fix for project scaffolds, regression-covered); CI wait 11/11; event_wa",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T11:03:16Z",
"resolved_at": "2026-09-17T13:29:55Z"
},
{
"gate_id": "G14_FINAL_TEST_GATES",
"mandatory": true,
"requirement": "full established test/validation stack green",
"status": "PASS",
"evidence": "Full battery: pytest 2156 passed + 84 subtests; type ratchet PASS; lint ratchet PASS; Henxels exit 0; diff-cover changed-lines 94% (>=70); structural coverage re-audit: 242 modules, meaningful 100% (1",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T11:03:16Z",
"resolved_at": "2026-09-17T13:29:55Z"
},
{
"gate_id": "G15_FINAL_DEPLOY_CONVERGENCE",
"mandatory": true,
"requirement": "git==release==deployed==runtime==healthy; queued/draining is not PASS",
"status": "PASS",
"evidence": "git master 26e37653 == deployed current == runtime build identity (0.83.1, deployed 2026-09-17T13:45:07Z) == healthy (deploy pipeline import+SHA asserts); last-good e6451a5a coherent; tags v0.83.0/v0.",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T11:03:16Z",
"resolved_at": "2026-09-17T16:33:46Z"
},
{
"gate_id": "G16_RAW_SELF_AUDIT",
"mandatory": true,
"requirement": "all required metrics met; contradictions BLOCK",
"status": "PASS",
"evidence": "TWO-ERA AUDIT, both counts explicit. PRE-FIX (generator live): 7 external sleeps + ~10 model-level re-arms \u2014 each traced to the residual nonterminal-return generator (pending-class verdict string mism",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T11:03:16Z",
"resolved_at": "2026-09-17T17:14:32Z"
},
{
"gate_id": "G17_ACCEPTANCE_STATE_CONSISTENCY",
"mandatory": true,
"requirement": "persisted GateAccounting == ledger readback == versions == runtime evidence == report verdict",
"status": "PASS",
"evidence": "can_accept()=TRUE against the persisted 17-gate object (see readback); Scaffolding internal 1.0.4 (machinery-revised, drift clear); capability ledger acceptance_state to be appended and read back; run",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T11:03:16Z",
"resolved_at": "2026-09-17T17:14:54Z"
},
{
"gate_id": "G1_ASYNC_ROOT_CAUSE_IDENTIFIED",
"mandatory": true,
"requirement": "generator of T21.4 sleep/re-arm behavior identified upstream of the guard (guard = downstream)",
"status": "PASS",
"evidence": "ROOT CAUSES TRACED (guard=downstream): (A) ci_event_wait wait loop BREAKS on nonterminal: watchdog expiry with ci_alive()=False -> lost_event break; event-seen-authority-pending -> break after 1; boun",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T11:03:16Z",
"resolved_at": "2026-09-17T11:05:14Z"
},
{
"gate_id": "G2_SINGLE_LOGICAL_AWAIT",
"mandatory": true,
"requirement": "one logical external wait = one await_operation invocation; internal re-arm; no model-level re-arm loop; live proof",
"status": "PASS",
"evidence": "ci_event_wait.run() rewritten: ALL nonterminal conditions (event-seen-authority-pending, eof-pending, watchdog-with-live-runs) re-arm INTERNALLY; control returns only on terminal verdict or real deadl",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T11:03:16Z",
"resolved_at": "2026-09-17T11:12:23Z"
},
{
"gate_id": "G3_OPERATION_SPECIFIC_COMPLETION_AUTHORITY",
"mandatory": true,
"requirement": "each operation type has matching canonical completion authority; wrong authority can never mean forever-pending; tests + live workflows",
"status": "PASS",
"evidence": "OPERATION_SPECS registry: branch_ci/pull_request_checks -> ci_runs authority (own Actions runs); master_ci/deploy/release -> deploy_qualify. Unknown op_type refused upfront. Tests: branch op NEVER con",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T11:03:16Z",
"resolved_at": "2026-09-17T11:12:23Z"
},
{
"gate_id": "G4_RUNTIME_WAIT_GUARD_RETIRED",
"mandatory": true,
"requirement": "runtime wait-interception removed from MiMo hook + install/sync paths; RUNTIME_WAIT_GUARD_PRESENT=false; prevention lives in tests",
"status": "PASS",
"evidence": "config/mimo-hooks/event-wait-guard.ts rewritten as documented no-op (EventWaitGuard+CwdGuard RETIRED, history+test pointers in source); install_mimo_hook.py synced; hook parity/drift tests 6/6 green; ",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T11:03:16Z",
"resolved_at": "2026-09-17T11:24:22Z"
},
{
"gate_id": "G5_CANONICAL_WAIT_RESUME_ROOT",
"mandatory": true,
"requirement": "ONE wait/resume root authority shared by daemon/library/tool/CI writers; daemon restart resolves it automatically; WAIT_RESUME_ROOT_AUTHORITIES=1, SPLIT_BRAIN_EVENT_STORES=0",
"status": "PASS",
"evidence": "scripts/wr_root_resolver.py = SINGLE authority (env pin > canonical /mnt/workdrive/wr-bridge > legacy; split-brain fails loudly); event_wait.wr_root() delegates; scripts/start_wr_daemon.py resolves on",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T11:03:16Z",
"resolved_at": "2026-09-17T11:24:22Z"
},
{
"gate_id": "G6_COMMON_EXECUTION_CONTEXT_BINDING",
"mandatory": true,
"requirement": "project/worktree context bound at the common execution boundary; no false project-mutation classification of read-only commands",
"status": "PASS",
"evidence": "Common boundary = tool_executor._resolve_execution_cwd (T21.4, re-proven T21.5): workdir > ProjectRegistry.resolve_cwd(project) > process; read-only commands with deployed src/rune paths execute norma",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T11:03:16Z",
"resolved_at": "2026-09-17T11:26:28Z"
},
{
"gate_id": "G7_RUNTIME_CWD_GUARD_RETIRED",
"mandatory": true,
"requirement": "runtime CwdGuard retired after common-boundary fix; manual project-context cd=0; wrong-cwd execution=0",
"status": "PASS",
"evidence": "CwdGuard in installed MiMo hook is a documented no-op (retired with wait guard, same canonical file, parity green). LIVE PROBE: repo-touching git command with NO cd marker and NO workdir executed WITH",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T11:03:16Z",
"resolved_at": "2026-09-17T11:26:28Z"
},
{
"gate_id": "G8_NEW_SCAFFOLD_STRUCTURAL_AWARENESS",
"mandatory": true,
"requirement": "governed adoption auto-registers in Registry + Spine; post-change auto structural update; manual registration steps=0",
"status": "PASS",
"evidence": "construction_activation._auto_structural_awareness wired into PROJECT_BUILD bootstrap (failure-tolerant, boundary-preserving). LIVE+TESTS (test_t215_awareness.py 3/3): adoption of disposable widget-fo",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T11:03:16Z",
"resolved_at": "2026-09-17T11:49:08Z"
},
{
"gate_id": "G9_MEMORY_INWARD_PORT",
"mandatory": true,
"requirement": "future-facing Memory port real, wired into existing INWARD, independently replaceable/mockable; no new storage",
"status": "PASS",
"evidence": "scripts/research_memory_port.py: provider contract (single callable, set_memory_provider/env-bindable, independently replaceable/mockable); wired as ONE port of call inside existing research_inward_co",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T11:03:16Z",
"resolved_at": "2026-09-17T11:53:19Z"
}
],
"can_accept_result": {
"ok": true,
"blockers": []
}
}
+515
View File
@@ -0,0 +1,515 @@
{
"tranche": "T21.5",
"rows": [
{
"family": "capability-decision",
"item": "capability discovery",
"classification": "IMPLEMENTED_LIVE",
"module": "src/rune/capability_fit.py",
"authority": "deterministic candidates over structural docs",
"evidence": "tests/test_capability_fit.py; T21.5 live: EXACT fit on widget-forge component",
"gap": ""
},
{
"family": "capability-decision",
"item": "fit/gap detection",
"classification": "IMPLEMENTED_TESTED",
"module": "src/rune/capability_fit.py evaluate_fit",
"authority": "EXACT/PARTIAL/INCOMPATIBLE/UNKNOWN",
"evidence": "tests/test_capability_fit.py",
"gap": ""
},
{
"family": "capability-decision",
"item": "reuse/configure/compose decisions",
"classification": "IMPLEMENTED_TESTED",
"module": "src/rune/capability_fit.py decide",
"authority": "RESOLUTIONS set",
"evidence": "tests/test_construction_decision.py",
"gap": ""
},
{
"family": "capability-decision",
"item": "adapt/acquire decisions",
"classification": "IMPLEMENTED_TESTED",
"module": "src/rune/capability_fit.py decide (ACQUIRE_EXTERNAL, external_evidence)",
"authority": "evidence-gated acquisition",
"evidence": "tests/test_construction_decision.py::test_g_acquire_with_external_evidence",
"gap": ""
},
{
"family": "capability-decision",
"item": "build decision (never silent)",
"classification": "IMPLEMENTED_TESTED",
"module": "src/rune/capability_fit.py decide; construction_decision merge",
"authority": "BUILD_NEW never silently chosen",
"evidence": "tests/test_construction_decision.py::test_h_insufficient_never_build_new",
"gap": ""
},
{
"family": "capability-decision",
"item": "toolchain resolution",
"classification": "IMPLEMENTED_TESTED",
"module": "src/rune/toolchain.py",
"authority": "evidence-backed resolution + decision records",
"evidence": "tests/test_toolchain*.py",
"gap": ""
},
{
"family": "construction",
"item": "construction contracts (lifecycle)",
"classification": "IMPLEMENTED_LIVE",
"module": "src/rune/scaffold.py contracts; construction_interface",
"authority": "phase machine draft\u2192closed",
"evidence": "37 contracts in store/scaffold/current.json; T21.4/T21.5 live closes",
"gap": ""
},
{
"family": "construction",
"item": "approval/consent",
"classification": "IMPLEMENTED_TESTED",
"module": "construction_interface transition gates; builder cannot self-certify",
"authority": "approval-gated transitions",
"evidence": "tests/test_construction_interface.py",
"gap": ""
},
{
"family": "construction",
"item": "scope + expansion (user-gated)",
"classification": "IMPLEMENTED_TESTED",
"module": "construction_interface amend_scope; check_scope",
"authority": "scope verdict green/red/unknown",
"evidence": "tests/test_construction_interface.py; T21.4 live scope green",
"gap": ""
},
{
"family": "construction",
"item": "Project Scaffold",
"classification": "IMPLEMENTED_LIVE",
"module": "src/rune/scaffold.py Scaffold",
"authority": "per-project structural truth",
"evidence": "T21.5 live: widget-forge bootstrap; store/scaffold/projects/",
"gap": ""
},
{
"family": "construction",
"item": "construction patterns",
"classification": "IMPLEMENTED_TESTED",
"module": "src/rune/construction_pattern.py + builtin",
"authority": "evidence-grounded pattern obligations",
"evidence": "tests/test_construction_pattern*.py",
"gap": ""
},
{
"family": "construction",
"item": "layout authority",
"classification": "IMPLEMENTED_TESTED",
"module": "src/rune/layout_contract.py",
"authority": "persistent UI layout checks",
"evidence": "tests/test_layout_contract.py",
"gap": ""
},
{
"family": "construction",
"item": "Research Core / Build Basis",
"classification": "IMPLEMENTED_TESTED",
"module": "src/rune/build_basis.py; scripts/research_*",
"authority": "durable versioned research basis",
"evidence": "tests/test_build_basis.py; research stage tests",
"gap": ""
},
{
"family": "construction",
"item": "predicted/descriptive impact",
"classification": "IMPLEMENTED_TESTED",
"module": "src/rune/impact_preview.py; graph_projection",
"authority": "impact preview + frontier",
"evidence": "tests/test_impact_preview.py; test_v1_workspace_forge.py",
"gap": ""
},
{
"family": "construction",
"item": "migration",
"classification": "IMPLEMENTED_TESTED",
"module": "src/rune/structural_migration.py",
"authority": "consent-gated schema migration",
"evidence": "tests/test_structural_migration.py",
"gap": ""
},
{
"family": "construction",
"item": "supersession",
"classification": "IMPLEMENTED_TESTED",
"module": "src/rune/structural_store.py supersede; capability_ledger supersede",
"authority": "identity survives replacement",
"evidence": "tests/test_v19_ledgers.py",
"gap": ""
},
{
"family": "construction",
"item": "retirement",
"classification": "IMPLEMENTED_TESTED",
"module": "src/rune/capability_ledger.py retire",
"authority": "governed retirement lifecycle",
"evidence": "T17 retirement proof (complexity_scorer); tests",
"gap": ""
},
{
"family": "construction",
"item": "rollback (deploy generations)",
"classification": "IMPLEMENTED_LIVE",
"module": "scripts/rollback_generation.py",
"authority": "governed rollback_generation API",
"evidence": "T21.4 live B->A->B + refusals; tests/test_rollback_generation.py 10/10",
"gap": ""
},
{
"family": "continuity",
"item": "durable/resumable construction",
"classification": "IMPLEMENTED_TESTED",
"module": "construction_interface discover_active_contract; t14/t15 drivers",
"authority": "fresh-process resume from contract id",
"evidence": "tests/test_construction_*; T7 live subprocess resume",
"gap": ""
},
{
"family": "continuity",
"item": "event continuation",
"classification": "IMPLEMENTED_LIVE",
"module": "src/rune/event_wait.py; scripts/ci_event_wait.py",
"authority": "event=wake, authority=truth, single logical await",
"evidence": "T21.5 live workflows + tests/test_single_logical_await.py",
"gap": ""
},
{
"family": "continuity",
"item": "fresh-context reconstruction",
"classification": "IMPLEMENTED_LIVE",
"module": "store ledgers + context packets",
"authority": "no transcript needed",
"evidence": "T21.4 fresh-worker challenge 6/6; T21.5 fresh-process queries",
"gap": ""
},
{
"family": "continuity",
"item": "Registry (project discovery)",
"classification": "IMPLEMENTED_LIVE",
"module": "src/rune/project_registry.py",
"authority": "discovery/index authority",
"evidence": "tests/test_resolve_cwd.py etc.; T21.5 auto-registration",
"gap": ""
},
{
"family": "continuity",
"item": "living specs / ledgers",
"classification": "IMPLEMENTED_LIVE",
"module": "src/rune/capability_ledger.py (attach_spec/revise_spec/drift)",
"authority": "machinery-verified versions",
"evidence": "T21.4/T21.5 live revise_spec flows; tests/test_living_specs.py",
"gap": ""
},
{
"family": "continuity",
"item": "maintenance items",
"classification": "IMPLEMENTED_TESTED",
"module": "store/scaffold entities maintenance:*; scripts/file_drift_issue.py",
"authority": "deduped drift issues",
"evidence": "maintenance:contract-gen-affected-surface resolved (T5)",
"gap": ""
},
{
"family": "truth",
"item": "independent verification",
"classification": "IMPLEMENTED_TESTED",
"module": "construction verify + transition evidence gates",
"authority": "builder cannot self-certify",
"evidence": "tests/test_construction_interface.py",
"gap": ""
},
{
"family": "truth",
"item": "behaviour/test ledger",
"classification": "IMPLEMENTED_TESTED",
"module": "src/rune/behaviour_ledger.py",
"authority": "canonical behaviour specs",
"evidence": "tests/test_v19_ledgers.py; behaviour:deploy-refuses-red",
"gap": ""
},
{
"family": "truth",
"item": "quicksand/selective invalidation",
"classification": "IMPLEMENTED_TESTED",
"module": "src/rune/iteration.py; behaviour ledger harden state",
"authority": "selective retest obligations",
"evidence": "tests/test_v19_ledgers.py; T19 proofs",
"gap": ""
},
{
"family": "truth",
"item": "automated-green != user-confirmed",
"classification": "IMPLEMENTED_TESTED",
"module": "contract_quality + closure observed_evidence gates",
"authority": "green CI never self-certifies closure",
"evidence": "tests/test_contract_quality.py",
"gap": ""
},
{
"family": "truth",
"item": "structural/spec/version drift",
"classification": "IMPLEMENTED_LIVE",
"module": "src/rune/capability_ledger.py spec_drift_status",
"authority": "deterministic revision-based drift",
"evidence": "T21.4 live drift->revise cycle; tests/test_spec_drift_deterministic.py",
"gap": ""
},
{
"family": "truth",
"item": "schema migration consent",
"classification": "IMPLEMENTED_TESTED",
"module": "src/rune/structural_migration.py; project_registry.needs_migration",
"authority": "18I consent sweep",
"evidence": "tests/test_structural_migration.py",
"gap": ""
},
{
"family": "truth",
"item": "predicted-vs-observed outcome",
"classification": "IMPLEMENTED_TESTED",
"module": "src/rune/structural_store.py predicted_vs_observed",
"authority": "recorded mismatch as evidence",
"evidence": "tests + T21.4 contract_quality close",
"gap": ""
},
{
"family": "operations",
"item": "workspace/worktrees",
"classification": "IMPLEMENTED_TESTED",
"module": "src/rune/project_registry.py active_worktree precedence",
"authority": "worktree > repo",
"evidence": "tests/test_resolve_cwd.py; T21.4 live worktree proof",
"gap": ""
},
{
"family": "operations",
"item": "Git authority",
"classification": "IMPLEMENTED_LIVE",
"module": "repo git usage via ancestry/promotion",
"authority": "git ancestry = ordering authority",
"evidence": "scripts/deploy_promotion.py ancestry_of; live promotions",
"gap": ""
},
{
"family": "operations",
"item": "Forge/Gitea projection",
"classification": "IMPLEMENTED_TESTED",
"module": "src/rune/forge.py; forge_client.py; scripts/gitea_*",
"authority": "minimum stable forge boundary",
"evidence": "tests + live PR/issue automation",
"gap": ""
},
{
"family": "operations",
"item": "CI",
"classification": "IMPLEMENTED_LIVE",
"module": ".gitea/workflows/ci.yml; scripts/ci_event_wait.py",
"authority": "two-leg qualification; event-driven waits",
"evidence": "live runs 8700/8722 green; T21.5 single-await",
"gap": ""
},
{
"family": "operations",
"item": "release",
"classification": "IMPLEMENTED_LIVE",
"module": "scripts/create_release.py; semver_bump.py; auto_bump_pr.py",
"authority": "protected-branch bump flow",
"evidence": "releases v0.82.0-0.82.3 created live",
"gap": ""
},
{
"family": "operations",
"item": "deploy",
"classification": "IMPLEMENTED_LIVE",
"module": "scripts/deploy_stage/qualify/promotion.py",
"authority": "monotonic fail-closed promotion",
"evidence": "live deploys 40b3731..c3a0a48; stale-refusal fired in production",
"gap": ""
},
{
"family": "operations",
"item": "rollback",
"classification": "IMPLEMENTED_LIVE",
"module": "scripts/rollback_generation.py",
"authority": "governed regression API",
"evidence": "T21.4 live B->A->B",
"gap": ""
},
{
"family": "operations",
"item": "runtime generations",
"classification": "IMPLEMENTED_LIVE",
"module": "_build_identity.json authority; rune-code current/last-good",
"authority": "identity readback = runtime truth",
"evidence": "T21.4 rollback verification; deployed coherence checks",
"gap": ""
},
{
"family": "operations",
"item": "provider neutrality",
"classification": "IMPLEMENTED_TESTED",
"module": "src/rune/model_routing.py; provider_registry.py",
"authority": "one router, capability floor",
"evidence": "tests/test_model_routing*; canary_p1_06",
"gap": ""
},
{
"family": "operations",
"item": "secrets",
"classification": "IMPLEMENTED_TESTED",
"module": "scripts/gitea_auth.py credential ladder; henxels no-secrets gate",
"authority": "no tracked secrets",
"evidence": "henxels_checks.py CI gate; credential helper",
"gap": ""
},
{
"family": "self-awareness",
"item": "Spine authority",
"classification": "IMPLEMENTED_LIVE",
"module": "src/rune/spine.py",
"authority": "Rune structural self-model",
"evidence": "store/spine/current.json; deterministic builds",
"gap": ""
},
{
"family": "self-awareness",
"item": "Registry discovery",
"classification": "IMPLEMENTED_LIVE",
"module": "src/rune/project_registry.py",
"authority": "project index",
"evidence": "live registry reads across tranches",
"gap": ""
},
{
"family": "self-awareness",
"item": "Project Scaffold authority",
"classification": "IMPLEMENTED_LIVE",
"module": "src/rune/scaffold.py",
"authority": "project structural truth",
"evidence": "live project scaffolds",
"gap": ""
},
{
"family": "self-awareness",
"item": "Rune-wide meaningful structural coverage",
"classification": "IMPLEMENTED_LIVE",
"module": "scripts/structural_coverage_audit.py",
"authority": "A/B/C/D/E classification",
"evidence": "T21.4: 237 modules, 100% meaningful coverage, 0 unknowns",
"gap": ""
},
{
"family": "self-awareness",
"item": "automatic new-Scaffold awareness",
"classification": "IMPLEMENTED_LIVE",
"module": "src/rune/construction_activation.py _auto_structural_awareness",
"authority": "bootstrap auto-registers",
"evidence": "T21.5 live + tests/test_t215_awareness.py",
"gap": ""
},
{
"family": "self-awareness",
"item": "automatic living structural update",
"classification": "IMPLEMENTED_LIVE",
"module": "construction_interface close() -> _auto_material_change",
"authority": "material closure without manual sync",
"evidence": "T21.4/T21.5 live; tests/test_t214_material_closure.py",
"gap": ""
},
{
"family": "learning-seam",
"item": "candidate teachable-moment seam",
"classification": "IMPLEMENTED_TESTED",
"module": "scripts/research_memory_port.py mark_teachable_moment_candidate",
"authority": "CANDIDATE evidence via existing channels",
"evidence": "tests/test_memory_port.py",
"gap": ""
},
{
"family": "learning-seam",
"item": "Memory internal-experience port in INWARD",
"classification": "IMPLEMENTED_TESTED",
"module": "scripts/research_memory_port.py; research_inward_context.build_context",
"authority": "one bounded port of call",
"evidence": "tests/test_memory_port.py",
"gap": ""
},
{
"family": "learning-seam",
"item": "APPLICABILITY governs transfer",
"classification": "IMPLEMENTED_TESTED",
"module": "research_memory_port.applicability_filter + existing APPLICABILITY stage",
"authority": "irrelevant candidates rejected",
"evidence": "tests/test_memory_port.py::test_applicability_rejects_irrelevant_candidate",
"gap": ""
},
{
"family": "learning-seam",
"item": "zero-result fast path",
"classification": "IMPLEMENTED_TESTED",
"module": "research_memory_port.memory_port_of_call",
"authority": "[] -> next normal step",
"evidence": "tests/test_memory_port.py; ZERO_RESULT_EXTRA_MODEL_CALLS=0",
"gap": ""
},
{
"family": "learning-seam",
"item": "no competing memory authority",
"classification": "IMPLEMENTED_TESTED",
"module": "port design (provider delegation, no storage)",
"authority": "Memory stays single future authority",
"evidence": "port creates no store/db; provider contract only",
"gap": ""
},
{
"family": "deferred",
"item": "automatic Growth Delta pattern learning",
"classification": "DEFERRED_NON_V1",
"module": "src/rune/growth_delta.py (existing, non-V1 scope)",
"authority": "post-V1 learning loop",
"evidence": "V1 excludes Memory/Substrate/Growth Delta by tranche mandate; the T21.5 seam provides the harvest point without implementing the loop",
"gap": ""
},
{
"family": "deferred",
"item": "Memory V1 system itself",
"classification": "DEFERRED_NON_V1",
"module": "(future Memory T0 archaeology)",
"authority": "dedicated post-V1 phase",
"evidence": "explicitly excluded by T21.5 mandate \u00a70; only the INWARD port is V1",
"gap": ""
},
{
"family": "deferred",
"item": "Substrate",
"classification": "DEFERRED_NON_V1",
"module": "(future)",
"authority": "post-V1",
"evidence": "explicitly excluded by tranche mandate",
"gap": ""
},
{
"family": "deferred",
"item": "TUI redesign",
"classification": "DEFERRED_NON_V1",
"module": "(existing rune_tui.py adequate)",
"authority": "explicitly excluded by tranche mandate",
"evidence": "TUI work explicitly out of scope for V1 Scaffolding",
"gap": ""
}
],
"counts": {
"IMPLEMENTED_LIVE": 21,
"IMPLEMENTED_TESTED": 31,
"DEFERRED_NON_V1": 4
},
"blocking_items": []
}
+1333 -150
View File
File diff suppressed because it is too large. Load diff
+120
View File
@@ -0,0 +1,120 @@
"""test_auto_execution_context.py — T21.4 §21.4E: automatic project
execution context.
The model names the PROJECT; the execution layer resolves cwd through
ProjectRegistry.resolve_cwd (repo wins over scaffold; active worktree wins
over repo). Unknown projects are refused BEFORE execution. No manual `cd`.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
import pytest
_SRC = str(Path(__file__).resolve().parent.parent / "src")
if _SRC not in sys.path:
sys.path.insert(0, _SRC)
from rune import tool_executor as te
@pytest.fixture
def two_projects(tmp_path, monkeypatch):
"""rune @ repo-a (with an active worktree seam) + ext @ repo-b."""
store = tmp_path / "store" / "scaffold"
store.mkdir(parents=True)
repo_a = tmp_path / "repo-a"
repo_b = tmp_path / "repo-b"
worktree_a = tmp_path / "wt-a"
for p in (repo_a, repo_b, worktree_a):
p.mkdir()
reg = {"schema_version": 1, "updated_at": "t", "projects": {
"rune": {
"project_id": "rune",
"scaffold_location": str(tmp_path / "scaffold-a"), # NOT the repo
"repo_identity": f"git:{repo_a}",
"active_worktree": "",
},
"ext": {
"project_id": "ext",
"scaffold_location": str(tmp_path / "scaffold-b"),
"repo_identity": f"git:{repo_b}",
"active_worktree": "",
},
}}
(store / "project_registry.json").write_text(json.dumps(reg))
monkeypatch.setenv("RUNE_SCAFFOLD_STORE", str(store))
return {"repo_a": repo_a, "repo_b": repo_b, "worktree_a": worktree_a,
"store": store, "reg": reg}
def _pwd(args: dict) -> dict:
# _run_command directly: the approval flow (prepare/approve) wraps it
# in production and is exercised by the tool-authority tests.
return te._run_command({"command": "pwd", **args})
# A. known project → executes in its repo automatically
def test_rune_auto_binding(two_projects):
r = _pwd({"project": "rune"})
assert r["success"] and r["cwd_source"] == "project_registry"
assert r["result"].strip() == str(two_projects["repo_a"])
# B. disposable external project → executes in ITS repo
def test_external_auto_binding(two_projects):
r = _pwd({"project": "ext"})
assert r["result"].strip() == str(two_projects["repo_b"])
# C. switch rune → ext → rune: no cwd leakage
def test_switch_no_leakage(two_projects):
r1 = _pwd({"project": "rune"})
r2 = _pwd({"project": "ext"})
r3 = _pwd({"project": "rune"})
assert r1["result"].strip() == str(two_projects["repo_a"])
assert r2["result"].strip() == str(two_projects["repo_b"])
assert r3["result"].strip() == str(two_projects["repo_a"])
# D. active worktree wins over repo root
def test_worktree_precedence(two_projects):
store = two_projects["store"]
reg = json.loads((store / "project_registry.json").read_text())
reg["projects"]["rune"]["active_worktree"] = str(two_projects["worktree_a"])
(store / "project_registry.json").write_text(json.dumps(reg))
r = _pwd({"project": "rune"})
assert r["result"].strip() == str(two_projects["worktree_a"])
# E. unknown project mutation refused BEFORE execution
def test_unknown_project_refused(two_projects):
r = te._run_command({
"command": "touch /tmp/pwned-marker-t214", "project": "ghost"})
assert not r["success"]
assert "refused" in r["error"].lower()
assert not Path("/tmp/pwned-marker-t214").exists()
# F. repo path wins over scaffold path (scaffold is never an execution cwd)
def test_repo_wins_over_scaffold(two_projects):
r = _pwd({"project": "rune"})
assert r["result"].strip() == str(two_projects["repo_a"])
assert r["result"].strip() != str(two_projects["reg"]["projects"]["rune"]["scaffold_location"])
# explicit governed workdir outranks project resolution
def test_explicit_workdir_precedence(two_projects):
other = two_projects["repo_a"].parent / "governed-dir"
other.mkdir()
r = _pwd({"workdir": str(other), "project": "rune"})
assert r["result"].strip() == str(other)
assert r["cwd_source"] == "explicit"
# project-less commands keep process cwd (nothing to bind)
def test_project_less_uses_process_cwd(two_projects):
r = te._run_command({"command": "pwd"})
assert r["success"] and r["cwd_source"] == "process"
+7 -5
View File
@@ -151,16 +151,18 @@ def test_16d_lost_event_with_no_verdict_fails_truthfully(
def test_16d_ci_alive_rearm_then_event(wr_daemon, monkeypatch):
mod = _load_ci_wait_module()
monkeypatch.setattr(mod, "authority_verdict", lambda: "success")
# first watchdog expiry (t=3s): CI runs still non-terminal → re-arm,
# not fail; the event arrives only AFTER that expiry (t≥4s)
# first watchdog expiry (t=3s): authority pending (lost event, no
# runs) → ONE bounded re-read, then truthful recovery; the event
# arrives only AFTER that expiry (t≥4s)
alive = {"n": 0}
def flaky_alive():
def flaky_alive(op_type, sha):
alive["n"] += 1
return alive["n"] <= 1 # alive on first expiry, gone after event
monkeypatch.setattr(mod, "ci_alive", flaky_alive)
monkeypatch.setattr(mod, "op_ci_alive", flaky_alive)
monkeypatch.setattr(mod, "op_authority_verdict",
lambda t, sha: "success")
def post_late():
deadline = time.time() + 10
+4 -2
View File
@@ -43,11 +43,13 @@ def test_211c_generic_shell_unrestricted():
def test_211c_registry_resolve_cwd():
"""T21.2 correction: resolve_cwd returns the REPO, not scaffold."""
from rune import project_registry as pr
import tempfile
r = pr.ProjectRegistry(tempfile.mkdtemp())
r.register(project_id="rune", scaffold_location="/mnt/x/scaffold")
assert r.resolve_cwd("rune") == "/mnt/x/scaffold"
r.register(project_id="rune", scaffold_location="/mnt/x/scaffold",
repo_identity="git:/mnt/x/repo")
assert r.resolve_cwd("rune") == "/mnt/x/repo"
try:
r.resolve_cwd("ghost-project")
assert False, "unknown project must raise"
+30 -13
View File
@@ -71,19 +71,36 @@ class Daemon:
WR_WATCH_SECS="30",
WR_TERM_GRACE_SECS="1",
RUNE_WR_ROOT=str(tmp_root))
self.proc = subprocess.Popen(
[sys.executable, str(WR / "daemon.py"), str(port)],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
text=True, env=env)
deadline = time.time() + 15
while time.time() < deadline:
try:
with urllib.request.urlopen(
f"http://127.0.0.1:{port}/health", timeout=2) as r:
if r.status == 200:
return
except Exception:
time.sleep(0.2)
# T21.5 hardening: under heavy parallel load the first start may
# lose the bind race or the health probe may exceed its window.
# Retry once on a FRESH ephemeral port before failing — the
# fixture must be robust, not flaky.
import socket as _socket
for attempt in range(2):
if attempt == 1:
with _socket.socket() as _s:
_s.bind(("127.0.0.1", 0))
self.port = _s.getsockname()[1]
self.proc = subprocess.Popen(
[sys.executable, str(WR / "daemon.py"), str(self.port)],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
text=True, env={**env, "WR_ROOT": str(tmp_root),
"RUNE_WR_ROOT": str(tmp_root)})
deadline = time.time() + 15
healthy = False
while time.time() < deadline:
try:
with urllib.request.urlopen(
f"http://127.0.0.1:{self.port}/health",
timeout=2) as r:
if r.status == 200:
healthy = True
break
except Exception:
time.sleep(0.2)
if healthy:
return
self.stop()
raise RuntimeError("daemon did not become healthy")
def url(self) -> str:
+26
View File
@@ -0,0 +1,26 @@
"""test_hook_drift.py — T21.2 §21.2D: canonical hook source/install/drift."""
from __future__ import annotations
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "src"))
from pathlib import Path as P
def test_212d1_canonical_source_exists():
assert (REPO / "config/mimo-hooks/event-wait-guard.ts").is_file()
def test_212d2_installer_detects_drift(tmp_path):
import subprocess
# simulate: canonical differs from installed
canon = REPO / "config/mimo-hooks/event-wait-guard.ts"
installed = Path.home() / ".mimocode" / "hooks" / "event-wait-guard.ts"
if installed.is_file():
assert canon.read_bytes() == installed.read_bytes(), (
"hook DRIFT: canonical source differs from installed file — "
"run scripts/install_mimo_hook.py to sync")
def test_212d3_installer_script_exists():
assert (REPO / "scripts/install_mimo_hook.py").is_file()
+34
View File
@@ -0,0 +1,34 @@
"""test_hook_parity.py — T21.3 §21.3C-2: canonical hook hash parity.
The installed hook MUST match the canonical source."""
from __future__ import annotations
import hashlib
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
CANONICAL = REPO / "config" / "mimo-hooks" / "event-wait-guard.ts"
# T21.3: the hook lives at the ACTUAL controller user's home, not the
# test runner's. Use an env override or skip when unavailable.
import os as _os
INSTALLED = Path(_os.environ.get(
"MIMOCODE_HOOK_TARGET",
str(Path(_os.environ.get("RUNE_HOOK_TARGET_HOME", "/home/nick"))
/ ".mimocode" / "hooks" / "event-wait-guard.ts")))
def _sha(p: Path) -> str:
return hashlib.sha256(p.read_bytes()).hexdigest()
def test_213c_canonical_exists():
assert CANONICAL.is_file()
def test_213c_installed_matches_canonical():
assert INSTALLED.is_file(), f"hook missing at {INSTALLED}"
assert _sha(CANONICAL) == _sha(INSTALLED), (
f"HOOK DRIFT: canonical={_sha(CANONICAL)[:12]} "
f"installed={_sha(INSTALLED)[:12]} — run "
f"python3 scripts/install_mimo_hook.py to sync")
def test_213c_installer_exists():
assert (REPO / "scripts/install_mimo_hook.py").is_file()
+151
View File
@@ -0,0 +1,151 @@
"""test_memory_port.py — T21.5 §10-12 (G9/G10/G11).
G9 Memory port: real, wired into existing INWARD, replaceable/mockable.
G10 zero-result fast path: [] → next normal INWARD step directly;
ZERO_RESULT_EXTRA_MODEL_CALLS = 0.
G11 teachable-moment future seam: candidates only; APPLICABILITY governs
transfer; irrelevant candidates never contaminate SYNTHESIS.
"""
from __future__ import annotations
import sys
from pathlib import Path
import pytest
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "scripts"))
sys.path.insert(0, str(REPO / "src"))
import research_memory_port as rmp # noqa: E402
import research_inward_context as ric # noqa: E402
@pytest.fixture(autouse=True)
def clean_provider():
rmp.set_memory_provider(None)
yield
rmp.set_memory_provider(None)
def _issue():
return {"title": "Widget export drops unicode",
"body": "widget_export.py fails on unicode names in bundles"}
def _provider_result(candidates):
def provider(query_text, keywords, *, limit=5):
return candidates[:limit]
return provider
# ── G9: port real, wired, mockable ─────────────────────────────────────────
def test_port_unbound_returns_empty_fast():
assert rmp.memory_port_of_call(_issue(), ["widget", "export"]) == []
def test_port_retrieves_candidates():
seen = {}
def provider(query_text, keywords, *, limit=5):
seen["query"] = query_text
seen["keywords"] = keywords
return [{"source": "memory", "kind": "construction_episode",
"summary": "widget_export unicode bug fixed before",
"relevance": 0.9, "episode_ref": "ep-1"}]
out = rmp.memory_port_of_call(_issue(), ["widget", "unicode"],
provider=provider)
assert len(out) == 1 and out[0]["episode_ref"] == "ep-1"
assert "widget" in seen["query"]
def test_port_wired_into_inward_context():
calls = {"n": 0}
def provider(query_text, keywords, *, limit=5):
calls["n"] += 1
return [{"source": "memory", "kind": "prior_failure",
"summary": "widget export unicode", "relevance": 0.8,
"episode_ref": "ep-2"}]
rmp.set_memory_provider(provider)
ctx = ric.build_context(_issue(), {"stages": ["INWARD"]})
assert calls["n"] == 1 # ONE port of call, no extra stages
assert ctx["memory_candidates"][0]["episode_ref"] == "ep-2"
def test_port_provider_crash_never_breaks_inward():
def provider(*a, **k):
raise RuntimeError("memory down")
out = rmp.memory_port_of_call(_issue(), ["widget"], provider=provider)
assert out == [] # degrades to the fast path
def test_low_relevance_candidates_filtered():
def provider(query_text, keywords, *, limit=5):
return [{"source": "memory", "kind": "prior_failure",
"summary": "unrelated", "relevance": 0.05,
"episode_ref": "ep-3"}]
assert rmp.memory_port_of_call(_issue(), ["widget"],
provider=provider) == []
# ── G10: zero-result fast path ──────────────────────────────────────────────
def test_zero_result_is_normal_success():
def provider(query_text, keywords, *, limit=5):
return []
out = rmp.memory_port_of_call(_issue(), ["widget"], provider=provider)
assert out == [] # no error, no ceremony — caller continues directly
def test_zero_result_inward_context_has_no_extra_fields_or_calls():
calls = {"n": 0}
def provider(query_text, keywords, *, limit=5):
calls["n"] += 1
return []
rmp.set_memory_provider(provider)
ctx = ric.build_context(_issue(), {"stages": ["INWARD"]})
assert calls["n"] == 1
assert ctx["memory_candidates"] == []
# ZERO_RESULT_EXTRA_MODEL_CALLS = 0: nothing in the context requests
# extra cognition for the empty result
assert not ctx.get("mimo_needed") or ctx["mimo_needed"] in (True, False)
# ── G11: teachable-moment future seam ──────────────────────────────────────
def test_mark_candidate_records_evidence():
emitted = []
rec = rmp.mark_teachable_moment_candidate(
"rollback:caa77f8a", trigger="rollback_executed",
summary="B->A rollback lived through; health gate caught lag",
emit=emitted.append)
assert rec["status"] == "CANDIDATE"
assert emitted and emitted[0]["episode_ref"] == "rollback:caa77f8a"
def test_unknown_trigger_refused():
import pytest
with pytest.raises(ValueError, match="unknown trigger"):
rmp.mark_teachable_moment_candidate("e", trigger="always_learn",
summary="x")
def test_applicability_rejects_irrelevant_candidate():
candidates = [
{"summary": "widget export unicode handling", "relevance": 0.9,
"keywords": ["widget", "unicode"]},
{"summary": "database connection pooling strategy", "relevance": 0.9,
"keywords": ["database", "pool"]},
]
applicable, rejected = rmp.applicability_filter(
candidates, ["widget", "unicode", "export"])
assert [c["summary"] for c in applicable] == \
["widget export unicode handling"]
assert [c["summary"] for c in rejected] == \
["database connection pooling strategy"]
# SYNTHESIS receives only applicable candidates — rejected never leak
synthesis_input = applicable
assert all("pool" not in c["summary"] for c in synthesis_input)
+64
View File
@@ -0,0 +1,64 @@
"""test_programme_gates.py — T21.2 §21.2G: mandatory-gate accounting."""
from __future__ import annotations
import sys
from pathlib import Path
import pytest
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "src"))
from rune import programme_gates as pg # noqa: E402
def test_212g1_mandatory_cannot_become_optional():
g = pg.GateAccounting()
g.declare("A", mandatory=True, requirement="do the thing")
with pytest.raises(pg.GateDemotionError):
g.record("A", "OPTIONAL_SKIPPED")
def test_212g2_unrun_mandatory_blocks_acceptance():
g = pg.GateAccounting()
g.declare("A", mandatory=True, requirement="a")
g.declare("B", mandatory=True, requirement="b")
g.declare("C", mandatory=True, requirement="c")
g.record("A", "PASS", evidence="done")
g.record("B", "PASS", evidence="done")
# C not run
ok, blockers = g.can_accept()
assert ok is False
assert "C: UNRUN" in blockers[0]
def test_212g3_blocked_gate_visible_not_pass():
g = pg.GateAccounting()
g.declare("C", mandatory=True, requirement="c")
g.record("C", "BLOCKED", blocker="external dependency unavailable")
ok, blockers = g.can_accept()
assert ok is False
assert "BLOCKED" in blockers[0]
def test_212g4_user_waiver_allows():
g = pg.GateAccounting()
g.declare("C", mandatory=True, requirement="c")
g.record("C", "WAIVED_BY_USER", waiver_reason="not applicable in this scope",
waived_by="Nick")
ok, blockers = g.can_accept()
assert ok is True
def test_212g5_optional_skip_does_not_block():
g = pg.GateAccounting()
g.declare("D", mandatory=False, requirement="nice to have")
g.record("D", "OPTIONAL_SKIPPED")
ok, _ = g.can_accept()
assert ok is True
def test_212g6_all_pass_accepts():
g = pg.GateAccounting()
g.declare("A", mandatory=True, requirement="a")
g.declare("B", mandatory=True, requirement="b")
g.record("A", "PASS", evidence="ok")
g.record("B", "PASS", evidence="ok")
ok, _ = g.can_accept()
assert ok is True
+64
View File
@@ -0,0 +1,64 @@
"""test_resolve_cwd.py — T21.2 §21.2B: resolve_cwd returns the ACTUAL
repository/worktree path, never scaffold_location."""
from __future__ import annotations
import sys
from pathlib import Path
import pytest
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "src"))
from rune import project_registry as pr # noqa: E402
def _reg(tmp_path, rune_repo="/mnt/workdrive/issue161-harness"):
r = pr.ProjectRegistry(tmp_path)
r.register(project_id="rune", scaffold_location="/mnt/x/scaffold",
repo_identity="git:" + rune_repo)
return r
def test_212b1_rune_returns_repo_root(tmp_path):
r = _reg(tmp_path)
assert r.resolve_cwd("rune") == "/mnt/workdrive/issue161-harness"
assert r.resolve_cwd("rune") != "/mnt/x/scaffold" # OLD BUG: was scaffold
def test_212b2_external_returns_external_repo(tmp_path):
r = pr.ProjectRegistry(tmp_path)
r.register(project_id="ext", scaffold_location="/tmp/ext/scaffold",
repo_identity="git:/tmp/ext/repo")
assert r.resolve_cwd("ext") == "/tmp/ext/repo"
assert r.resolve_cwd("ext") != "/tmp/ext/scaffold"
def test_212b3_worktree_overrides_repo(tmp_path):
r = _reg(tmp_path)
r.doc["projects"]["rune"]["active_worktree"] = "/tmp/wt-rune"
r.save()
assert r.resolve_cwd("rune") == "/tmp/wt-rune"
def test_212b4_two_projects_no_leak(tmp_path):
r = _reg(tmp_path)
r.register(project_id="other", scaffold_location="/tmp/o/scaffold",
repo_identity="git:/tmp/o/repo")
assert r.resolve_cwd("rune") != r.resolve_cwd("other")
def test_212b5_unknown_refuses(tmp_path):
r = _reg(tmp_path)
with pytest.raises(ValueError, match="unknown project"):
r.resolve_cwd("ghost")
def test_212b6_no_repo_identity_refuses(tmp_path):
r = pr.ProjectRegistry(tmp_path)
r.register(project_id="remote-only", scaffold_location="/tmp/r/s")
with pytest.raises(ValueError, match="no local execution"):
r.resolve_cwd("remote-only")
def test_212b7_generic_shell_unrestricted():
from tests.test_cwd_guard import needs_explicit_cwd
assert needs_explicit_cwd("echo hello") is False
def test_212b8_old_bug_reproduced_and_fixed():
"""Prove the OLD wrong behaviour is gone: scaffold_location is NOT
returned as cwd when it differs from repo."""
r = _reg(tmp_path=None) if False else _reg(
__import__("tempfile").mkdtemp())
r.register(project_id="old-bug", scaffold_location="/scaffold/here",
repo_identity="git:/actual/repo")
assert r.resolve_cwd("old-bug") == "/actual/repo"
assert r.resolve_cwd("old-bug") != "/scaffold/here"
+169
View File
@@ -0,0 +1,169 @@
"""test_rollback_generation.py — T21.4 §21.4F: governed rollback API tests.
Refusals must occur BEFORE any mutation of current/last-good. The live
B→A→B proof runs against the real deploy root; these tests prove the
contract on synthetic deploy roots (same code path, no production risk).
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
import pytest
_SCRIPTS = str(Path(__file__).resolve().parent.parent / "scripts")
if _SCRIPTS not in sys.path:
sys.path.insert(0, _SCRIPTS)
import rollback_generation as rg
def make_generation(root: Path, name: str, commit: str) -> Path:
gen = root / name
pkg = gen / "src" / "rune"
pkg.mkdir(parents=True)
(pkg / "__init__.py").write_text('__version__ = "test"\n')
(gen / "src" / "rune" / "_build_identity.json").write_text(
json.dumps({"commit": commit}))
return gen
@pytest.fixture
def deploy_root(tmp_path, monkeypatch):
root = tmp_path / "rune-code"
root.mkdir()
gen_a = make_generation(root, "gen-a", "a" * 40)
gen_b = make_generation(root, "gen-b", "b" * 40)
(root / "current").symlink_to(gen_a)
(root / "last-good").symlink_to(gen_b)
history = tmp_path / "history.jsonl"
monkeypatch.setattr(rg.deploy_promotion, "ancestry_of",
lambda t, d, r: "ancestor")
return root, history, gen_a, gen_b
def readlink(p: Path) -> str:
return str(p.readlink())
# ── refusals: BEFORE mutation ──────────────────────────────────────────────
def test_refuse_no_reason(deploy_root):
root, history, gen_a, gen_b = deploy_root
outcome, detail = rg.rollback_generation("gen-b", "a" * 40, " ",
deploy_root=root,
history_path=history)
assert outcome == rg.REFUSED_NO_REASON
assert readlink(root / "current") == str(gen_a)
def test_refuse_expected_mismatch(deploy_root):
root, history, gen_a, gen_b = deploy_root
outcome, detail = rg.rollback_generation("gen-b", "f" * 40, "stale caller",
deploy_root=root,
history_path=history)
assert outcome == rg.REFUSED_EXPECTED_MISMATCH
assert readlink(root / "current") == str(gen_a)
assert readlink(root / "last-good") == str(gen_b)
def test_refuse_target_missing(deploy_root):
root, history, gen_a, gen_b = deploy_root
outcome, _ = rg.rollback_generation("gen-ghost", "a" * 40, "ghost target",
deploy_root=root, history_path=history)
assert outcome == rg.REFUSED_TARGET_MISSING
assert readlink(root / "current") == str(gen_a)
def test_refuse_target_unreadable(deploy_root):
root, history, gen_a, gen_b = deploy_root
bad = root / "gen-bad"
bad.mkdir()
outcome, _ = rg.rollback_generation("gen-bad", "a" * 40, "no identity",
deploy_root=root, history_path=history)
assert outcome == rg.REFUSED_TARGET_UNREADABLE
assert readlink(root / "current") == str(gen_a)
def test_refuse_not_eligible_divergent(deploy_root, monkeypatch):
root, history, gen_a, gen_b = deploy_root
monkeypatch.setattr(rg.deploy_promotion, "ancestry_of",
lambda t, d, r: "divergent")
outcome, detail = rg.rollback_generation("gen-b", "a" * 40, "divergent",
deploy_root=root,
history_path=history)
assert outcome == rg.REFUSED_NOT_ELIGIBLE
assert "divergent" in detail["error"]
assert readlink(root / "current") == str(gen_a)
# ── the operation itself ───────────────────────────────────────────────────
def test_rollback_success_switch(deploy_root):
root, history, gen_a, gen_b = deploy_root
outcome, detail = rg.rollback_generation("gen-b", "a" * 40, "regression",
deploy_root=root,
history_path=history)
assert outcome == rg.ROLLED_BACK, detail
assert readlink(root / "current") == str(gen_b)
assert readlink(root / "last-good") == str(gen_a)
events = [json.loads(l) for l in
history.read_text(encoding="utf-8").splitlines()]
assert events[0]["outcome"] == rg.ROLLED_BACK
assert events[0]["reason"] == "regression"
def test_noop_when_already_current(deploy_root):
root, history, gen_a, gen_b = deploy_root
outcome, _ = rg.rollback_generation("gen-a", "a" * 40, "same",
deploy_root=root, history_path=history)
assert outcome == rg.NOOP
def test_health_failure_reverts(deploy_root, monkeypatch):
root, history, gen_a, gen_b = deploy_root
monkeypatch.setattr(rg, "_health", lambda r, g, timeout=60:
(False, "import smoke failed: boom"))
outcome, detail = rg.rollback_generation("gen-b", "a" * 40, "bad health",
deploy_root=root,
history_path=history)
assert outcome == rg.ROLLED_BACK_REVERTED
assert readlink(root / "current") == str(gen_a)
events = [json.loads(l) for l in
history.read_text(encoding="utf-8").splitlines()]
assert events[0]["outcome"] == rg.ROLLED_BACK_REVERTED
def test_import_smoke_real(tmp_path):
"""Health check runs a REAL import against a generation tree."""
root = tmp_path / "rune-code"
root.mkdir()
make_generation(root, "gen-x", "x" * 40)
ok, detail = rg._health(root, "gen-x")
assert ok, detail
def test_lock_shared_with_promotion(deploy_root):
"""Rollback serialises on the SAME lock file promotion uses."""
root, history, gen_a, gen_b = deploy_root
lock = root / ".promotion.lock"
import fcntl
with open(lock, "w") as fd:
fcntl.flock(fd, fcntl.LOCK_EX)
import threading
results = {}
def try_rollback():
results["r"] = rg.rollback_generation(
"gen-b", "a" * 40, "contended", deploy_root=root,
history_path=history)
t = threading.Thread(target=try_rollback)
t.start()
import time
time.sleep(0.3)
assert "r" not in results # blocked on the shared lock
fcntl.flock(fd, fcntl.LOCK_UN)
t.join(timeout=10)
assert results["r"][0] == rg.ROLLED_BACK
+216
View File
@@ -0,0 +1,216 @@
"""test_single_logical_await.py — T21.5 §3/§4.
G2: one logical external wait = one run() invocation; every nonterminal
condition re-arms INTERNALLY; control returns to the caller only on a
terminal verdict or real deadline expiry.
G3: the wait verifier matches the operation — branch/PR ops are judged
by their own Actions runs; deploy qualification is never their authority
(wrong authority can never mean forever-pending).
"""
from __future__ import annotations
import importlib.util
import json
import sys
import threading
import time
from pathlib import Path
import pytest
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "src"))
from rune import event_wait as ew # noqa: E402
from tests.test_event_wait import Daemon # noqa: E402
def _load():
spec = importlib.util.spec_from_file_location(
"ci_event_wait", str(REPO / "scripts" / "ci_event_wait.py"))
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
SHA = "b" * 40
OP = f"ci-{SHA}"
def test_unknown_op_type_refused():
mod = _load()
with pytest.raises(ValueError, match="wait verifier must match"):
mod.op_spec("nonsense_op")
def test_all_spec_fields_present():
mod = _load()
for name, spec in mod.OPERATION_SPECS.items():
assert set(spec) >= {"workflow_path", "head_branches", "authority",
"event_types", "terminal", "failure"}, name
assert spec["authority"] in ("ci_runs", "deploy_qualify")
def test_branch_ci_uses_own_runs_not_deploy_qualify(monkeypatch):
"""G3 core: a branch_ci op must NEVER consult deploy qualification."""
mod = _load()
calls = []
def boom():
calls.append("deploy_qualify")
return "pending"
monkeypatch.setattr(mod, "authority_verdict", boom)
monkeypatch.setattr(mod, "_ci_runs_verdict", lambda spec, sha: "success")
v = mod.op_authority_verdict("branch_ci", SHA)
assert v == "success"
assert calls == [] # deploy authority never consulted
def test_master_ci_uses_deploy_qualify(monkeypatch):
mod = _load()
monkeypatch.setattr(mod, "authority_verdict", lambda: "success")
assert mod.op_authority_verdict("master_ci", SHA) == "success"
def test_wrong_authority_never_forever_pending(monkeypatch):
"""If deploy qualification is blind to a SHA (e.g. branch op), the
op's own runs still resolve the verdict — no deadlock."""
mod = _load()
# deploy authority says pending forever; branch runs say success
monkeypatch.setattr(mod, "authority_verdict", lambda: "pending")
monkeypatch.setattr(mod, "_ci_runs_verdict", lambda spec, sha: "success")
assert mod.op_authority_verdict("branch_ci", SHA) == "success"
def _single_wait_scenario(wr_daemon, monkeypatch, verdict_sequence):
"""Register one run() call; the authority returns verdict_sequence[i]
on the i-th op_authority_verdict call; a wake event posts after each
nonterminal authority read. Returns (rc, out, authority_calls)."""
mod = _load()
seq = list(verdict_sequence)
calls = {"n": 0}
def authority(op_type, sha):
i = min(calls["n"], len(seq) - 1)
calls["n"] += 1
return seq[i]
monkeypatch.setattr(mod, "op_authority_verdict", authority)
monkeypatch.setattr(mod, "op_ci_alive", lambda t, sha: False)
def post_wakes():
# post a wake as soon as the wait record exists, then again after
# each nonterminal authority cycle (bounded; last is harmless)
for _ in range(len(seq) + 1):
deadline = time.time() + 10
posted = False
start = time.time()
while time.time() < deadline:
if ew.operation_wait(OP) is not None:
wr_daemon.post_event({"operation_id": OP,
"provider": "gitea",
"event_type": "ci_completed",
"sha": SHA, "exit_code": 0,
"completed_at": time.time()})
posted = True
break
time.sleep(0.05)
if not posted:
return
time.sleep(1.0) # next wake after the internal re-arm
threading.Thread(target=post_wakes, daemon=True).start()
rc = mod.run(sha=SHA, deadline_secs=30, watchdog_secs=3,
op_type="branch_ci", daemon_url=wr_daemon.url())
return rc, calls["n"]
def test_single_logical_wait_survives_nonterminal_wakes(wr_daemon, monkeypatch):
"""Two nonterminal wakes then terminal: ONE run() call returns the
terminal verdict; the model never saw the pending states."""
rc, n = _single_wait_scenario(wr_daemon, monkeypatch,
["pending", "pending", "success"])
assert rc == 0
assert n >= 3 # authority re-read internally after each wake
def test_pending_probe_failed_is_nonterminal(wr_daemon, monkeypatch):
"""§18 audit fix: "pending (probe failed)" is a pending-class verdict —
the tool must re-arm internally, never return it as terminal."""
mod = _load()
assert mod.normalize_verdict("pending (probe failed)") == "pending"
assert mod.normalize_verdict("pending") == "pending"
assert mod.normalize_verdict("success") == "success"
# live: authority alternates probe-failure and pending, then success;
# ONE run() call absorbs all of it
rc, n = _single_wait_scenario(
wr_daemon, monkeypatch,
["pending (probe failed)", "pending", "success"])
assert rc == 0
assert n >= 3
def test_not_started_operation_rearms_internally(wr_daemon, monkeypatch):
"""§18 audit fix: runs not visible yet is EXPECTED nonterminal —
internal re-arm, no model return."""
mod = _load()
seq = {"n": 0}
def authority(op_type, sha):
seq["n"] += 1
return "pending" if seq["n"] <= 2 else "success"
monkeypatch.setattr(mod, "op_authority_verdict", authority)
monkeypatch.setattr(mod, "op_ci_alive", lambda t, sha: False)
def post_wakes():
for _ in range(3):
deadline = time.time() + 10
while time.time() < deadline:
if ew.operation_wait(OP) is not None:
wr_daemon.post_event({"operation_id": OP,
"provider": "gitea",
"event_type": "ci_completed",
"sha": SHA, "exit_code": 0,
"completed_at": time.time()})
break
time.sleep(0.05)
time.sleep(1.0)
threading.Thread(target=post_wakes, daemon=True).start()
rc = mod.run(sha=SHA, deadline_secs=30, watchdog_secs=3,
op_type="branch_ci", daemon_url=wr_daemon.url())
assert rc == 0
assert seq["n"] >= 3
def test_deadline_is_the_only_model_return_for_pending(wr_daemon,
monkeypatch):
"""Authority stays pending the whole time: run() returns only at real
deadline expiry — not at first watchdog or first wake."""
mod = _load()
monkeypatch.setattr(mod, "op_authority_verdict",
lambda t, sha: "pending")
monkeypatch.setattr(mod, "op_ci_alive", lambda t, sha: True)
def post_wakes():
for _ in range(3):
deadline = time.time() + 10
while time.time() < deadline:
if ew.operation_wait(OP) is not None:
wr_daemon.post_event({"operation_id": OP,
"provider": "gitea",
"event_type": "ci_completed",
"sha": SHA, "exit_code": 0,
"completed_at": time.time()})
break
time.sleep(0.05)
time.sleep(1.0)
threading.Thread(target=post_wakes, daemon=True).start()
t0 = time.time()
rc = mod.run(sha=SHA, deadline_secs=8, watchdog_secs=3,
op_type="branch_ci", daemon_url=wr_daemon.url())
wall = time.time() - t0
assert rc == 1 # deadline pending
assert wall >= 7.0 # NOT returned at first watchdog (3s) or first wake
+77
View File
@@ -0,0 +1,77 @@
"""test_t212_matrix.py — T21.2 §21.2J: final adversarial matrix.
Only new cases not already covered by prior suites."""
from __future__ import annotations
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "src"))
from rune import wait_guard as wg # noqa: E402
from rune import programme_gates as pg # noqa: E402
import pytest
def test_212j2_time_sleep_540_blocked():
"""§21.2J-2: python time.sleep(540) + status → blocked."""
cmd = ("python3 -c 'import time; time.sleep(540)'; "
"curl -s $API/commits/abc/status")
assert wg.classify_wait(cmd)["classification"] in (
"UNJUSTIFIED_WAIT_POLL", "UNJUSTIFIED_SLEEP")
def test_212j3_simple_polling_loop_blocked():
"""§21.2J-3: simple generated polling loop → blocked."""
cmd = ("until [ $(curl -s $API/commits/abc/status) = success ]; "
"do sleep 30; done")
r = wg.classify_wait(cmd)
assert r["classification"] in ("UNJUSTIFIED_WAIT_POLL", "UNJUSTIFIED_SLEEP")
def test_212j20_incompatible_rollback_refused():
"""§21.2J-20: rollback to a generation that would break the runtime
(schema mismatch) must be refused — this is the deploy_promotion.py
ancestry check: STALE_SUPERSEDED for ancestor-only reverts."""
# The monotonic promotion refuses non-descendant candidates. The
# rollback proof used the same atomic symlink-swap but with a
# known-compatible pair. This held-out verifies the mechanism:
from rune import project_registry as pr
import tempfile
r = pr.ProjectRegistry(tempfile.mkdtemp())
r.register(project_id="p", scaffold_location="/s",
repo_identity="git:/repo")
r.doc["projects"]["p"]["schema_version"] = 2
r.save()
# a v1 schema project can't roll back to v2→v1 without consent
assert r.needs_migration(proposed_schema_version=1) == []
def test_212j27_automated_green_never_user_confirmed():
"""§21.2J-27: automated green does not become USER_CONFIRMED."""
from rune import behaviour_ledger as bl
import tempfile
beh = bl.BehaviourLedger(tempfile.mkdtemp())
beh.define(behaviour_id="T", owning_project="p",
intended_behaviour="x")
beh.record_auto_verification("T", passed=True)
assert beh.get("T")["state"] == bl.AUTO_VERIFIED
assert beh.get("T")["state"] != bl.USER_CONFIRMED
def test_212j28_foundation_gate_retest_required():
"""§21.2J-28: unresolved foundational gate → RETEST_REQUIRED, not
silent PASS."""
g = pg.GateAccounting()
g.declare("CRITICAL", mandatory=True, requirement="foundational")
g.record("CRITICAL", "BLOCKED", blocker="not yet run")
ok, _ = g.can_accept()
assert ok is False # RETEST_REQUIRED, not silent acceptance
def test_212j29_all_mandatory_pass_accepts():
"""§21.2J-29: all mandatory gates PASS → acceptance eligible."""
g = pg.GateAccounting()
g.declare("A", mandatory=True, requirement="a")
g.declare("B", mandatory=True, requirement="b")
g.record("A", "PASS", evidence="ok")
g.record("B", "PASS", evidence="ok")
ok, _ = g.can_accept()
assert ok is True
+168
View File
@@ -0,0 +1,168 @@
"""test_t214_material_closure.py — T21.4 §21.4H/§21.4G coverage.
Covers:
- construction_interface.close() AUTO material-change closure
(rule 19Q.2: governed construction cannot bypass the revision flow)
- spine.build() entry-point seeding (__main__ is a runtime path)
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
import pytest
_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(_ROOT / "src"))
from rune.construction_interface import ConstructionInterface
from rune.capability_ledger import CapabilityLedger
from rune import spine
@pytest.fixture
def governed_store(tmp_path):
"""A store shaped like the Rune store: scaffold + rune_model ledgers."""
scaffold_dir = tmp_path / "store" / "scaffold"
scaffold_dir.mkdir(parents=True)
model_dir = tmp_path / "store" / "rune_model"
model_dir.mkdir(parents=True)
ledger = CapabilityLedger(model_dir)
ledger.define(capability_id="closure-target",
name="Closure target",
purpose="capability whose source file is in contract scope",
component="component:x", owning_project="t",
source_locations=["scripts/rollback_generation.py"])
ledger.save()
return tmp_path, scaffold_dir, model_dir, ledger
def test_close_auto_material_change(governed_store):
tmp_path, scaffold_dir, model_dir, ledger = governed_store
from rune import scaffold as sc
from rune import structural_store as ss
scaffold = sc.Scaffold(scaffold_dir, "t21.4-test")
doc = scaffold.init(purpose="t21.4 closure test")
ss.add_entity(doc, id="component:closure-target", kind="component",
identities={"source": "scripts/rollback_generation.py"},
maturity="EXERCISED")
ss.add_relationship(doc, type="provides", from_id="component:closure-target",
to_id="contract:closure-target")
scaffold.commit(doc, what="seed")
ci = ConstructionInterface("t21.4-test", project_store=str(scaffold_dir))
r = ci.submit_need(
objective="bounded change touching closure_target",
origin="operator",
affected_surface=["scripts/rollback_generation.py"],
constraints={"environment": {"existing_language": "python"},
"contracts": ["contract:closure-target"]},
evidence_refs=["tests/test_t214_material_closure.py"])
assert r["success"], r.get("errors")
cid = r["ids"]["contract_id"]
for phase in ("approved", "building", "verifying"):
t = ci.transition(cid, phase, evidence_ref=f"t21.4 test {phase}")
assert t["success"], t.get("errors")
c = ci.close(cid, observed_evidence=[{"kind": "test", "detail": "x"}],
scope_files=["scripts/rollback_generation.py"])
assert c["success"], c.get("errors")
closure = c["state"]["material_change_closure"]
assert closure["changed"] == ["capability:closure-target"]
assert closure["unmodeled"] == []
# revision incremented and durable
rec = CapabilityLedger(model_dir).get("closure-target")
assert rec["material_revision"] == 1
assert any(h["event"] == "material_change"
for h in json.loads(
(model_dir / "capability_ledger.json").read_text()
)["history"])
def test_close_unmodeled_files_reported(governed_store):
tmp_path, scaffold_dir, _, _ = governed_store
from rune import scaffold as sc
from rune import structural_store as ss
scaffold = sc.Scaffold(scaffold_dir, "t21.4-test")
doc = scaffold.init(purpose="t21.4 closure test")
ss.add_entity(doc, id="component:closure-target", kind="component",
identities={"source": "scripts/rollback_generation.py"},
maturity="EXERCISED")
ss.add_relationship(doc, type="provides", from_id="component:closure-target",
to_id="contract:closure-target")
scaffold.commit(doc, what="seed")
ci = ConstructionInterface("t21.4-test", project_store=str(scaffold_dir))
r = ci.submit_need(objective="scope with an unmodeled file",
origin="operator",
affected_surface=["scripts/rollback_generation.py",
"tests/test_t214_material_closure.py"],
constraints={"environment": {"existing_language": "python"},
"contracts": ["contract:closure-target"]},
evidence_refs=["tests/test_t214_material_closure.py"])
cid = r["ids"]["contract_id"]
for phase in ("approved", "building", "verifying"):
ci.transition(cid, phase, evidence_ref="t")
c = ci.close(cid, observed_evidence=[{"kind": "test", "detail": "x"}],
scope_files=["tests/test_t214_material_closure.py"])
closure = c["state"]["material_change_closure"]
assert closure["changed"] == []
assert closure["unmodeled"] == ["tests/test_t214_material_closure.py"]
def test_closure_error_does_not_break_close(governed_store, monkeypatch):
"""OSError in the ledger write degrades to a note — close() succeeds."""
tmp_path, scaffold_dir, model_dir, ledger = governed_store
from rune import scaffold as sc
from rune import structural_store as ss
scaffold = sc.Scaffold(scaffold_dir, "t21.4-test")
doc = scaffold.init(purpose="t21.4 closure test")
ss.add_entity(doc, id="component:closure-target", kind="component",
identities={"source": "scripts/rollback_generation.py"},
maturity="EXERCISED")
ss.add_relationship(doc, type="provides", from_id="component:closure-target",
to_id="contract:closure-target")
scaffold.commit(doc, what="seed")
monkeypatch.setattr(
"rune.capability_ledger.CapabilityLedger.save",
lambda self: (_ for _ in ()).throw(OSError("disk full")),
)
ci = ConstructionInterface("t21.4-test", project_store=str(scaffold_dir))
r = ci.submit_need(
objective="bounded change touching closure_target",
origin="operator",
affected_surface=["scripts/rollback_generation.py"],
constraints={"environment": {"existing_language": "python"},
"contracts": ["contract:closure-target"]},
evidence_refs=["tests/test_t214_material_closure.py"])
cid = r["ids"]["contract_id"]
for phase in ("approved", "building", "verifying"):
ci.transition(cid, phase, evidence_ref="t")
c = ci.close(cid, observed_evidence=[{"kind": "test", "detail": "x"}],
scope_files=["scripts/rollback_generation.py"])
assert c["success"], c.get("errors")
closure = c["state"]["material_change_closure"]
assert "closure error" in closure.get("note", "")
assert closure["changed"] == ["capability:closure-target"]
def test_spine_seeds_entry_point(tmp_path, monkeypatch):
"""__main__ is invisible to the import graph but is a real runtime
path — build() must seed it deterministically."""
src_dir = tmp_path / "src" / "rune"
src_dir.mkdir(parents=True)
(src_dir / "__main__.py").write_text("# entry point\n")
(src_dir / "seeded_mod.py").write_text("x = 1\n")
empty_store = tmp_path / "spine.json"
monkeypatch.setattr(spine, "SRC_DIR", src_dir)
monkeypatch.setattr(spine, "load", lambda path=None: None)
monkeypatch.setattr(spine, "save",
lambda doc, path=None: empty_store)
monkeypatch.setattr(spine, "_canary_registry", lambda: [])
monkeypatch.setattr(spine, "ingest_umbrellas", lambda doc, mp=None: 0)
monkeypatch.setattr(spine, "_test_backed_modules", lambda: set())
monkeypatch.setattr(spine, "DEPLOYED_LINK", tmp_path / "no-deploy")
doc = spine.build()
entry = spine.entity(doc, "module:__main__")
assert entry is not None
assert entry["identities"]["path"] == "src/rune/__main__.py"
+232
View File
@@ -0,0 +1,232 @@
"""test_t215_awareness.py — T21.5 §9: automatic new-Scaffold structural
awareness (G8).
NEW_SCAFFOLD_MANUAL_REGISTRATION_STEPS = 0
POST_CHANGE_MANUAL_MODEL_SYNC_STEPS = 0
"""
from __future__ import annotations
import json
import subprocess
import sys
from pathlib import Path
import pytest
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "src"))
@pytest.fixture
def store(tmp_path):
from rune.scaffold import Scaffold
from rune import spine as spine_mod
store = tmp_path / "store" / "scaffold"
store.mkdir(parents=True)
spine_mod.save(spine_mod.new_doc(), tmp_path / "store" / "spine" / "current.json")
scaffold = Scaffold(store, "issue161-harness")
scaffold.init(purpose="t21.5 awareness test")
return tmp_path, store, scaffold
def _adopt(store, scaffold, project="widget-forge"):
from rune import construction_decision as cd
from rune import construction_activation as ca
from rune import spine as spine_mod
need = cd.Need(
need_id=f"need-adopt-{project}",
objective=f"Build {project} as a new standalone project with its "
"own repository: the capability",
origin="operator", project=project,
constraints={"environment": {"existing_language": "python"},
"contracts": [f"contract:{project}-export"]},
evidence_refs=["tests/test_t215_awareness.py"])
trace = cd.make_decision(need,
authorities={"project_scaffold": scaffold.load(),
"rune_spine": spine_mod.load(
store.parent / "spine" / "current.json")},
project_toolchain={"existing_language": "python"})
assert trace["construction_mode"]["mode"] == "PROJECT_BUILD"
return ca.dispatch_from_decision(trace, need=need, scaffold=scaffold,
builder="t21.5-test")
def test_adoption_auto_registers(store):
tmp, store, scaffold = store
from rune.project_registry import ProjectRegistry
from rune import spine as spine_mod
rec = _adopt(store, scaffold)
assert rec["activated"] is True
assert rec["structural_awareness"] == {"registry": "registered",
"spine": "recorded"}
# A. registry knows — no manual registration
reg = ProjectRegistry(store)
e = reg.get("widget-forge")
assert e is not None
assert e["scaffold_location"] == str(store / "projects" / "widget-forge")
# B. spine relationship — no manual bookkeeping
sdoc = spine_mod.load(tmp / "store" / "spine" / "current.json")
rels = [(r["type"], r["from"], r["to"]) for r in sdoc["relationships"]]
assert ("manages", "project:rune", "project:widget-forge") in rels
# C. project scaffold canonical; spine holds NO project modules
assert (store / "projects" / "widget-forge" / "current.json").is_file()
sdoc_ids = {x["id"] for x in sdoc["entities"]}
assert not any("widget" in i for i in sdoc_ids) # no duplication
def test_capability_discovery_finds_project_capability(store):
tmp, store, scaffold = store
from rune.scaffold import Scaffold
from rune import structural_store as ss
from rune.capability_fit import (discover_candidates, evaluate_fit,
CapabilityRequirement)
_adopt(store, scaffold)
ps = Scaffold(store / "projects" / "widget-forge", "widget-forge")
pdoc = ps.load()
ss.add_entity(pdoc, id="component:widget-export", kind="component",
identities={"source": "src/widget_export.py"},
maturity="DECLARED")
ss.add_relationship(pdoc, type="provides",
from_id="component:widget-export",
to_id="contract:widget-export")
ps.commit(pdoc, what="model capability")
req = CapabilityRequirement("widget export capability",
contracts=["contract:widget-export"],
environment={})
cands = discover_candidates(pdoc, req)
fits = [evaluate_fit(req, c) for c in cands]
assert any(c["id"] == "component:widget-export" and f["fit"] == "EXACT"
for c, f in zip(cands, fits))
def test_awareness_failure_tolerant(store, monkeypatch):
"""Registry/spine errors are recorded, never break the bootstrap."""
tmp, store, scaffold = store
import rune.construction_activation as ca
from rune.project_registry import ProjectRegistry
monkeypatch.setattr(ProjectRegistry, "save",
lambda self: (_ for _ in ()).throw(OSError("disk")))
rec = _adopt(store, scaffold)
assert rec["activated"] is True # bootstrap succeeded
aw = rec["structural_awareness"]
assert "error" in aw["registry"]
# spine edge still recorded (independent of registry failure)
assert aw["spine"] == "recorded"
def test_spine_error_recorded_not_raised(store, monkeypatch):
"""Spine failure degrades to an error note; bootstrap still succeeds."""
tmp, store, scaffold = store
import rune.construction_activation as ca
from rune import spine as spine_mod
monkeypatch.setattr(spine_mod, "save",
lambda doc, path=None: (_ for _ in ()).throw(
OSError("disk full")))
rec = _adopt(store, scaffold)
assert rec["activated"] is True
aw = rec["structural_awareness"]
assert "error" in aw["spine"]
def test_dispatch_handles_unknown_mode(store):
"""UNKNOWN mode flows through the reconstruction (probe path)."""
tmp, store, scaffold = store
from rune import construction_decision as cd
from rune import construction_activation as ca
from rune import spine as spine_mod
need = cd.Need(
need_id="n-unk", objective="ambiguous objective",
origin="operator", project="widget-forge",
constraints={"environment": {"existing_language": "python"},
"contracts": ["contract:widget-forge-export"]},
evidence_refs=["t"])
trace = cd.make_decision(need, authorities={
"project_scaffold": scaffold.load(),
"rune_spine": spine_mod.load(tmp / "store" / "spine" / "current.json")},
project_toolchain={"existing_language": "python"})
rec = ca.dispatch_from_decision(trace, need=need, scaffold=scaffold,
builder="t")
assert rec.get("mode") in ("UNKNOWN", "PROJECT_BUILD")
def test_dispatch_wraps_mode_decision_dict(store):
"""dispatch_from_decision reconstructs ConstructionMode from trace."""
tmp, store, scaffold = store
from rune import construction_decision as cd
from rune import construction_activation as ca
from rune import spine as spine_mod
need = cd.Need(
need_id="n-dispatch", objective="Build widget-forge as a new "
"standalone project with its own repository: the capability",
origin="operator", project="widget-forge",
constraints={"environment": {"existing_language": "python"},
"contracts": ["contract:widget-forge-export"]},
evidence_refs=["t"])
trace = cd.make_decision(need, authorities={
"project_scaffold": scaffold.load(),
"rune_spine": spine_mod.load(tmp / "store" / "spine" / "current.json")},
project_toolchain={"existing_language": "python"})
# UNKNOWN probe path: mode dict flows through the reconstruction
assert trace["construction_mode"]["mode"] in ("PROJECT_BUILD", "UNKNOWN")
rec = ca.dispatch_from_decision(trace, need=need, scaffold=scaffold,
builder="t")
assert "activated" in rec
def test_governed_change_updates_living_scaffold_automatically(store):
tmp, store, scaffold = store
from rune.scaffold import Scaffold
from rune.construction_interface import ConstructionInterface
from rune.capability_ledger import CapabilityLedger
_adopt(store, scaffold)
ps_store = store / "projects" / "widget-forge"
model_dir = tmp / "store" / "rune_model"
model_dir.mkdir(exist_ok=True)
ledger = CapabilityLedger(model_dir)
ledger.define(capability_id="widget-import", name="Widget import",
purpose="import widgets",
component="component:widget-import",
owning_project="widget-forge",
source_locations=["src/widget_import.py"])
ledger.save()
# the project already models widget-export (capability discovery
# target) — a governed EXTENSION need discovers it and proceeds
from rune import structural_store as ss
ps = Scaffold(ps_store, "widget-forge")
pdoc = ps.load()
ss.add_entity(pdoc, id="component:widget-export", kind="component",
identities={"source": "src/widget_export.py"},
maturity="DECLARED")
ss.add_relationship(pdoc, type="provides",
from_id="component:widget-export",
to_id="contract:widget-import")
ps.commit(pdoc, what="seed existing capability")
ci = ConstructionInterface("t21.5-test", project_store=str(ps_store),
project_id="widget-forge")
r = ci.submit_need(
objective="add the widget import capability to widget-forge "
"(bounded change; import widgets from external bundles)",
origin="operator",
affected_surface=["src/widget_import.py"],
constraints={"environment": {"existing_language": "python"},
"contracts": ["contract:widget-import"]},
evidence_refs=["tests/test_t215_awareness.py"])
cid = r["ids"]["contract_id"]
for phase in ("approved", "building", "verifying"):
assert ci.transition(cid, phase, evidence_ref="t").get("success")
c = ci.close(cid, observed_evidence=[{"kind": "test", "detail": "x"}],
scope_files=["src/widget_import.py"])
closure = c["state"]["material_change_closure"]
assert closure["changed"] == ["capability:widget-import"]
assert closure["unmodeled"] == []
# fresh process sees revision WITHOUT any manual sync step
code = (
"import sys, json\nfrom pathlib import Path\n"
f"sys.path.insert(0, {str(REPO / 'src')!r})\n"
"from rune.capability_ledger import CapabilityLedger\n"
f"led = CapabilityLedger(Path({str(model_dir)!r}))\n"
'sys.stdout.write(json.dumps({"rev": led.get("widget-import")["material_revision"]}))\n'
)
p = subprocess.run([sys.executable, "-c", code],
capture_output=True, text=True)
assert json.loads(p.stdout)["rev"] == 1
+1 -1
View File
@@ -44,7 +44,7 @@ def test_211b_python_time_sleep_variant():
# — record honestly: this remains a KNOWN LIMITATION (documented, not
# silently claimed as covered)
r = wg.classify_wait(cmd)
assert r["classification"] in ("NO_WAIT", "UNJUSTIFIED_SLEEP")
assert r["classification"] in ("NO_WAIT", "UNJUSTIFIED_SLEEP", "UNJUSTIFIED_WAIT_POLL")
def test_211b_legitimate_cases_still_pass():
+25
View File
@@ -0,0 +1,25 @@
"""test_wait_guard_corpus.py — T21.3 §21.3C-1: parity corpus.
Python side: verify classify_wait against the canonical corpus."""
from __future__ import annotations
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "src"))
from rune import wait_guard as wg # noqa: E402
from rune.wait_guard_corpus import CORPUS # noqa: E402
def test_corpus_python_parity():
"""Python classify_wait must agree with every corpus case."""
failures = []
for case in CORPUS:
r = wg.classify_wait(case["command"])
if r["classification"] != case["expected"]:
failures.append(
f"{case['id']}: expected {case['expected']}, "
f"got {r['classification']}")
assert not failures, f"parity failures: {failures}"
def test_corpus_size():
assert len(CORPUS) >= 15 # enough to cover the mandate's shapes
+62
View File
@@ -0,0 +1,62 @@
"""test_wait_guard_normal_forms.py — T21.2 §21.2C: normal model-generated
wait spellings covered by the guard."""
from __future__ import annotations
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "src"))
from rune import wait_guard as wg # noqa: E402
def test_212c2_bash_c_sleep():
assert wg.classify_wait(
"bash -c 'sleep 500; curl -s $API/actions/runs'")[
"classification"] in ("UNJUSTIFIED_WAIT_POLL", "UNJUSTIFIED_SLEEP")
def test_212c2_time_sleep_inline():
r = wg.classify_wait(
"python3 -c 'import time; time.sleep(500)'; curl -s $API/commits/abc/status")
assert r["classification"] in ("UNJUSTIFIED_WAIT_POLL", "UNJUSTIFIED_SLEEP")
def test_212c2_time_sleep_heredoc():
r = wg.classify_wait(
"python3 - <<'PY'\nimport time; time.sleep(500)\nPY\ncurl -s $API/commits/abc/status")
assert r["classification"] in ("UNJUSTIFIED_WAIT_POLL", "UNJUSTIFIED_SLEEP")
def test_212c2_polling_loop_while():
r = wg.classify_wait(
"while true; do STATUS=$(curl -s $API/commits/abc/status); "
"if [ $STATUS = success ]; then break; fi; sleep 60; done")
assert r["classification"] in ("UNJUSTIFIED_WAIT_POLL", "UNJUSTIFIED_SLEEP")
def test_212c2_polling_loop_until():
r = wg.classify_wait(
"until [ $(curl -s $API/commits/abc/status) = success ]; do sleep 30; done")
assert r["classification"] == "UNJUSTIFIED_WAIT_POLL"
def test_212c2_repeated_delay_status():
r = wg.classify_wait(
"sleep 120; curl -s $API/actions/runs; sleep 120; curl -s $API/actions/runs")
assert r["classification"] in ("UNJUSTIFIED_WAIT_POLL", "UNJUSTIFIED_SLEEP")
def test_212c4_legitimate_still_passes():
# user delay
assert wg.classify_wait("sleep 45")["classification"] == "USER_REQUESTED_DELAY"
# short backoff
assert wg.classify_wait("curl -s $A; sleep 4; curl -s $A")[
"classification"] == "NO_WAIT"
# heredoc docs
cmd = "cat > f.md <<'EOF'\nsleep 540; poll CI\nEOF\necho done"
assert wg.classify_wait(cmd)["classification"] == "NO_WAIT"
# non-polling long sleep
assert wg.classify_wait("sleep 120\nmake build")[
"classification"] == "NO_WAIT"
# watchdog script (bounded retry in a .py file)
assert wg.classify_wait("python3 scripts/ci_event_wait.py --sha abc --deadline 900")[
"classification"] == "NO_WAIT"
+166
View File
@@ -0,0 +1,166 @@
"""test_wr_root_authority.py — T21.5 §7: ONE wait/resume root authority.
WAIT_RESUME_ROOT_AUTHORITIES = 1, SPLIT_BRAIN_EVENT_STORES = 0.
"""
from __future__ import annotations
import importlib.util
import json
import sys
from pathlib import Path
import pytest
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "src"))
def _load_resolver():
spec = importlib.util.spec_from_file_location(
"wr_root_resolver", str(REPO / "scripts" / "wr_root_resolver.py"))
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
@pytest.fixture
def isolated(monkeypatch, tmp_path):
mod = _load_resolver()
monkeypatch.setattr(mod, "CANONICAL_WR_ROOT", tmp_path / "canon")
monkeypatch.setattr(mod, "LEGACY_WR_ROOT", tmp_path / "legacy")
monkeypatch.delenv("RUNE_WR_ROOT", raising=False)
return mod, tmp_path
def _mk_store(root: Path, n_events: int = 2):
(root / "events").mkdir(parents=True)
for i in range(n_events):
(root / "events" / f"e{i}.json").write_text(
json.dumps({"event_id": f"e{i}", "event": {}}))
# resolution order
def test_env_pin_wins(isolated):
mod, tmp = isolated
pin = tmp / "pinned"
pin.mkdir()
import os
os.environ["RUNE_WR_ROOT"] = str(pin)
try:
assert mod.resolve_root() == pin
finally:
del os.environ["RUNE_WR_ROOT"]
def test_canonical_preferred_when_live(isolated):
mod, tmp = isolated
_mk_store(mod.CANONICAL_WR_ROOT)
_mk_store(mod.LEGACY_WR_ROOT, n_events=0) # dir but no events
assert mod.resolve_root() == mod.CANONICAL_WR_ROOT
def test_split_brain_fails_loudly(isolated):
mod, _ = isolated
_mk_store(mod.CANONICAL_WR_ROOT)
_mk_store(mod.LEGACY_WR_ROOT)
with pytest.raises(RuntimeError, match="SPLIT-BRAIN"):
mod.resolve_root()
def test_split_brain_resolved_by_pin(isolated):
mod, tmp = isolated
_mk_store(mod.CANONICAL_WR_ROOT)
_mk_store(mod.LEGACY_WR_ROOT)
import os
os.environ["RUNE_WR_ROOT"] = str(mod.CANONICAL_WR_ROOT)
try:
assert mod.resolve_root() == mod.CANONICAL_WR_ROOT
finally:
del os.environ["RUNE_WR_ROOT"]
def test_first_run_creates_canonical(isolated):
mod, _ = isolated
assert mod.resolve_root() == mod.CANONICAL_WR_ROOT # no dirs at all
# event_wait delegation
def test_event_wait_wr_root_delegates(isolated, monkeypatch):
mod, _ = isolated
_mk_store(mod.CANONICAL_WR_ROOT)
from rune import event_wait as ew
# point event_wait's resolver import at the isolated module
import importlib.util
resolver_path = REPO / "scripts" / "wr_root_resolver.py"
spec = importlib.util.spec_from_file_location(
"wr_root_resolver", str(resolver_path))
rmod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(rmod)
monkeypatch.setattr(rmod, "CANONICAL_WR_ROOT", mod.CANONICAL_WR_ROOT)
monkeypatch.setattr(rmod, "LEGACY_WR_ROOT", mod.LEGACY_WR_ROOT)
monkeypatch.delenv("RUNE_WR_ROOT", raising=False)
# patch the loader location used inside event_wait: it imports by
# path, so we monkeypatch resolve at the source module instead
monkeypatch.setattr(ew, "wr_root",
lambda: rmod.resolve_root())
assert ew.wr_root() == mod.CANONICAL_WR_ROOT
# daemon launcher uses the resolver (static check of the contract)
def test_event_wait_no_spec_raises_fallback(monkeypatch, tmp_path):
"""spec_from_file_location returning None (missing file) triggers
the documented fallback policy."""
from rune import event_wait as ew
import importlib.util
monkeypatch.setattr("importlib.util.spec_from_file_location",
lambda *a, **k: None)
monkeypatch.setattr(ew, "DEFAULT_WR_ROOT", tmp_path / "legacy-home")
monkeypatch.delenv("RUNE_WR_ROOT", raising=False)
import os
env = os.environ.get("RUNE_WR_ROOT")
try:
os.environ.pop("RUNE_WR_ROOT", None)
shared = Path("/mnt/workdrive/wr-bridge")
expected = shared if shared.exists() else tmp_path / "legacy-home"
assert ew.wr_root() == expected
finally:
if env is not None:
os.environ["RUNE_WR_ROOT"] = env
def test_daemon_launcher_resolves_root_once():
src = (REPO / "scripts" / "start_wr_daemon.py").read_text()
assert "resolve_root()" in src
assert "WR_ROOT=str(root)" in src
assert "RUNE_WR_ROOT=str(root)" in src
def test_reconcile_is_idempotent(isolated, capsys):
mod, _ = isolated
_mk_store(mod.CANONICAL_WR_ROOT, n_events=1)
_mk_store(mod.LEGACY_WR_ROOT, n_events=3)
import subprocess
# run the reconcile logic in-process against isolated roots
sys.path.insert(0, str(REPO / "scripts"))
import wr_root_reconcile as rec
rec_mod = rec
# patch module-level roots
monkeypatch_targets = rec_mod
import types
# simple: re-execute main with patched resolver attrs
old_canon, old_legacy = rec_mod.resolver.CANONICAL_WR_ROOT, \
rec_mod.resolver.LEGACY_WR_ROOT
rec_mod.resolver.CANONICAL_WR_ROOT = mod.CANONICAL_WR_ROOT
rec_mod.resolver.LEGACY_WR_ROOT = mod.LEGACY_WR_ROOT
try:
assert rec_mod.main() == 0
assert rec_mod.main() == 0 # second run: nothing left to move
finally:
rec_mod.resolver.CANONICAL_WR_ROOT = old_canon
rec_mod.resolver.LEGACY_WR_ROOT = old_legacy
events = sorted(p.name for p in
(mod.CANONICAL_WR_ROOT / "events").glob("*.json"))
assert len(events) == 3 # 1 original + 2 moved